Download and save bulk URL concurrentlyPerforming tasks concurrentlyDownload a file and update the current...

Describing a chess game in a novel

How does one measure the Fourier components of a signal?

Regex aa* = a*a?

Tikz: place node leftmost of two nodes of different widths

Probably overheated black color SMD pads

What does "^L" mean in c?

Do US professors/group leaders only get a salary, but no group budget?

Why Choose Less Effective Armour Types?

What exactly is this small puffer fish doing and how did it manage to accomplish such a feat?

Why is indicated airspeed rather than ground speed used during the takeoff roll?

A Ri-diddley-iley Riddle

How are passwords stolen from companies if they only store hashes?

Comment Box for Substitution Method of Integrals

Unnormalized Log Probability - RNN

What favor did Moody owe Dumbledore?

Do native speakers use "ultima" and "proxima" frequently in spoken English?

Turning a hard to access nut?

How to generate binary array whose elements with values 1 are randomly drawn

How can I wire 7 outdoor posts correctly?

Official degrees of earth’s rotation per day

Print a physical multiplication table

Light propagating through a sound wave

Fragmentation Level for Heaps

What are substitutions for coconut in curry?



Download and save bulk URL concurrently


Performing tasks concurrentlyDownload a file and update the current downloaded percentageConcurrent download in GoCLI app to download a .gitignore fileDownload pictures from RedditTesting validity of Proxy servers concurrentlyIssue tokens concurrently with synchronization per clientReading lines from a file and processing them concurrentlyReading a user credentials from file concurrentlyFinding the longest Collatz sequence using Go, concurrently













3












$begingroup$


I am new to Go and wrote program to download and save bulk URLs concurrently. It is working correctly, but I would like to make it more efficient and follow best practices.



package main

import (...)

// Connection String
const connectionString = "connectionString"

// Declare Channels
var wg sync.WaitGroup
var ch = make(chan *item) // For success item(s)
var errItems = make(chan *item) // For error item(s)

type item struct {
id int
url string
path string
content []byte
err error
}

func main() {
start := time.Now()
var basePath = os.Args[1]
var from, to = os.Args[2], os.Args[3]
db, err := sql.Open("sqlserver", connectionString)
if err != nil {
log.Fatal(err.Error())
}
rows, err := db.Query("SELECT [ImageID],[AccommodationlID],[Link],[FileName] FROM [dbo].[AccomodationImage] where AccommodationlID between @from and @to and FileName not like '%NoHotel.png%'", sql.Named("from", from), sql.Named("to", to))
if err != nil {
log.Fatal(err.Error())
}
defer db.Close()
for rows.Next() {
var item = item{}
var accId, name string
_ = rows.Scan(&item.id, &accId, &item.url, &name)
item.path = fmt.Sprintf("%s\%s\%s", basePath, accId, name)
wg.Add(1)
go downloadFile(&item)
go func() {
select {
case done := <-ch:
wg.Add(1)
go saveAndUpdateFile(db, done)
case errorItem := <-errItems:
wg.Add(1)
go printResult(errorItem)
}
}()
}
wg.Wait()
fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
}
func downloadFile(item *item) {
defer wg.Done()
resp, err := http.Get(item.url)
if err != nil {
item.content, item.err = nil, err
} else if resp.StatusCode != http.StatusOK {
item.content, item.err = nil, errors.New(resp.Status)
} else {
item.content, item.err = ioutil.ReadAll(resp.Body)
}
if item.err != nil {
errItems <- item
} else {
ch <- item
}
}
func saveAndUpdateFile(db *sql.DB, item *item) {
defer wg.Done()
if item.content == nil {
item.err = errors.New("Content is empty.")
} else {
dir := filepath.Dir(item.path)
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
item.err = err
} else {
item.err = ioutil.WriteFile(item.path, item.content, 0644)
}
}
if item.err == nil {
result, err := db.Exec("UPDATE [dbo].[AccomodationImage] SET IsRead = 1 WHERE ImageID = @id", sql.Named("id", item.id))
if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
item.err = errors.New("Update status failed.")
}
}
if item.err != nil {
errItems <- item
}
}
func printResult(item *item) {
defer wg.Done()
fmt.Println(item.toString())
}


I use a select statement to implement event so that when errItems or ch channels receive, save or print items. I also surrounded select statement with goroutine to prevent blocking.










share|improve this question









New contributor




MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$

















    3












    $begingroup$


    I am new to Go and wrote program to download and save bulk URLs concurrently. It is working correctly, but I would like to make it more efficient and follow best practices.



    package main

    import (...)

    // Connection String
    const connectionString = "connectionString"

    // Declare Channels
    var wg sync.WaitGroup
    var ch = make(chan *item) // For success item(s)
    var errItems = make(chan *item) // For error item(s)

    type item struct {
    id int
    url string
    path string
    content []byte
    err error
    }

    func main() {
    start := time.Now()
    var basePath = os.Args[1]
    var from, to = os.Args[2], os.Args[3]
    db, err := sql.Open("sqlserver", connectionString)
    if err != nil {
    log.Fatal(err.Error())
    }
    rows, err := db.Query("SELECT [ImageID],[AccommodationlID],[Link],[FileName] FROM [dbo].[AccomodationImage] where AccommodationlID between @from and @to and FileName not like '%NoHotel.png%'", sql.Named("from", from), sql.Named("to", to))
    if err != nil {
    log.Fatal(err.Error())
    }
    defer db.Close()
    for rows.Next() {
    var item = item{}
    var accId, name string
    _ = rows.Scan(&item.id, &accId, &item.url, &name)
    item.path = fmt.Sprintf("%s\%s\%s", basePath, accId, name)
    wg.Add(1)
    go downloadFile(&item)
    go func() {
    select {
    case done := <-ch:
    wg.Add(1)
    go saveAndUpdateFile(db, done)
    case errorItem := <-errItems:
    wg.Add(1)
    go printResult(errorItem)
    }
    }()
    }
    wg.Wait()
    fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
    }
    func downloadFile(item *item) {
    defer wg.Done()
    resp, err := http.Get(item.url)
    if err != nil {
    item.content, item.err = nil, err
    } else if resp.StatusCode != http.StatusOK {
    item.content, item.err = nil, errors.New(resp.Status)
    } else {
    item.content, item.err = ioutil.ReadAll(resp.Body)
    }
    if item.err != nil {
    errItems <- item
    } else {
    ch <- item
    }
    }
    func saveAndUpdateFile(db *sql.DB, item *item) {
    defer wg.Done()
    if item.content == nil {
    item.err = errors.New("Content is empty.")
    } else {
    dir := filepath.Dir(item.path)
    err := os.MkdirAll(dir, os.ModePerm)
    if err != nil {
    item.err = err
    } else {
    item.err = ioutil.WriteFile(item.path, item.content, 0644)
    }
    }
    if item.err == nil {
    result, err := db.Exec("UPDATE [dbo].[AccomodationImage] SET IsRead = 1 WHERE ImageID = @id", sql.Named("id", item.id))
    if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
    item.err = errors.New("Update status failed.")
    }
    }
    if item.err != nil {
    errItems <- item
    }
    }
    func printResult(item *item) {
    defer wg.Done()
    fmt.Println(item.toString())
    }


    I use a select statement to implement event so that when errItems or ch channels receive, save or print items. I also surrounded select statement with goroutine to prevent blocking.










    share|improve this question









    New contributor




    MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.







    $endgroup$















      3












      3








      3





      $begingroup$


      I am new to Go and wrote program to download and save bulk URLs concurrently. It is working correctly, but I would like to make it more efficient and follow best practices.



      package main

      import (...)

      // Connection String
      const connectionString = "connectionString"

      // Declare Channels
      var wg sync.WaitGroup
      var ch = make(chan *item) // For success item(s)
      var errItems = make(chan *item) // For error item(s)

      type item struct {
      id int
      url string
      path string
      content []byte
      err error
      }

      func main() {
      start := time.Now()
      var basePath = os.Args[1]
      var from, to = os.Args[2], os.Args[3]
      db, err := sql.Open("sqlserver", connectionString)
      if err != nil {
      log.Fatal(err.Error())
      }
      rows, err := db.Query("SELECT [ImageID],[AccommodationlID],[Link],[FileName] FROM [dbo].[AccomodationImage] where AccommodationlID between @from and @to and FileName not like '%NoHotel.png%'", sql.Named("from", from), sql.Named("to", to))
      if err != nil {
      log.Fatal(err.Error())
      }
      defer db.Close()
      for rows.Next() {
      var item = item{}
      var accId, name string
      _ = rows.Scan(&item.id, &accId, &item.url, &name)
      item.path = fmt.Sprintf("%s\%s\%s", basePath, accId, name)
      wg.Add(1)
      go downloadFile(&item)
      go func() {
      select {
      case done := <-ch:
      wg.Add(1)
      go saveAndUpdateFile(db, done)
      case errorItem := <-errItems:
      wg.Add(1)
      go printResult(errorItem)
      }
      }()
      }
      wg.Wait()
      fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
      }
      func downloadFile(item *item) {
      defer wg.Done()
      resp, err := http.Get(item.url)
      if err != nil {
      item.content, item.err = nil, err
      } else if resp.StatusCode != http.StatusOK {
      item.content, item.err = nil, errors.New(resp.Status)
      } else {
      item.content, item.err = ioutil.ReadAll(resp.Body)
      }
      if item.err != nil {
      errItems <- item
      } else {
      ch <- item
      }
      }
      func saveAndUpdateFile(db *sql.DB, item *item) {
      defer wg.Done()
      if item.content == nil {
      item.err = errors.New("Content is empty.")
      } else {
      dir := filepath.Dir(item.path)
      err := os.MkdirAll(dir, os.ModePerm)
      if err != nil {
      item.err = err
      } else {
      item.err = ioutil.WriteFile(item.path, item.content, 0644)
      }
      }
      if item.err == nil {
      result, err := db.Exec("UPDATE [dbo].[AccomodationImage] SET IsRead = 1 WHERE ImageID = @id", sql.Named("id", item.id))
      if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
      item.err = errors.New("Update status failed.")
      }
      }
      if item.err != nil {
      errItems <- item
      }
      }
      func printResult(item *item) {
      defer wg.Done()
      fmt.Println(item.toString())
      }


      I use a select statement to implement event so that when errItems or ch channels receive, save or print items. I also surrounded select statement with goroutine to prevent blocking.










      share|improve this question









      New contributor




      MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.







      $endgroup$




      I am new to Go and wrote program to download and save bulk URLs concurrently. It is working correctly, but I would like to make it more efficient and follow best practices.



      package main

      import (...)

      // Connection String
      const connectionString = "connectionString"

      // Declare Channels
      var wg sync.WaitGroup
      var ch = make(chan *item) // For success item(s)
      var errItems = make(chan *item) // For error item(s)

      type item struct {
      id int
      url string
      path string
      content []byte
      err error
      }

      func main() {
      start := time.Now()
      var basePath = os.Args[1]
      var from, to = os.Args[2], os.Args[3]
      db, err := sql.Open("sqlserver", connectionString)
      if err != nil {
      log.Fatal(err.Error())
      }
      rows, err := db.Query("SELECT [ImageID],[AccommodationlID],[Link],[FileName] FROM [dbo].[AccomodationImage] where AccommodationlID between @from and @to and FileName not like '%NoHotel.png%'", sql.Named("from", from), sql.Named("to", to))
      if err != nil {
      log.Fatal(err.Error())
      }
      defer db.Close()
      for rows.Next() {
      var item = item{}
      var accId, name string
      _ = rows.Scan(&item.id, &accId, &item.url, &name)
      item.path = fmt.Sprintf("%s\%s\%s", basePath, accId, name)
      wg.Add(1)
      go downloadFile(&item)
      go func() {
      select {
      case done := <-ch:
      wg.Add(1)
      go saveAndUpdateFile(db, done)
      case errorItem := <-errItems:
      wg.Add(1)
      go printResult(errorItem)
      }
      }()
      }
      wg.Wait()
      fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
      }
      func downloadFile(item *item) {
      defer wg.Done()
      resp, err := http.Get(item.url)
      if err != nil {
      item.content, item.err = nil, err
      } else if resp.StatusCode != http.StatusOK {
      item.content, item.err = nil, errors.New(resp.Status)
      } else {
      item.content, item.err = ioutil.ReadAll(resp.Body)
      }
      if item.err != nil {
      errItems <- item
      } else {
      ch <- item
      }
      }
      func saveAndUpdateFile(db *sql.DB, item *item) {
      defer wg.Done()
      if item.content == nil {
      item.err = errors.New("Content is empty.")
      } else {
      dir := filepath.Dir(item.path)
      err := os.MkdirAll(dir, os.ModePerm)
      if err != nil {
      item.err = err
      } else {
      item.err = ioutil.WriteFile(item.path, item.content, 0644)
      }
      }
      if item.err == nil {
      result, err := db.Exec("UPDATE [dbo].[AccomodationImage] SET IsRead = 1 WHERE ImageID = @id", sql.Named("id", item.id))
      if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
      item.err = errors.New("Update status failed.")
      }
      }
      if item.err != nil {
      errItems <- item
      }
      }
      func printResult(item *item) {
      defer wg.Done()
      fmt.Println(item.toString())
      }


      I use a select statement to implement event so that when errItems or ch channels receive, save or print items. I also surrounded select statement with goroutine to prevent blocking.







      beginner sql go concurrency http






      share|improve this question









      New contributor




      MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      share|improve this question









      New contributor




      MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this question




      share|improve this question








      edited 2 days ago









      200_success

      130k17153419




      130k17153419






      New contributor




      MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked Mar 14 at 16:47









      MatinMoeziMatinMoezi

      162




      162




      New contributor




      MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      MatinMoezi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          1 Answer
          1






          active

          oldest

          votes


















          2












          $begingroup$

          Full code



          import (...)


          Please include your full code next time. This should instead be:



          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )


          You also have item.toString(), but this is nowhere defined. I changed it to:



          fmt.Println(item)


          Scope



          connectionString doesn't need to be in the global scope, whether it's const or not. The same could be argued about wg, ch, and errItems -- but moving those to a lower scope would involve adding more function arguments, which is a choice you can make.



          In terms of security, I recommend against hard-coding the connection string in the source code. Instead, as one of many options, you can have the connection credentials as a separate file that you read.



          Simplify query



          You use named arguments in your query. But here, the query is so short that it's clear from the context. You can split the query across multiple lines to make it easier to read. You also have some keywords in all uppercase and some in all lowercase.



          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)


          (Notice that you also have a misspelling in AccomodationImage.)



          This kind of formatting for query strings is also how the Go documentation does it.



          Vertical spacing



          All of the code is compressed together and has no room to breathe. I recommend adding the occasional empty lines, such as between if-statements, for loops, goroutines, etc.



          Validate os.Args



          Your code assumes the program will always have three arguments. While this makes sense if only you plan to use it, it's generally not good practice.



          Without bounds checking, you will get a runtime panic "index out of range" -- no a particularly useful message for those running the program.



          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }


          You can also use short variable declaration when declaring basePath, from, and to.



          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]


          Default values



          var item = item{}


          This is redundant. You should be able to simply use var item -- please correct my if I'm wrong here and Rows.Scan() produces an error if item is nil. Since we do not need to initialize item, we can combine our declarations. Notice I rename the variable item to avoid confusion with the type item.



          var (
          i item
          accID string
          name string
          )


          Conclusion



          Here's the code I ended up with:



          package main

          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )

          type item struct {
          id int
          url string
          path string
          content []byte
          err error
          }

          var wg sync.WaitGroup
          var ch = make(chan *item) // For success items
          var errItems = make(chan *item) // For error items

          func main() {
          const connectionString = "connectionString"

          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }

          start := time.Now()

          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]

          db, err := sql.Open("sqlserver", connectionString)

          if err != nil {
          log.Fatal(err.Error())
          }

          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)

          if err != nil {
          log.Fatal(err.Error())
          }

          defer db.Close()

          for rows.Next() {
          var (
          i item
          accID string
          name string
          )

          _ = rows.Scan(&i.id, &accID, &i.url, &name)

          i.path = fmt.Sprintf("%s\%s\%s", basePath, accID, name)

          wg.Add(1)

          go downloadFile(&i)

          go func() {
          select {
          case done := <-ch:
          wg.Add(1)
          go saveAndUpdateFile(db, done)
          case errorItem := <-errItems:
          wg.Add(1)
          go printResult(errorItem)
          }
          }()
          }

          wg.Wait()

          fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
          }

          func downloadFile(i *item) {
          defer wg.Done()

          resp, err := http.Get(i.url)

          if err != nil {
          i.content, i.err = nil, err
          } else if resp.StatusCode != http.StatusOK {
          i.content, i.err = nil, errors.New(resp.Status)
          } else {
          i.content, i.err = ioutil.ReadAll(resp.Body)
          }

          if i.err != nil {
          errItems <- i
          } else {
          ch <- i
          }
          }

          func saveAndUpdateFile(db *sql.DB, i *item) {
          defer wg.Done()

          if i.content == nil {
          i.err = errors.New("Content is empty.")
          } else {
          dir := filepath.Dir(i.path)
          err := os.MkdirAll(dir, os.ModePerm)

          if err != nil {
          i.err = err
          } else {
          i.err = ioutil.WriteFile(i.path, i.content, 0644)
          }
          }

          q := `
          UPDATE [dbo].[AccomodationImage]
          SET IsRead = 1
          WHERE ImageID = ?`

          if i.err == nil {
          result, err := db.Exec(q, i.id)

          if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
          i.err = errors.New("Update status failed.")
          }
          }

          if i.err != nil {
          errItems <- i
          }
          }

          func printResult(i *item) {
          defer wg.Done()

          fmt.Println(i)
          }


          Unfortunately I cannot test this code and suggest algorithmic or more in-depth changes.



          Hope this helps!






          share|improve this answer











          $endgroup$













          • $begingroup$
            "Double-spacing" code so that it can "breathe" severely limits readablity. Readability is the paramount concern.
            $endgroup$
            – peterSO
            2 days ago












          • $begingroup$
            We don't need the full code for imports. Simply run the goimports command.
            $endgroup$
            – peterSO
            2 days ago










          • $begingroup$
            @peterSO And goimports is what I ran in this case. It is still invalid Go code nonetheless. In my opinion adding spacing improves readability, but this change is of little consequence.
            $endgroup$
            – esote
            2 days ago






          • 1




            $begingroup$
            Thank you very much @esote. your tips are very helpful. i didn't write imports because of summarization. generally my emphasis is on concurrent approach and implementing goroutines and select statement in for loop.
            $endgroup$
            – MatinMoezi
            yesterday











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          MatinMoezi is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215433%2fdownload-and-save-bulk-url-concurrently%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          2












          $begingroup$

          Full code



          import (...)


          Please include your full code next time. This should instead be:



          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )


          You also have item.toString(), but this is nowhere defined. I changed it to:



          fmt.Println(item)


          Scope



          connectionString doesn't need to be in the global scope, whether it's const or not. The same could be argued about wg, ch, and errItems -- but moving those to a lower scope would involve adding more function arguments, which is a choice you can make.



          In terms of security, I recommend against hard-coding the connection string in the source code. Instead, as one of many options, you can have the connection credentials as a separate file that you read.



          Simplify query



          You use named arguments in your query. But here, the query is so short that it's clear from the context. You can split the query across multiple lines to make it easier to read. You also have some keywords in all uppercase and some in all lowercase.



          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)


          (Notice that you also have a misspelling in AccomodationImage.)



          This kind of formatting for query strings is also how the Go documentation does it.



          Vertical spacing



          All of the code is compressed together and has no room to breathe. I recommend adding the occasional empty lines, such as between if-statements, for loops, goroutines, etc.



          Validate os.Args



          Your code assumes the program will always have three arguments. While this makes sense if only you plan to use it, it's generally not good practice.



          Without bounds checking, you will get a runtime panic "index out of range" -- no a particularly useful message for those running the program.



          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }


          You can also use short variable declaration when declaring basePath, from, and to.



          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]


          Default values



          var item = item{}


          This is redundant. You should be able to simply use var item -- please correct my if I'm wrong here and Rows.Scan() produces an error if item is nil. Since we do not need to initialize item, we can combine our declarations. Notice I rename the variable item to avoid confusion with the type item.



          var (
          i item
          accID string
          name string
          )


          Conclusion



          Here's the code I ended up with:



          package main

          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )

          type item struct {
          id int
          url string
          path string
          content []byte
          err error
          }

          var wg sync.WaitGroup
          var ch = make(chan *item) // For success items
          var errItems = make(chan *item) // For error items

          func main() {
          const connectionString = "connectionString"

          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }

          start := time.Now()

          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]

          db, err := sql.Open("sqlserver", connectionString)

          if err != nil {
          log.Fatal(err.Error())
          }

          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)

          if err != nil {
          log.Fatal(err.Error())
          }

          defer db.Close()

          for rows.Next() {
          var (
          i item
          accID string
          name string
          )

          _ = rows.Scan(&i.id, &accID, &i.url, &name)

          i.path = fmt.Sprintf("%s\%s\%s", basePath, accID, name)

          wg.Add(1)

          go downloadFile(&i)

          go func() {
          select {
          case done := <-ch:
          wg.Add(1)
          go saveAndUpdateFile(db, done)
          case errorItem := <-errItems:
          wg.Add(1)
          go printResult(errorItem)
          }
          }()
          }

          wg.Wait()

          fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
          }

          func downloadFile(i *item) {
          defer wg.Done()

          resp, err := http.Get(i.url)

          if err != nil {
          i.content, i.err = nil, err
          } else if resp.StatusCode != http.StatusOK {
          i.content, i.err = nil, errors.New(resp.Status)
          } else {
          i.content, i.err = ioutil.ReadAll(resp.Body)
          }

          if i.err != nil {
          errItems <- i
          } else {
          ch <- i
          }
          }

          func saveAndUpdateFile(db *sql.DB, i *item) {
          defer wg.Done()

          if i.content == nil {
          i.err = errors.New("Content is empty.")
          } else {
          dir := filepath.Dir(i.path)
          err := os.MkdirAll(dir, os.ModePerm)

          if err != nil {
          i.err = err
          } else {
          i.err = ioutil.WriteFile(i.path, i.content, 0644)
          }
          }

          q := `
          UPDATE [dbo].[AccomodationImage]
          SET IsRead = 1
          WHERE ImageID = ?`

          if i.err == nil {
          result, err := db.Exec(q, i.id)

          if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
          i.err = errors.New("Update status failed.")
          }
          }

          if i.err != nil {
          errItems <- i
          }
          }

          func printResult(i *item) {
          defer wg.Done()

          fmt.Println(i)
          }


          Unfortunately I cannot test this code and suggest algorithmic or more in-depth changes.



          Hope this helps!






          share|improve this answer











          $endgroup$













          • $begingroup$
            "Double-spacing" code so that it can "breathe" severely limits readablity. Readability is the paramount concern.
            $endgroup$
            – peterSO
            2 days ago












          • $begingroup$
            We don't need the full code for imports. Simply run the goimports command.
            $endgroup$
            – peterSO
            2 days ago










          • $begingroup$
            @peterSO And goimports is what I ran in this case. It is still invalid Go code nonetheless. In my opinion adding spacing improves readability, but this change is of little consequence.
            $endgroup$
            – esote
            2 days ago






          • 1




            $begingroup$
            Thank you very much @esote. your tips are very helpful. i didn't write imports because of summarization. generally my emphasis is on concurrent approach and implementing goroutines and select statement in for loop.
            $endgroup$
            – MatinMoezi
            yesterday
















          2












          $begingroup$

          Full code



          import (...)


          Please include your full code next time. This should instead be:



          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )


          You also have item.toString(), but this is nowhere defined. I changed it to:



          fmt.Println(item)


          Scope



          connectionString doesn't need to be in the global scope, whether it's const or not. The same could be argued about wg, ch, and errItems -- but moving those to a lower scope would involve adding more function arguments, which is a choice you can make.



          In terms of security, I recommend against hard-coding the connection string in the source code. Instead, as one of many options, you can have the connection credentials as a separate file that you read.



          Simplify query



          You use named arguments in your query. But here, the query is so short that it's clear from the context. You can split the query across multiple lines to make it easier to read. You also have some keywords in all uppercase and some in all lowercase.



          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)


          (Notice that you also have a misspelling in AccomodationImage.)



          This kind of formatting for query strings is also how the Go documentation does it.



          Vertical spacing



          All of the code is compressed together and has no room to breathe. I recommend adding the occasional empty lines, such as between if-statements, for loops, goroutines, etc.



          Validate os.Args



          Your code assumes the program will always have three arguments. While this makes sense if only you plan to use it, it's generally not good practice.



          Without bounds checking, you will get a runtime panic "index out of range" -- no a particularly useful message for those running the program.



          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }


          You can also use short variable declaration when declaring basePath, from, and to.



          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]


          Default values



          var item = item{}


          This is redundant. You should be able to simply use var item -- please correct my if I'm wrong here and Rows.Scan() produces an error if item is nil. Since we do not need to initialize item, we can combine our declarations. Notice I rename the variable item to avoid confusion with the type item.



          var (
          i item
          accID string
          name string
          )


          Conclusion



          Here's the code I ended up with:



          package main

          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )

          type item struct {
          id int
          url string
          path string
          content []byte
          err error
          }

          var wg sync.WaitGroup
          var ch = make(chan *item) // For success items
          var errItems = make(chan *item) // For error items

          func main() {
          const connectionString = "connectionString"

          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }

          start := time.Now()

          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]

          db, err := sql.Open("sqlserver", connectionString)

          if err != nil {
          log.Fatal(err.Error())
          }

          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)

          if err != nil {
          log.Fatal(err.Error())
          }

          defer db.Close()

          for rows.Next() {
          var (
          i item
          accID string
          name string
          )

          _ = rows.Scan(&i.id, &accID, &i.url, &name)

          i.path = fmt.Sprintf("%s\%s\%s", basePath, accID, name)

          wg.Add(1)

          go downloadFile(&i)

          go func() {
          select {
          case done := <-ch:
          wg.Add(1)
          go saveAndUpdateFile(db, done)
          case errorItem := <-errItems:
          wg.Add(1)
          go printResult(errorItem)
          }
          }()
          }

          wg.Wait()

          fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
          }

          func downloadFile(i *item) {
          defer wg.Done()

          resp, err := http.Get(i.url)

          if err != nil {
          i.content, i.err = nil, err
          } else if resp.StatusCode != http.StatusOK {
          i.content, i.err = nil, errors.New(resp.Status)
          } else {
          i.content, i.err = ioutil.ReadAll(resp.Body)
          }

          if i.err != nil {
          errItems <- i
          } else {
          ch <- i
          }
          }

          func saveAndUpdateFile(db *sql.DB, i *item) {
          defer wg.Done()

          if i.content == nil {
          i.err = errors.New("Content is empty.")
          } else {
          dir := filepath.Dir(i.path)
          err := os.MkdirAll(dir, os.ModePerm)

          if err != nil {
          i.err = err
          } else {
          i.err = ioutil.WriteFile(i.path, i.content, 0644)
          }
          }

          q := `
          UPDATE [dbo].[AccomodationImage]
          SET IsRead = 1
          WHERE ImageID = ?`

          if i.err == nil {
          result, err := db.Exec(q, i.id)

          if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
          i.err = errors.New("Update status failed.")
          }
          }

          if i.err != nil {
          errItems <- i
          }
          }

          func printResult(i *item) {
          defer wg.Done()

          fmt.Println(i)
          }


          Unfortunately I cannot test this code and suggest algorithmic or more in-depth changes.



          Hope this helps!






          share|improve this answer











          $endgroup$













          • $begingroup$
            "Double-spacing" code so that it can "breathe" severely limits readablity. Readability is the paramount concern.
            $endgroup$
            – peterSO
            2 days ago












          • $begingroup$
            We don't need the full code for imports. Simply run the goimports command.
            $endgroup$
            – peterSO
            2 days ago










          • $begingroup$
            @peterSO And goimports is what I ran in this case. It is still invalid Go code nonetheless. In my opinion adding spacing improves readability, but this change is of little consequence.
            $endgroup$
            – esote
            2 days ago






          • 1




            $begingroup$
            Thank you very much @esote. your tips are very helpful. i didn't write imports because of summarization. generally my emphasis is on concurrent approach and implementing goroutines and select statement in for loop.
            $endgroup$
            – MatinMoezi
            yesterday














          2












          2








          2





          $begingroup$

          Full code



          import (...)


          Please include your full code next time. This should instead be:



          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )


          You also have item.toString(), but this is nowhere defined. I changed it to:



          fmt.Println(item)


          Scope



          connectionString doesn't need to be in the global scope, whether it's const or not. The same could be argued about wg, ch, and errItems -- but moving those to a lower scope would involve adding more function arguments, which is a choice you can make.



          In terms of security, I recommend against hard-coding the connection string in the source code. Instead, as one of many options, you can have the connection credentials as a separate file that you read.



          Simplify query



          You use named arguments in your query. But here, the query is so short that it's clear from the context. You can split the query across multiple lines to make it easier to read. You also have some keywords in all uppercase and some in all lowercase.



          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)


          (Notice that you also have a misspelling in AccomodationImage.)



          This kind of formatting for query strings is also how the Go documentation does it.



          Vertical spacing



          All of the code is compressed together and has no room to breathe. I recommend adding the occasional empty lines, such as between if-statements, for loops, goroutines, etc.



          Validate os.Args



          Your code assumes the program will always have three arguments. While this makes sense if only you plan to use it, it's generally not good practice.



          Without bounds checking, you will get a runtime panic "index out of range" -- no a particularly useful message for those running the program.



          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }


          You can also use short variable declaration when declaring basePath, from, and to.



          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]


          Default values



          var item = item{}


          This is redundant. You should be able to simply use var item -- please correct my if I'm wrong here and Rows.Scan() produces an error if item is nil. Since we do not need to initialize item, we can combine our declarations. Notice I rename the variable item to avoid confusion with the type item.



          var (
          i item
          accID string
          name string
          )


          Conclusion



          Here's the code I ended up with:



          package main

          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )

          type item struct {
          id int
          url string
          path string
          content []byte
          err error
          }

          var wg sync.WaitGroup
          var ch = make(chan *item) // For success items
          var errItems = make(chan *item) // For error items

          func main() {
          const connectionString = "connectionString"

          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }

          start := time.Now()

          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]

          db, err := sql.Open("sqlserver", connectionString)

          if err != nil {
          log.Fatal(err.Error())
          }

          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)

          if err != nil {
          log.Fatal(err.Error())
          }

          defer db.Close()

          for rows.Next() {
          var (
          i item
          accID string
          name string
          )

          _ = rows.Scan(&i.id, &accID, &i.url, &name)

          i.path = fmt.Sprintf("%s\%s\%s", basePath, accID, name)

          wg.Add(1)

          go downloadFile(&i)

          go func() {
          select {
          case done := <-ch:
          wg.Add(1)
          go saveAndUpdateFile(db, done)
          case errorItem := <-errItems:
          wg.Add(1)
          go printResult(errorItem)
          }
          }()
          }

          wg.Wait()

          fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
          }

          func downloadFile(i *item) {
          defer wg.Done()

          resp, err := http.Get(i.url)

          if err != nil {
          i.content, i.err = nil, err
          } else if resp.StatusCode != http.StatusOK {
          i.content, i.err = nil, errors.New(resp.Status)
          } else {
          i.content, i.err = ioutil.ReadAll(resp.Body)
          }

          if i.err != nil {
          errItems <- i
          } else {
          ch <- i
          }
          }

          func saveAndUpdateFile(db *sql.DB, i *item) {
          defer wg.Done()

          if i.content == nil {
          i.err = errors.New("Content is empty.")
          } else {
          dir := filepath.Dir(i.path)
          err := os.MkdirAll(dir, os.ModePerm)

          if err != nil {
          i.err = err
          } else {
          i.err = ioutil.WriteFile(i.path, i.content, 0644)
          }
          }

          q := `
          UPDATE [dbo].[AccomodationImage]
          SET IsRead = 1
          WHERE ImageID = ?`

          if i.err == nil {
          result, err := db.Exec(q, i.id)

          if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
          i.err = errors.New("Update status failed.")
          }
          }

          if i.err != nil {
          errItems <- i
          }
          }

          func printResult(i *item) {
          defer wg.Done()

          fmt.Println(i)
          }


          Unfortunately I cannot test this code and suggest algorithmic or more in-depth changes.



          Hope this helps!






          share|improve this answer











          $endgroup$



          Full code



          import (...)


          Please include your full code next time. This should instead be:



          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )


          You also have item.toString(), but this is nowhere defined. I changed it to:



          fmt.Println(item)


          Scope



          connectionString doesn't need to be in the global scope, whether it's const or not. The same could be argued about wg, ch, and errItems -- but moving those to a lower scope would involve adding more function arguments, which is a choice you can make.



          In terms of security, I recommend against hard-coding the connection string in the source code. Instead, as one of many options, you can have the connection credentials as a separate file that you read.



          Simplify query



          You use named arguments in your query. But here, the query is so short that it's clear from the context. You can split the query across multiple lines to make it easier to read. You also have some keywords in all uppercase and some in all lowercase.



          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)


          (Notice that you also have a misspelling in AccomodationImage.)



          This kind of formatting for query strings is also how the Go documentation does it.



          Vertical spacing



          All of the code is compressed together and has no room to breathe. I recommend adding the occasional empty lines, such as between if-statements, for loops, goroutines, etc.



          Validate os.Args



          Your code assumes the program will always have three arguments. While this makes sense if only you plan to use it, it's generally not good practice.



          Without bounds checking, you will get a runtime panic "index out of range" -- no a particularly useful message for those running the program.



          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }


          You can also use short variable declaration when declaring basePath, from, and to.



          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]


          Default values



          var item = item{}


          This is redundant. You should be able to simply use var item -- please correct my if I'm wrong here and Rows.Scan() produces an error if item is nil. Since we do not need to initialize item, we can combine our declarations. Notice I rename the variable item to avoid confusion with the type item.



          var (
          i item
          accID string
          name string
          )


          Conclusion



          Here's the code I ended up with:



          package main

          import (
          "database/sql"
          "errors"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "os"
          "path/filepath"
          "sync"
          "time"
          )

          type item struct {
          id int
          url string
          path string
          content []byte
          err error
          }

          var wg sync.WaitGroup
          var ch = make(chan *item) // For success items
          var errItems = make(chan *item) // For error items

          func main() {
          const connectionString = "connectionString"

          if len(os.Args) < 4 {
          log.Fatal("missing arguments")
          }

          start := time.Now()

          basePath := os.Args[1]
          from, to := os.Args[2], os.Args[3]

          db, err := sql.Open("sqlserver", connectionString)

          if err != nil {
          log.Fatal(err.Error())
          }

          q := `
          SELECT [ImageID],
          [AccommodationlID],
          [Link],
          [FileName]
          FROM [dbo]. [AccomodationImage]
          WHERE AccommodationlID BETWEEN ? AND ?
          AND FileName NOT LIKE '%NoHotel.png%'`

          rows, err := db.Query(q, from, to)

          if err != nil {
          log.Fatal(err.Error())
          }

          defer db.Close()

          for rows.Next() {
          var (
          i item
          accID string
          name string
          )

          _ = rows.Scan(&i.id, &accID, &i.url, &name)

          i.path = fmt.Sprintf("%s\%s\%s", basePath, accID, name)

          wg.Add(1)

          go downloadFile(&i)

          go func() {
          select {
          case done := <-ch:
          wg.Add(1)
          go saveAndUpdateFile(db, done)
          case errorItem := <-errItems:
          wg.Add(1)
          go printResult(errorItem)
          }
          }()
          }

          wg.Wait()

          fmt.Printf("%.2fs elapsedn", time.Since(start).Seconds())
          }

          func downloadFile(i *item) {
          defer wg.Done()

          resp, err := http.Get(i.url)

          if err != nil {
          i.content, i.err = nil, err
          } else if resp.StatusCode != http.StatusOK {
          i.content, i.err = nil, errors.New(resp.Status)
          } else {
          i.content, i.err = ioutil.ReadAll(resp.Body)
          }

          if i.err != nil {
          errItems <- i
          } else {
          ch <- i
          }
          }

          func saveAndUpdateFile(db *sql.DB, i *item) {
          defer wg.Done()

          if i.content == nil {
          i.err = errors.New("Content is empty.")
          } else {
          dir := filepath.Dir(i.path)
          err := os.MkdirAll(dir, os.ModePerm)

          if err != nil {
          i.err = err
          } else {
          i.err = ioutil.WriteFile(i.path, i.content, 0644)
          }
          }

          q := `
          UPDATE [dbo].[AccomodationImage]
          SET IsRead = 1
          WHERE ImageID = ?`

          if i.err == nil {
          result, err := db.Exec(q, i.id)

          if rows, _ := result.RowsAffected(); rows <= 0 || err != nil {
          i.err = errors.New("Update status failed.")
          }
          }

          if i.err != nil {
          errItems <- i
          }
          }

          func printResult(i *item) {
          defer wg.Done()

          fmt.Println(i)
          }


          Unfortunately I cannot test this code and suggest algorithmic or more in-depth changes.



          Hope this helps!







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 2 days ago

























          answered 2 days ago









          esoteesote

          2,7761937




          2,7761937












          • $begingroup$
            "Double-spacing" code so that it can "breathe" severely limits readablity. Readability is the paramount concern.
            $endgroup$
            – peterSO
            2 days ago












          • $begingroup$
            We don't need the full code for imports. Simply run the goimports command.
            $endgroup$
            – peterSO
            2 days ago










          • $begingroup$
            @peterSO And goimports is what I ran in this case. It is still invalid Go code nonetheless. In my opinion adding spacing improves readability, but this change is of little consequence.
            $endgroup$
            – esote
            2 days ago






          • 1




            $begingroup$
            Thank you very much @esote. your tips are very helpful. i didn't write imports because of summarization. generally my emphasis is on concurrent approach and implementing goroutines and select statement in for loop.
            $endgroup$
            – MatinMoezi
            yesterday


















          • $begingroup$
            "Double-spacing" code so that it can "breathe" severely limits readablity. Readability is the paramount concern.
            $endgroup$
            – peterSO
            2 days ago












          • $begingroup$
            We don't need the full code for imports. Simply run the goimports command.
            $endgroup$
            – peterSO
            2 days ago










          • $begingroup$
            @peterSO And goimports is what I ran in this case. It is still invalid Go code nonetheless. In my opinion adding spacing improves readability, but this change is of little consequence.
            $endgroup$
            – esote
            2 days ago






          • 1




            $begingroup$
            Thank you very much @esote. your tips are very helpful. i didn't write imports because of summarization. generally my emphasis is on concurrent approach and implementing goroutines and select statement in for loop.
            $endgroup$
            – MatinMoezi
            yesterday
















          $begingroup$
          "Double-spacing" code so that it can "breathe" severely limits readablity. Readability is the paramount concern.
          $endgroup$
          – peterSO
          2 days ago






          $begingroup$
          "Double-spacing" code so that it can "breathe" severely limits readablity. Readability is the paramount concern.
          $endgroup$
          – peterSO
          2 days ago














          $begingroup$
          We don't need the full code for imports. Simply run the goimports command.
          $endgroup$
          – peterSO
          2 days ago




          $begingroup$
          We don't need the full code for imports. Simply run the goimports command.
          $endgroup$
          – peterSO
          2 days ago












          $begingroup$
          @peterSO And goimports is what I ran in this case. It is still invalid Go code nonetheless. In my opinion adding spacing improves readability, but this change is of little consequence.
          $endgroup$
          – esote
          2 days ago




          $begingroup$
          @peterSO And goimports is what I ran in this case. It is still invalid Go code nonetheless. In my opinion adding spacing improves readability, but this change is of little consequence.
          $endgroup$
          – esote
          2 days ago




          1




          1




          $begingroup$
          Thank you very much @esote. your tips are very helpful. i didn't write imports because of summarization. generally my emphasis is on concurrent approach and implementing goroutines and select statement in for loop.
          $endgroup$
          – MatinMoezi
          yesterday




          $begingroup$
          Thank you very much @esote. your tips are very helpful. i didn't write imports because of summarization. generally my emphasis is on concurrent approach and implementing goroutines and select statement in for loop.
          $endgroup$
          – MatinMoezi
          yesterday










          MatinMoezi is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          MatinMoezi is a new contributor. Be nice, and check out our Code of Conduct.













          MatinMoezi is a new contributor. Be nice, and check out our Code of Conduct.












          MatinMoezi is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215433%2fdownload-and-save-bulk-url-concurrently%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          is 'sed' thread safeWhat should someone know about using Python scripts in the shell?Nexenta bash script uses...

          How do i solve the “ No module named 'mlxtend' ” issue on Jupyter?

          Pilgersdorf Inhaltsverzeichnis Geografie | Geschichte | Bevölkerungsentwicklung | Politik | Kultur...