download.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package downloader
  2. import (
  3. "fmt"
  4. "os"
  5. "path"
  6. "path/filepath"
  7. "sort"
  8. "sync"
  9. "time"
  10. "github.com/IceWreck/BookStack2Site/bookstackclient"
  11. "github.com/IceWreck/BookStack2Site/config"
  12. )
  13. func Download(app *config.Application) {
  14. w, _ := bookstackclient.FetchWiki(app)
  15. sem := make(chan struct{}, app.Config.Concurrency)
  16. var wg sync.WaitGroup
  17. for _, book := range w.Books {
  18. for _, chapter := range book.Chapters {
  19. for _, page := range chapter.Pages {
  20. wg.Add(1)
  21. sem <- struct{}{}
  22. page.FilePath = fmt.Sprint("/", book.Slug, "/", chapter.Slug, "/", page.Slug)
  23. go func(p bookstackclient.WikiPage) {
  24. defer wg.Done()
  25. downloadPage(app, p)
  26. // release semaphore
  27. <-sem
  28. }(page)
  29. }
  30. }
  31. for _, indiePage := range book.IndiePages {
  32. wg.Add(1)
  33. sem <- struct{}{}
  34. indiePage.FilePath = fmt.Sprint("/", book.Slug, "/", indiePage.Slug)
  35. go func(p bookstackclient.WikiPage) {
  36. defer wg.Done()
  37. downloadPage(app, p)
  38. // release semaphore
  39. <-sem
  40. }(indiePage)
  41. }
  42. }
  43. wg.Wait()
  44. // Create the contents of the SUMMARY.md file
  45. summaryContents := "# Summary\n"
  46. summaryContents += "[Introduction](README.md)\n\n"
  47. for _, book := range w.Books {
  48. summaryContents += "\n# " + book.Name + "\n\n"
  49. priorityQueue := make([]interface{}, 0)
  50. for _, chapter := range book.Chapters {
  51. priorityQueue = append(priorityQueue, chapter)
  52. }
  53. for _, indiePage := range book.IndiePages {
  54. priorityQueue = append(priorityQueue, indiePage)
  55. }
  56. sort.Slice(priorityQueue, func(i, j int) bool {
  57. var pi, pj int
  58. switch priorityQueue[i].(type) {
  59. case bookstackclient.WikiChapter:
  60. pi = priorityQueue[i].(bookstackclient.WikiChapter).Priority
  61. case bookstackclient.WikiPage:
  62. pi = priorityQueue[i].(bookstackclient.WikiPage).Priority
  63. }
  64. switch priorityQueue[j].(type) {
  65. case bookstackclient.WikiChapter:
  66. pj = priorityQueue[j].(bookstackclient.WikiChapter).Priority
  67. case bookstackclient.WikiPage:
  68. pj = priorityQueue[j].(bookstackclient.WikiPage).Priority
  69. }
  70. return pi < pj
  71. })
  72. for _, item := range priorityQueue {
  73. switch item := item.(type) {
  74. case bookstackclient.WikiChapter:
  75. summaryContents += fmt.Sprint("- [", item.Name, "]()\n")
  76. for _, page := range item.Pages {
  77. summaryContents += fmt.Sprint(" - [", page.Name, "](", book.Slug, "/", item.Slug, "/", page.Slug, ".md)\n")
  78. }
  79. case bookstackclient.WikiPage:
  80. summaryContents += fmt.Sprint("- [", item.Name, "](", book.Slug, "/", item.Slug, ".md)\n")
  81. }
  82. }
  83. }
  84. summaryContents += fmt.Sprintf("\n---\n\nGenerated on %s.\n\n", time.Now().Format(time.RFC850))
  85. createFileWithContents(app, summaryContents, fmt.Sprint(app.Config.DownloadLocation, "/SUMMARY.md"))
  86. createFileWithContents(app, summaryContents, fmt.Sprint(app.Config.DownloadLocation, "/README.md"))
  87. }
  88. func downloadPage(app *config.Application, page bookstackclient.WikiPage) {
  89. app.Logger.Info().Str("page", page.Name).Msg("Downloading Page")
  90. markdownBytes, err := bookstackclient.FetchPageMarkdown(app, page.PageID)
  91. if err != nil {
  92. app.Logger.Error().Err(err).Str("page", page.Name).Msg("Error downloading page")
  93. return
  94. }
  95. fileLocation := path.Clean(fmt.Sprint(app.Config.DownloadLocation, "/", page.FilePath, ".md"))
  96. file, err := createFile(fileLocation)
  97. if err != nil {
  98. app.Logger.Error().Str("fileLocation", fileLocation).Err(err).Str("page", page.Name).Msg("Error creating file")
  99. }
  100. defer func() {
  101. if err = file.Close(); err != nil {
  102. app.Logger.Error().Err(err).Str("page", page.Name).Msg("Error closing file")
  103. }
  104. }()
  105. _, err = file.Write(markdownBytes)
  106. if err != nil {
  107. app.Logger.Error().Err(err).Str("page", page.Name).Msg("Error writing to file")
  108. }
  109. }
  110. func createFileWithContents(app *config.Application, contents string, filepath string) {
  111. file, err := createFile(path.Clean(filepath))
  112. if err != nil {
  113. app.Logger.Error().Err(err).Str("page", filepath).Msg("Error creating file")
  114. }
  115. _, err = file.Write([]byte(contents))
  116. if err != nil {
  117. app.Logger.Error().Err(err).Str("page", filepath).Msg("Error writing to file")
  118. } else {
  119. app.Logger.Info().Str("page", filepath).Msg("Written to file")
  120. }
  121. }
  122. // createFile creates nested directories if needed and then calls os.Create
  123. func createFile(p string) (*os.File, error) {
  124. if err := os.MkdirAll(filepath.Dir(p), 0770); err != nil {
  125. return nil, err
  126. }
  127. return os.Create(p)
  128. }