diff --git a/.idea/SFM_Loan_RestApi.iml b/.idea/SFM_Loan_RestApi.iml index 5e764c4..0dc9279 100644 --- a/.idea/SFM_Loan_RestApi.iml +++ b/.idea/SFM_Loan_RestApi.iml @@ -2,6 +2,7 @@ + diff --git a/UploadsOnDisk.go b/UploadsOnDisk.go index 46d9d89..02f4363 100644 --- a/UploadsOnDisk.go +++ b/UploadsOnDisk.go @@ -2,6 +2,7 @@ package main import ( "biukop.com/sfm/loan" + "context" "errors" log "github.com/sirupsen/logrus" "io/ioutil" @@ -11,6 +12,8 @@ import ( "path/filepath" "strconv" "strings" + "sync" + "time" ) type uploadsOnDisk struct { @@ -114,9 +117,10 @@ func (m *uploadsOnDisk) convertExcelToJpg() (e error) { return m.convertExcelTo("jpg") } +var libreOfficeMutex sync.Mutex // make sure we only have one libreoffice running at a time func (m *uploadsOnDisk) convertExcelTo(format string) (e error) { if format != "pdf" && format != "jpg" { - e = errors.New("unsupported format") + e = errors.New("convert excel to unsupported format " + format) return } @@ -132,12 +136,59 @@ func (m *uploadsOnDisk) convertExcelTo(format string) (e error) { } defer os.RemoveAll(dir) - cmd := exec.Command("libreoffice", "--convert-to", format, "--outdir", dir, m.filePath()) - strCmd := cmd.String() - log.Debug("command is ", strCmd) + // Create a new context and add a timeout to it + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() // The cancel should be deferred so resources are cleaned up + + // Create the command with our context + cmd := exec.CommandContext(ctx, "libreoffice", "--convert-to", format, "--outdir", dir, m.filePath()) + + libreOfficeMutex.Lock() //ensure only one libreoffice is running + // This time we can simply use Output() to get the result. out, e := cmd.Output() + libreOfficeMutex.Unlock() + + // We want to check the context error to see if the timeout was executed. + // The error returned by cmd.Output() will be OS specific based on what + // happens when a process is killed. + if ctx.Err() == context.DeadlineExceeded { + log.Error(cmd.String(), " timed out") + + switch format { + case "pdf": + _, e = copyFile(config.UploadsDir.PdfDefault, m.pdfPath()) + break + case "jpg": + _, e = copyFile(config.UploadsDir.JpgDefault, m.jpgPath()) + break + } + return + } + + // If there's no context error, we know the command completed (or errored). + //fmt.Println("Output:", string(out)) + //if err != nil { + // fmt.Println("Non-zero exit code:", err) + //} + + // ---------- for cases not using ctx + // for some unknown reason, libreoffice may just hung for ever + //cmd := exec.Command("libreoffice", "--convert-to", format, "--outdir", dir, m.filePath()) + //strCmd := cmd.String() + //log.Debug("command is ", strCmd) + //out, e := cmd.Output() + // ------------ end of without ctx + if e != nil { log.Error("cannot converting Excel to "+format+":", m.Upload, e) + switch format { + case "pdf": + _, e = copyFile(config.UploadsDir.PdfDefault, m.pdfPath()) + break + case "jpg": + _, e = copyFile(config.UploadsDir.JpgDefault, m.jpgPath()) + break + } return } else { // success log.Info("convert to "+format, m.Upload, " output: ", string(out)) @@ -177,6 +228,7 @@ func (m *uploadsOnDisk) convertPDFToJpg() (e error) { out, e := cmd.Output() if e != nil { log.Error("cannot create png file for PDF", m.Upload, e) + _, e = copyFile(config.UploadsDir.JpgDefault, m.jpgPath()) return } else { log.Info("convert ", m.Upload, " output: ", string(out)) @@ -184,8 +236,8 @@ func (m *uploadsOnDisk) convertPDFToJpg() (e error) { // montage -mode concatenate -tile 1x 30*png 30.jpg if fileExists(target) { // single file, - e = os.Rename(target, m.jpgPath()) // there should be only one jpg _ = ConvertImageToThumbnail(target, m.thumbPath(), 256) + e = os.Rename(target, m.jpgPath()) // there should be only one jpg } else { // multi-page, we have -0 -1 -2 -3 -4 files firstPage := dir + string(os.PathSeparator) + "result-0.jpg" _ = ConvertImageToThumbnail(firstPage, m.thumbPath(), 256) @@ -245,6 +297,44 @@ func (m *uploadsOnDisk) GetFileType() (ret string, e error) { return "", nil } +func (m *uploadsOnDisk) DeleteAll() (e error) { + eJpg := os.Remove(m.jpgPath()) + eFile := os.Remove(m.filePath()) + ePdf := os.Remove(m.pdfPath()) + eThumb := os.Remove(m.thumbPath()) + eMeta := m.Upload.Delete() + + strId := strconv.Itoa(int(m.Upload.Id)) + + errMsg := "" + if eJpg != nil { + errMsg += " jpg " + } + + if eFile != nil { + errMsg += " original " + } + + if ePdf != nil { + errMsg += " pdf " + } + + if eThumb != nil { + errMsg += " thumb " + } + + if eMeta != nil { + errMsg += " Meta in DB " + e = errors.New(errMsg + " cannot be deleted " + strId) + } + + if errMsg != "" { + log.Error(errMsg, "files on disk cannot be deleted need disk cleaning ", m.Upload) + } + return +} + +// GetFileContentType // tested, not accurate with xls, xlsx, it becomes zip and octstream sometime. func GetFileContentType(filename string) (contentType string, e error) { contentType = "" @@ -257,7 +347,7 @@ func GetFileContentType(filename string) (contentType string, e error) { return } - // Use the net/http package's handy DectectContentType function. Always returns a valid + // Use the net/http package's handy Detect ContentType function. Always returns a valid // content-type by returning "application/octet-stream" if no others seemed to match. contentType = http.DetectContentType(buffer) return @@ -268,7 +358,7 @@ func ConvertImageToThumbnail(srcPath string, dstPath string, size int) (e error) log.Info("skip converting thumbnail it exists", dstPath) return } - if size <= 0 { + if size <= 0 { //thumb nail width size = 256 } // convert -thumbnail 200 abc.png thumb.abc.png @@ -278,13 +368,11 @@ func ConvertImageToThumbnail(srcPath string, dstPath string, size int) (e error) out, e := cmd.Output() if e != nil { + log.Error("Failed to convert thumbnail", e) + _, e = copyFile(config.UploadsDir.ThumbDefault, dstPath) return } else { // success log.Info("success output: \n: ", string(out)) } return } - -func fileNameWithoutExtTrimSuffix(fileName string) string { - return strings.TrimSuffix(fileName, filepath.Ext(fileName)) -} diff --git a/apiV1UploadAnalysis.go b/apiV1UploadAnalysis.go index 1e8e9d7..37db776 100644 --- a/apiV1UploadAnalysis.go +++ b/apiV1UploadAnalysis.go @@ -5,6 +5,7 @@ import ( log "github.com/sirupsen/logrus" "net/http" "strconv" + "time" ) func apiV1UploadAnalysis(w http.ResponseWriter, r *http.Request, ss *loan.Session) { @@ -33,3 +34,32 @@ func apiV1UploadAnalysis(w http.ResponseWriter, r *http.Request, ss *loan.Sessio } apiV1SendJson(ai, w, r, ss) } + +func apiV1UploadCreateAnalysis(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + time.Sleep(1 * time.Second) + + strId := r.URL.Path[len(apiV1Prefix+"upload-analysis/"):] //remove prefix + Id, e := strconv.Atoi(strId) + if e != nil { + log.Error("Invalid uploads Id cannot convert to integer", Id, e) + apiV1Client403Error(w, r, ss) + return + } + + ul := loan.Uploads{} + e = ul.Read(int64(Id)) + if e != nil { + log.Error("Upload not found or read error from db", Id, e) + apiV1Client404Error(w, r, ss) + return + } + + ai := AiDecodeIncome{} + e = ai.decodeUploadToPayIn(ul) + if e != nil { + log.Error("Cannot decode upload", Id, e) + apiV1Server500Error(w, r) + return + } + apiV1SendJson(ai, w, r, ss) +} diff --git a/apiV1UploadAsImage.go b/apiV1UploadAsImage.go new file mode 100644 index 0000000..e1df732 --- /dev/null +++ b/apiV1UploadAsImage.go @@ -0,0 +1,95 @@ +package main + +import ( + "biukop.com/sfm/loan" + log "github.com/sirupsen/logrus" + "net/http" + "os" + "strings" +) + +func apiV1UploadAsImage(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + // time.Sleep(5* time.Second) + + strId := r.URL.Path[len(apiV1Prefix+"upload-as-image/"):] //remove prefix + if strId == "default" { + http.ServeFile(w, r, config.UploadsDir.JpgDefault) + return + } + + ul, e := getRequestedUpload(strId, w, r, ss) + if e != nil { + return + } + + // if this is image itself, serve it directly + if strings.Contains(strings.ToLower(ul.Upload.Format), "image") { + f, e := os.Open(ul.filePath()) + if e == nil { + defer f.Close() + fi, e := f.Stat() + if e == nil { + //w.Header().Set("Content-Disposition", "attachment; filename="+ul.Upload.FileName) + http.ServeContent(w, r, ul.filePath(), fi.ModTime(), f) + return + } + } + // if we reach here, some err has happened + log.Error("failed to serve image file ", ul, e) + apiV1Server500Error(w, r) + return + } + + // see if a converted image exist, if not convert it and then send + if !fileExists(ul.jpgPath()) { + e = ul.convertUploadsToJpg() + if e != nil { + // serve a default no preview is available + http.ServeFile(w, r, config.UploadsDir.JpgDefault) + log.Error("error creating preview image", ul, e) + return + } + } + if fileExists(ul.jpgPath()) { + http.ServeFile(w, r, ul.jpgPath()) + return + } else { + apiV1Client404Error(w, r, ss) + } + +} +func apiV1UploadCreateImage(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + //time.Sleep(1 * time.Second) + + strId := r.URL.Path[len(apiV1Prefix+"upload-as-image/"):] //remove prefix + if strId == "" { + apiV1Client404Error(w, r, ss) + return + } + + ul, e := getRequestedUpload(strId, w, r, ss) + if e != nil { + return + } + + // if this is image itself, serve it directly + if strings.Contains(strings.ToLower(ul.Upload.Format), "image") { + apiV1SendJson(true, w, r, ss) + return + } + + // see if a converted image exist, if not convert it and then send + if !fileExists(ul.jpgPath()) { + e = ul.convertUploadsToJpg() + if e != nil { + log.Error("error creating preview image", ul, e) + } + } + + if fileExists(ul.jpgPath()) { + apiV1SendJson(true, w, r, ss) + return + } else { + apiV1SendJson(false, w, r, ss) + } +} diff --git a/apiV1UploadList.go b/apiV1UploadList.go new file mode 100644 index 0000000..efbe6ef --- /dev/null +++ b/apiV1UploadList.go @@ -0,0 +1,31 @@ +package main + +import ( + "biukop.com/sfm/loan" + "encoding/json" + log "github.com/sirupsen/logrus" + "net/http" +) + +func decodeUploadsMetaListFilter(r *http.Request) (ret loan.UploadListFilter, e error) { + decoder := json.NewDecoder(r.Body) + e = decoder.Decode(&ret) + if e != nil { + log.Error("failed decoding Upload Filter", e.Error()) + return + } + return +} + +func apiV1UploadMetaList(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + filter, e := decodeUploadsMetaListFilter(r) + if e != nil { + log.Println("invalid filter", e) + apiV1Client403Error(w, r, ss) // bad request + return + } + + data := loan.GetUploadMetaList(filter) + + apiV1SendJson(data, w, r, ss) +} diff --git a/apiV1UploadThumb.go b/apiV1UploadThumb.go new file mode 100644 index 0000000..31ee707 --- /dev/null +++ b/apiV1UploadThumb.go @@ -0,0 +1,63 @@ +package main + +import ( + "biukop.com/sfm/loan" + log "github.com/sirupsen/logrus" + "net/http" +) + +func apiV1UploadAsThumbnail(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + strId := r.URL.Path[len(apiV1Prefix+"upload-as-thumbnail/"):] //remove prefix + if strId == "default" { + http.ServeFile(w, r, config.UploadsDir.ThumbDefault) + return + } + ul, e := getRequestedUpload(strId, w, r, ss) + if e != nil { + return + } + + // see if a thumbnail is available already + if !fileExists(ul.thumbPath()) { + e = ul.convertUploadsToThumb() + if e != nil { + // serve a default no preview is available + http.ServeFile(w, r, config.UploadsDir.ThumbDefault) + log.Error("error creating preview image", ul, e) + return + } + } + if fileExists(ul.thumbPath()) { + http.ServeFile(w, r, ul.thumbPath()) + return + } else { + apiV1Client404Error(w, r, ss) + } +} + +func apiV1UploadCreateThumbnail(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + //time.Sleep(1 * time.Second) + strId := r.URL.Path[len(apiV1Prefix+"upload-as-thumbnail/"):] //remove prefix + if strId == "" { + apiV1Client404Error(w, r, ss) + return + } + ul, e := getRequestedUpload(strId, w, r, ss) + if e != nil { + return + } + + // see if a thumbnail is available already + if !fileExists(ul.thumbPath()) { + e = ul.convertUploadsToThumb() + if e != nil { + log.Error("error creating thumbNail image", ul, e) + } + } + if fileExists(ul.thumbPath()) { + apiV1SendJson(true, w, r, ss) + return + } else { + apiV1SendJson(false, w, r, ss) + } +} diff --git a/apiV1Uploads.go b/apiV1Uploads.go index eefeb98..209f33d 100644 --- a/apiV1Uploads.go +++ b/apiV1Uploads.go @@ -11,7 +11,6 @@ import ( "net/http" "os" "strconv" - "strings" "time" ) @@ -34,6 +33,34 @@ func apiV1UploadMetaGet(w http.ResponseWriter, r *http.Request, ss *loan.Session apiV1SendJson(ulmeta, w, r, ss) } +func apiV1UploadDelete(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + id := r.URL.Path[len(apiV1Prefix+"upload/"):] //remove prefix + intId, e := strconv.Atoi(id) + if e != nil { + log.Println("invalid id for upload get", id, e) + apiV1Client403Error(w, r, ss) // bad request + return + } + + ul := uploadsOnDisk{} + ul.Upload = loan.Uploads{} + e = ul.Upload.Read(int64(intId)) + if e != nil { + log.Println("upload not found", id, e) + apiV1Client404Error(w, r, ss) // bad request + return + } + + e = ul.DeleteAll() + if e != nil { + log.Println("upload cannot be deleted", id, e) + apiV1Server500Error(w, r) // bad operation + return + } + + apiV1SendJson(ul.Upload, w, r, ss) +} + func apiV1UploadOriginalFileGet(w http.ResponseWriter, r *http.Request, ss *loan.Session) { id := r.URL.Path[len(apiV1Prefix+"upload-original/"):] //remove prefix intId, e := strconv.Atoi(id) @@ -235,139 +262,6 @@ func getRequestedUpload(strId string, w http.ResponseWriter, r *http.Request, ss return } -func apiV1UploadAsImage(w http.ResponseWriter, r *http.Request, ss *loan.Session) { - strId := r.URL.Path[len(apiV1Prefix+"upload-as-image/"):] //remove prefix - if strId == "default" { - http.ServeFile(w, r, config.UploadsDir.JpgDefault) - return - } - - ul, e := getRequestedUpload(strId, w, r, ss) - if e != nil { - return - } - //time.Sleep(5* time.Second); - // if this is image itself, serve it directly - if strings.Contains(strings.ToLower(ul.Upload.Format), "image") { - f, e := os.Open(ul.filePath()) - if e == nil { - defer f.Close() - fi, e := f.Stat() - if e == nil { - //w.Header().Set("Content-Disposition", "attachment; filename="+ul.Upload.FileName) - http.ServeContent(w, r, ul.filePath(), fi.ModTime(), f) - return - } - } - // if we reach here, some err has happened - log.Error("failed to serve image file ", ul, e) - apiV1Server500Error(w, r) - return - } - - // see if a converted image exist, if not convert it and then send - if !fileExists(ul.jpgPath()) { - e = ul.convertUploadsToJpg() - if e != nil { - // serve a default no preview is available - http.ServeFile(w, r, config.UploadsDir.JpgDefault) - log.Error("error creating preview image", ul, e) - return - } - } - if fileExists(ul.jpgPath()) { - http.ServeFile(w, r, ul.jpgPath()) - return - } else { - apiV1Client404Error(w, r, ss) - } - -} -func apiV1UploadAsThumbnail(w http.ResponseWriter, r *http.Request, ss *loan.Session) { - strId := r.URL.Path[len(apiV1Prefix+"upload-as-thumbnail/"):] //remove prefix - if strId == "default" { - http.ServeFile(w, r, config.UploadsDir.ThumbDefault) - return - } - ul, e := getRequestedUpload(strId, w, r, ss) - if e != nil { - return - } - - // see if a thumbnail is available already - if !fileExists(ul.thumbPath()) { - e = ul.convertUploadsToThumb() - if e != nil { - // serve a default no preview is available - http.ServeFile(w, r, config.UploadsDir.ThumbDefault) - log.Error("error creating preview image", ul, e) - return - } - } - if fileExists(ul.thumbPath()) { - http.ServeFile(w, r, ul.thumbPath()) - return - } else { - apiV1Client404Error(w, r, ss) - } -} - -func apiV1UploadAsPDF(w http.ResponseWriter, r *http.Request, ss *loan.Session) { - strId := r.URL.Path[len(apiV1Prefix+"upload-as-pdf/"):] //remove prefix - if strId == "default" { - http.ServeFile(w, r, config.UploadsDir.PdfDefault) - return - } - ul, e := getRequestedUpload(strId, w, r, ss) - if e != nil { - return - } - - //get file type - fileType, e := ul.GetFileType() - if e != nil { - apiV1Client403Error(w, r, ss) - return - } - - // if its ready pdf, no need to convert - if fileType == "pdf" { - f, e := os.Open(ul.filePath()) - if e == nil { - defer f.Close() - fi, e := f.Stat() - if e == nil { - if forceHttpDownload(r) { - w.Header().Set("Content-Disposition", "attachment; filename="+ul.Upload.FileName) - } - http.ServeContent(w, r, ul.Upload.FileName, fi.ModTime(), f) - return - } - } - // if we reach here, some err has happened - log.Error("failed to serve pdf file ", ul, e) - apiV1Server500Error(w, r) - return - } - - // see if a converted pdf exist, if not convert it and then send - if !fileExists(ul.pdfPath()) { - e = ul.convertUploadsToPDF() - if e != nil { - // serve a default no preview is available - http.ServeFile(w, r, config.UploadsDir.PdfDefault) - log.Error("error creating preview image", ul, e) - return - } - } - if fileExists(ul.pdfPath()) { - http.ServeFile(w, r, ul.pdfPath()) - return - } else { - apiV1Client404Error(w, r, ss) - } -} - func forceHttpDownload(r *http.Request) bool { keys, ok := r.URL.Query()["download"] diff --git a/apiV1UploadsAsPdf.go b/apiV1UploadsAsPdf.go new file mode 100644 index 0000000..1fee129 --- /dev/null +++ b/apiV1UploadsAsPdf.go @@ -0,0 +1,109 @@ +package main + +import ( + "biukop.com/sfm/loan" + log "github.com/sirupsen/logrus" + "net/http" + "os" +) + +func apiV1UploadAsPDF(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + strId := r.URL.Path[len(apiV1Prefix+"upload-as-pdf/"):] //remove prefix + if strId == "default" { + http.ServeFile(w, r, config.UploadsDir.PdfDefault) + return + } + ul, e := getRequestedUpload(strId, w, r, ss) + if e != nil { + return + } + + //get file type + fileType, e := ul.GetFileType() + if e != nil { + apiV1Client403Error(w, r, ss) + return + } + + // if its ready pdf, no need to convert + if fileType == "pdf" { + f, e := os.Open(ul.filePath()) + if e == nil { + defer f.Close() + fi, e := f.Stat() + if e == nil { + if forceHttpDownload(r) { + w.Header().Set("Content-Disposition", "attachment; filename="+ul.Upload.FileName) + } + http.ServeContent(w, r, ul.Upload.FileName, fi.ModTime(), f) + return + } + } + // if we reach here, some err has happened + log.Error("failed to serve pdf file ", ul, e) + apiV1Server500Error(w, r) + return + } + + // see if a converted pdf exist, if not convert it and then send + if !fileExists(ul.pdfPath()) { + e = ul.convertUploadsToPDF() + if e != nil { + // serve a default no preview is available + http.ServeFile(w, r, config.UploadsDir.PdfDefault) + log.Error("error creating preview image", ul, e) + return + } + } + if fileExists(ul.pdfPath()) { + http.ServeFile(w, r, ul.pdfPath()) + return + } else { + apiV1Client404Error(w, r, ss) + } +} + +func apiV1UploadCreatePDF(w http.ResponseWriter, r *http.Request, ss *loan.Session) { + //time.Sleep(1* time.Second) + + strId := r.URL.Path[len(apiV1Prefix+"upload-as-pdf/"):] //remove prefix + if strId == "" { + apiV1Client404Error(w, r, ss) + return + } + ul, e := getRequestedUpload(strId, w, r, ss) + if e != nil { + return + } + + //get file type + fileType, e := ul.GetFileType() + if e != nil { + apiV1Client403Error(w, r, ss) + return + } + + // if its ready pdf, no need to convert + if fileType == "pdf" { + apiV1SendJson(true, w, r, ss) + return + } + + // see if a converted pdf exist, if not convert it and then send + if !fileExists(ul.pdfPath()) { + e = ul.convertUploadsToPDF() + if e != nil { + log.Error("error creating pdf", ul, e) + _, e = copyFile(config.UploadsDir.PdfDefault, ul.pdfPath()) + if e != nil { + log.Error("failed copy default pdf", e) + } + } + } + if fileExists(ul.pdfPath()) { + apiV1SendJson(true, w, r, ss) + return + } else { + apiV1SendJson(false, w, r, ss) + } +} diff --git a/apiv1.go b/apiv1.go index d8c1b6d..bc1d8f3 100644 --- a/apiv1.go +++ b/apiv1.go @@ -78,11 +78,17 @@ func setupApiV1Handler() []apiV1HandlerMap { {"GET", "lender-upload/", apiV1UploadOriginalFileGet}, {"GET", "upload-analysis/", apiV1UploadAnalysis}, + {"PUT", "upload-analysis/", apiV1UploadCreateAnalysis}, {"GET", "upload-as-image/", apiV1UploadAsImage}, + {"PUT", "upload-as-image/", apiV1UploadCreateImage}, {"GET", "upload-as-thumbnail/", apiV1UploadAsThumbnail}, + {"PUT", "upload-as-thumbnail/", apiV1UploadCreateThumbnail}, {"GET", "upload-as-pdf/", apiV1UploadAsPDF}, + {"PUT", "upload-as-pdf/", apiV1UploadCreatePDF}, {"GET", "upload-original/", apiV1UploadOriginalFileGet}, {"GET", "upload/", apiV1UploadMetaGet}, + {"DELETE", "upload/", apiV1UploadDelete}, + {"POST", "upload-meta-list/", apiV1UploadMetaList}, {"GET", "login", apiV1DumpRequest}, } @@ -139,11 +145,17 @@ func setupApiV1Handler() []apiV1HandlerMap { {"GET", "lender-upload/", apiV1UploadOriginalFileGet}, {"GET", "upload-analysis/", apiV1UploadAnalysis}, + {"PUT", "upload-analysis/", apiV1UploadCreateAnalysis}, {"GET", "upload-as-image/", apiV1UploadAsImage}, + {"PUT", "upload-as-image/", apiV1UploadCreateImage}, {"GET", "upload-as-thumbnail/", apiV1UploadAsThumbnail}, + {"PUT", "upload-as-thumbnail/", apiV1UploadCreateThumbnail}, {"GET", "upload-as-pdf/", apiV1UploadAsPDF}, + {"PUT", "upload-as-pdf/", apiV1UploadCreatePDF}, {"GET", "upload-original/", apiV1UploadOriginalFileGet}, {"GET", "upload-meta/", apiV1UploadMetaGet}, + {"DELETE", "upload/", apiV1UploadDelete}, + {"POST", "upload-meta-list/", apiV1UploadMetaList}, {"GET", "login", apiV1EmptyResponse}, } diff --git a/assets/thumb_file_icon.webp b/assets/thumb_file_icon.webp index 550d62f..24c151a 100644 Binary files a/assets/thumb_file_icon.webp and b/assets/thumb_file_icon.webp differ diff --git a/fileUtil.go b/fileUtil.go new file mode 100644 index 0000000..0f810e9 --- /dev/null +++ b/fileUtil.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +func fileNameWithoutExtTrimSuffix(fileName string) string { + return strings.TrimSuffix(fileName, filepath.Ext(fileName)) +} + +func copyFile(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + nBytes, err := io.Copy(destination, source) + return nBytes, err +}