Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

379 rindas
10KB

  1. package main
  2. import (
  3. "biukop.com/sfm/loan"
  4. "context"
  5. "errors"
  6. log "github.com/sirupsen/logrus"
  7. "io/ioutil"
  8. "net/http"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. )
  17. type uploadsOnDisk struct {
  18. Upload loan.Uploads
  19. }
  20. func (m *uploadsOnDisk) convertUploadsToPDF() (e error) {
  21. return convertUploadsToPDF(m.Upload)
  22. }
  23. func convertUploadsToPDF(ul loan.Uploads) (e error) {
  24. m := uploadsOnDisk{}
  25. m.Upload = ul
  26. if strings.Contains(strings.ToLower(ul.Format), "excel") ||
  27. strings.Contains(strings.ToLower(ul.Format), "spreadsheet") {
  28. e = m.convertExcelToPDF()
  29. return // excel is converted
  30. }
  31. if strings.Contains(strings.ToLower(ul.Format), "/pdf") {
  32. return // no need to convert
  33. }
  34. e = errors.New("don't know how to convert file to PDF")
  35. log.Error("don't know how to convert file to PDF", ul)
  36. return
  37. }
  38. func (m *uploadsOnDisk) convertUploadsToJpg() (e error) {
  39. return convertUploadsToJpg(m.Upload)
  40. }
  41. func convertUploadsToJpg(ul loan.Uploads) (e error) {
  42. m := uploadsOnDisk{}
  43. m.Upload = ul
  44. if strings.Contains(strings.ToLower(ul.Format), "excel") ||
  45. strings.Contains(strings.ToLower(ul.Format), "spreadsheet") {
  46. e = m.convertExcelToJpg()
  47. return // excel is converted
  48. }
  49. if strings.Contains(strings.ToLower(ul.Format), "/pdf") {
  50. e = m.convertPDFToJpg()
  51. return // excel is converted
  52. }
  53. e = errors.New("don't know how to convert file to image")
  54. log.Error("don't know how to convert file to image", ul)
  55. return
  56. }
  57. func (m *uploadsOnDisk) convertUploadsToThumb() (e error) {
  58. return convertUploadsToThumb(m.Upload)
  59. }
  60. func convertUploadsToThumb(ul loan.Uploads) (e error) {
  61. m := uploadsOnDisk{}
  62. m.Upload = ul
  63. if !fileExists(m.jpgPath()) {
  64. e = m.convertUploadsToJpg()
  65. if e != nil {
  66. return
  67. }
  68. }
  69. e = ConvertImageToThumbnail(m.jpgPath(), m.thumbPath(), 256)
  70. if e != nil {
  71. log.Error("cannot create thumbnail for uploads", m.Upload, e)
  72. return
  73. }
  74. return
  75. }
  76. func (m *uploadsOnDisk) filePath() string {
  77. return config.UploadsDir.FileDir + strconv.Itoa(int(m.Upload.Id)) + ".uploads"
  78. }
  79. func (m *uploadsOnDisk) jpgPath() string {
  80. return config.UploadsDir.JpgDir + strconv.Itoa(int(m.Upload.Id)) + ".jpg"
  81. }
  82. func (m *uploadsOnDisk) thumbPath() string {
  83. return config.UploadsDir.ThumbDir + strconv.Itoa(int(m.Upload.Id)) + ".webp"
  84. }
  85. func (m *uploadsOnDisk) pdfPath() string {
  86. return config.UploadsDir.PdfDir + strconv.Itoa(int(m.Upload.Id)) + ".pdf"
  87. }
  88. func (m *uploadsOnDisk) convertExcelToPDF() (e error) {
  89. if fileExists(m.pdfPath()) {
  90. log.Info("Skip conversion excel to PDF , already exists", m)
  91. return
  92. }
  93. return m.convertExcelTo("pdf")
  94. }
  95. func (m *uploadsOnDisk) convertExcelToJpg() (e error) {
  96. if fileExists(m.jpgPath()) {
  97. log.Info("Skip conversion excel to Jpg , already exists", m)
  98. return
  99. }
  100. return m.convertExcelTo("jpg")
  101. }
  102. var libreOfficeMutex sync.Mutex // make sure we only have one libreoffice running at a time
  103. func (m *uploadsOnDisk) convertExcelTo(format string) (e error) {
  104. if format != "pdf" && format != "jpg" {
  105. e = errors.New("convert excel to unsupported format " + format)
  106. return
  107. }
  108. dst := m.jpgPath()
  109. if format == "pdf" {
  110. dst = m.pdfPath()
  111. }
  112. dir, e := ioutil.TempDir(config.TempDir, "tmp-convert-xls-to-"+format+"-")
  113. if e != nil {
  114. log.Error("cannot create tmp dir for converting image", m.Upload, e)
  115. return
  116. }
  117. defer os.RemoveAll(dir)
  118. // Create a new context and add a timeout to it
  119. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  120. defer cancel() // The cancel should be deferred so resources are cleaned up
  121. // Create the command with our context
  122. cmd := exec.CommandContext(ctx, "libreoffice", "--convert-to", format, "--outdir", dir, m.filePath())
  123. libreOfficeMutex.Lock() //ensure only one libreoffice is running
  124. // This time we can simply use Output() to get the result.
  125. out, e := cmd.Output()
  126. libreOfficeMutex.Unlock()
  127. // We want to check the context error to see if the timeout was executed.
  128. // The error returned by cmd.Output() will be OS specific based on what
  129. // happens when a process is killed.
  130. if ctx.Err() == context.DeadlineExceeded {
  131. log.Error(cmd.String(), " timed out")
  132. switch format {
  133. case "pdf":
  134. _, e = copyFile(config.UploadsDir.PdfDefault, m.pdfPath())
  135. break
  136. case "jpg":
  137. _, e = copyFile(config.UploadsDir.JpgDefault, m.jpgPath())
  138. break
  139. }
  140. return
  141. }
  142. // If there's no context error, we know the command completed (or errored).
  143. //fmt.Println("Output:", string(out))
  144. //if err != nil {
  145. // fmt.Println("Non-zero exit code:", err)
  146. //}
  147. // ---------- for cases not using ctx
  148. // for some unknown reason, libreoffice may just hung for ever
  149. //cmd := exec.Command("libreoffice", "--convert-to", format, "--outdir", dir, m.filePath())
  150. //strCmd := cmd.String()
  151. //log.Debug("command is ", strCmd)
  152. //out, e := cmd.Output()
  153. // ------------ end of without ctx
  154. if e != nil {
  155. log.Error("cannot converting Excel to "+format+":", m.Upload, e)
  156. switch format {
  157. case "pdf":
  158. _, e = copyFile(config.UploadsDir.PdfDefault, m.pdfPath())
  159. break
  160. case "jpg":
  161. _, e = copyFile(config.UploadsDir.JpgDefault, m.jpgPath())
  162. break
  163. }
  164. return
  165. } else { // success
  166. log.Info("convert to "+format, m.Upload, " output: ", string(out))
  167. }
  168. _, name := filepath.Split(m.filePath())
  169. src := dir + string(os.PathSeparator) + fileNameWithoutExtTrimSuffix(name) + "." + format
  170. e = os.Rename(src, dst) // there should be only one jpg
  171. return
  172. }
  173. // first page to thumbnail
  174. // all page to single jpg
  175. func (m *uploadsOnDisk) convertPDFToJpg() (e error) {
  176. if fileExists(m.jpgPath()) {
  177. // no need to reconvert it again
  178. log.Info("PDF to JPG skipped it already exists ", m)
  179. return
  180. }
  181. dir, e := ioutil.TempDir(config.TempDir, "tmp-convert-pdf-to-jpg-")
  182. if e != nil {
  183. log.Error("cannot create tmp dir for converting image", m.Upload, e)
  184. return
  185. }
  186. defer os.RemoveAll(dir)
  187. // convert -density 3000 abc.pdf path/tmp/result.png
  188. // could be path/tmp/result-0, result-1, result-2, ... png
  189. target := dir + string(os.PathSeparator) + "result.jpg" //.jpg suffix is important
  190. cmd := exec.Command("convert", "-density", "300", m.filePath(), target)
  191. strCmd := cmd.String()
  192. log.Debug("command is ", strCmd)
  193. out, e := cmd.Output()
  194. if e != nil {
  195. log.Error("cannot create png file for PDF", m.Upload, e)
  196. _, e = copyFile(config.UploadsDir.JpgDefault, m.jpgPath())
  197. return
  198. } else {
  199. log.Info("convert ", m.Upload, " output: ", string(out))
  200. }
  201. // montage -mode concatenate -tile 1x 30*png 30.jpg
  202. if fileExists(target) { // single file,
  203. _ = ConvertImageToThumbnail(target, m.thumbPath(), 256)
  204. e = os.Rename(target, m.jpgPath()) // there should be only one jpg
  205. } else { // multi-page, we have -0 -1 -2 -3 -4 files
  206. firstPage := dir + string(os.PathSeparator) + "result-0.jpg"
  207. _ = ConvertImageToThumbnail(firstPage, m.thumbPath(), 256)
  208. batch := dir + string(os.PathSeparator) + "result*jpg" // result* is important
  209. target = dir + string(os.PathSeparator) + "final.jpg" // .jpg suffix is important
  210. cmd = exec.Command("montage", "-mode", "concatenate", "-tile", "1x", batch, target)
  211. strCmd = cmd.String()
  212. log.Debug("command is ", strCmd)
  213. out, e = cmd.Output()
  214. if e != nil {
  215. return
  216. } else {
  217. log.Info("montage ", m, " output: ", string(out))
  218. }
  219. e = os.Rename(target, m.jpgPath()) // give combined file to target
  220. }
  221. return
  222. }
  223. func (m *uploadsOnDisk) GetFileType() (ret string, e error) {
  224. strMime, e := GetFileContentType(m.filePath())
  225. if e != nil {
  226. return
  227. }
  228. if strings.ToLower(strMime) == "application/pdf" {
  229. return "pdf", nil
  230. }
  231. if strings.ToLower(strMime) == "application/vnd.ms-excel" || strings.ToLower(m.Upload.Format) ==
  232. "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
  233. return "excel", nil
  234. }
  235. if strings.ToLower(strMime) == "application/zip" &&
  236. strings.ToLower(m.Upload.Format) ==
  237. "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
  238. return "opensheet", nil
  239. }
  240. // check suffix which is not reliable
  241. ext := filepath.Ext(m.Upload.FileName)
  242. ext = strings.ToLower(ext)
  243. if ext[0] == '.' {
  244. ext = ext[1:] // remove the first char 'dot' .
  245. }
  246. switch ext {
  247. case "xls", "xlsx":
  248. return "excel", nil
  249. case "pdf":
  250. return "pdf", nil
  251. default:
  252. log.Warn("unhandled uploads type", ext)
  253. }
  254. return "", nil
  255. }
  256. func (m *uploadsOnDisk) DeleteAll() (e error) {
  257. eJpg := os.Remove(m.jpgPath())
  258. eFile := os.Remove(m.filePath())
  259. ePdf := os.Remove(m.pdfPath())
  260. eThumb := os.Remove(m.thumbPath())
  261. eMeta := m.Upload.Delete()
  262. strId := strconv.Itoa(int(m.Upload.Id))
  263. errMsg := ""
  264. if eJpg != nil {
  265. errMsg += " jpg "
  266. }
  267. if eFile != nil {
  268. errMsg += " original "
  269. }
  270. if ePdf != nil {
  271. errMsg += " pdf "
  272. }
  273. if eThumb != nil {
  274. errMsg += " thumb "
  275. }
  276. if eMeta != nil {
  277. errMsg += " Meta in DB "
  278. e = errors.New(errMsg + " cannot be deleted " + strId)
  279. }
  280. if errMsg != "" {
  281. log.Error(errMsg, "files on disk cannot be deleted need disk cleaning ", m.Upload)
  282. }
  283. return
  284. }
  285. // GetFileContentType
  286. // tested, not accurate with xls, xlsx, it becomes zip and octstream sometime.
  287. func GetFileContentType(filename string) (contentType string, e error) {
  288. contentType = ""
  289. input, e := os.OpenFile(filename, os.O_RDONLY, 0755)
  290. // Only the first 512 bytes are used to sniff the content type.
  291. buffer := make([]byte, 512)
  292. _, e = input.Read(buffer)
  293. if e != nil {
  294. return
  295. }
  296. // Use the net/http package's handy Detect ContentType function. Always returns a valid
  297. // content-type by returning "application/octet-stream" if no others seemed to match.
  298. contentType = http.DetectContentType(buffer)
  299. return
  300. }
  301. func ConvertImageToThumbnail(srcPath string, dstPath string, size int) (e error) {
  302. if fileExists(dstPath) {
  303. log.Info("skip converting thumbnail it exists", dstPath)
  304. return
  305. }
  306. if size <= 0 { //thumb nail width
  307. size = 256
  308. }
  309. // convert -thumbnail 200 abc.png thumb.abc.png
  310. cmd := exec.Command("convert", "-thumbnail", strconv.Itoa(size), srcPath, dstPath)
  311. strCmd := cmd.String()
  312. log.Debug("create thumbnail: ", strCmd)
  313. out, e := cmd.Output()
  314. if e != nil {
  315. log.Error("Failed to convert thumbnail", e)
  316. _, e = copyFile(config.UploadsDir.ThumbDefault, dstPath)
  317. return
  318. } else { // success
  319. log.Info("success output: \n: ", string(out))
  320. }
  321. return
  322. }