include-files.go (7396B)
1 package main 2 3 import ( 4 "fmt" 5 "log" 6 "net/http" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 ) 12 13 func fileRouter(w http.ResponseWriter, r *http.Request) { 14 parts := strings.Split(r.URL.Path, "/") 15 16 if len(parts) >= 4 && parts[3] == "delete" { 17 fileDeleteHandler(w, r, parts) 18 return 19 } 20 21 if len(parts) >= 4 && parts[3] == "rename" { 22 fileRenameHandler(w, r, parts) 23 return 24 } 25 26 if len(parts) >= 5 && parts[3] == "tag" && parts[4] == "delete" { 27 tagActionHandler(w, r, parts) 28 return 29 } 30 31 fileHandler(w, r) 32 } 33 34 func fileDeleteHandler(w http.ResponseWriter, r *http.Request, parts []string) { 35 if r.Method != http.MethodPost { 36 http.Redirect(w, r, "/file/"+parts[2], http.StatusSeeOther) 37 return 38 } 39 40 fileID := parts[2] 41 42 var currentFile File 43 err := db.QueryRow("SELECT id, filename, path FROM files WHERE id=?", fileID).Scan(¤tFile.ID, ¤tFile.Filename, ¤tFile.Path) 44 if err != nil { 45 log.Printf("Error: fileDeleteHandler: file not found for id=%s: %v", fileID, err) 46 renderError(w, "File not found", http.StatusNotFound) 47 return 48 } 49 50 tx, err := db.Begin() 51 if err != nil { 52 log.Printf("Error: fileDeleteHandler: failed to start transaction for file id=%s: %v", fileID, err) 53 renderError(w, "Failed to start transaction", http.StatusInternalServerError) 54 return 55 } 56 defer tx.Rollback() 57 58 if _, err = tx.Exec("DELETE FROM file_tags WHERE file_id=?", fileID); err != nil { 59 log.Printf("Error: fileDeleteHandler: failed to delete file_tags for file id=%s: %v", fileID, err) 60 renderError(w, "Failed to delete file tags", http.StatusInternalServerError) 61 return 62 } 63 64 if _, err = tx.Exec("DELETE FROM file_properties WHERE file_id=?", fileID); err != nil { 65 log.Printf("Error: fileDeleteHandler: failed to delete file_properties for file id=%s: %v", fileID, err) 66 renderError(w, "Failed to delete file properties", http.StatusInternalServerError) 67 return 68 } 69 70 if _, err = tx.Exec("DELETE FROM files WHERE id=?", fileID); err != nil { 71 log.Printf("Error: fileDeleteHandler: failed to delete files record for file id=%s: %v", fileID, err) 72 renderError(w, "Failed to delete file record", http.StatusInternalServerError) 73 return 74 } 75 76 if err = tx.Commit(); err != nil { 77 log.Printf("Error: fileDeleteHandler: failed to commit transaction for file id=%s: %v", fileID, err) 78 renderError(w, "Failed to commit transaction", http.StatusInternalServerError) 79 return 80 } 81 82 absPath := filepath.Join(config.UploadDir, currentFile.Path) 83 if err = os.Remove(absPath); err != nil { 84 log.Printf("Warning: fileDeleteHandler: failed to delete physical file %s: %v", absPath, err) 85 } 86 87 // Delete thumbnail if it exists 88 thumbPath := filepath.Join(config.UploadDir, "thumbnails", currentFile.Filename+".jpg") 89 if _, err := os.Stat(thumbPath); err == nil { 90 if err := os.Remove(thumbPath); err != nil { 91 log.Printf("Warning: fileDeleteHandler: failed to delete thumbnail %s: %v", thumbPath, err) 92 } 93 } 94 95 http.Redirect(w, r, "/?deleted="+currentFile.Filename, http.StatusSeeOther) 96 } 97 98 func fileRenameHandler(w http.ResponseWriter, r *http.Request, parts []string) { 99 if r.Method != http.MethodPost { 100 http.Redirect(w, r, "/file/"+parts[2], http.StatusSeeOther) 101 return 102 } 103 104 fileID := parts[2] 105 newFilename := sanitizeFilename(strings.TrimSpace(r.FormValue("newfilename"))) 106 107 if newFilename == "" { 108 renderError(w, "New filename cannot be empty", http.StatusBadRequest) 109 return 110 } 111 112 var currentFilename, currentRelPath string 113 err := db.QueryRow("SELECT filename, path FROM files WHERE id=?", fileID).Scan(¤tFilename, ¤tRelPath) 114 if err != nil { 115 log.Printf("Error: fileRenameHandler: file not found for id=%s: %v", fileID, err) 116 renderError(w, "File not found", http.StatusNotFound) 117 return 118 } 119 120 if currentFilename == newFilename { 121 http.Redirect(w, r, "/file/"+fileID, http.StatusSeeOther) 122 return 123 } 124 125 currentAbsPath := filepath.Join(config.UploadDir, currentRelPath) 126 newPath := filepath.Join(config.UploadDir, newFilename) 127 if _, err := os.Stat(newPath); !os.IsNotExist(err) { 128 renderError(w, "A file with that name already exists", http.StatusConflict) 129 return 130 } 131 132 if err := os.Rename(currentAbsPath, newPath); err != nil { 133 log.Printf("Error: fileRenameHandler: failed to rename %s -> %s: %v", currentAbsPath, newPath, err) 134 renderError(w, "Failed to rename physical file: "+err.Error(), http.StatusInternalServerError) 135 return 136 } 137 138 thumbOld := filepath.Join(config.UploadDir, "thumbnails", currentFilename+".jpg") 139 thumbNew := filepath.Join(config.UploadDir, "thumbnails", newFilename+".jpg") 140 141 if _, err := os.Stat(thumbOld); err == nil { 142 if err := os.Rename(thumbOld, thumbNew); err != nil { 143 log.Printf("Error: fileRenameHandler: failed to rename thumbnail %s -> %s: %v", thumbOld, thumbNew, err) 144 if renameErr := os.Rename(newPath, currentAbsPath); renameErr != nil { 145 log.Printf("Error: fileRenameHandler: failed to roll back file rename %s -> %s: %v", newPath, currentAbsPath, renameErr) 146 } 147 renderError(w, "Failed to rename thumbnail: "+err.Error(), http.StatusInternalServerError) 148 return 149 } 150 } 151 152 newRelPath, err := filepath.Rel(config.UploadDir, newPath) 153 if err != nil { 154 log.Printf("Error: fileRenameHandler: failed to compute relative path for %s: %v", newPath, err) 155 newRelPath = newFilename 156 } 157 158 _, err = db.Exec("UPDATE files SET filename=?, path=? WHERE id=?", newFilename, newRelPath, fileID) 159 if err != nil { 160 log.Printf("Error: fileRenameHandler: failed to update database for file id=%s: %v", fileID, err) 161 if renameErr := os.Rename(newPath, currentAbsPath); renameErr != nil { 162 log.Printf("Error: fileRenameHandler: failed to roll back file rename %s -> %s: %v", newPath, currentAbsPath, renameErr) 163 } 164 if _, statErr := os.Stat(thumbNew); statErr == nil { 165 if renameErr := os.Rename(thumbNew, thumbOld); renameErr != nil { 166 log.Printf("Error: fileRenameHandler: failed to roll back thumbnail rename %s -> %s: %v", thumbNew, thumbOld, renameErr) 167 } 168 } 169 renderError(w, "Failed to update database", http.StatusInternalServerError) 170 return 171 } 172 173 // Recompute properties in case the extension changed 174 if _, err := db.Exec("DELETE FROM file_properties WHERE file_id = ?", fileID); err != nil { 175 log.Printf("Warning: fileRenameHandler: failed to delete old properties for file id=%s: %v", fileID, err) 176 } 177 if id, err := strconv.ParseInt(fileID, 10, 64); err == nil { 178 computeProperties(id, newPath) 179 } else { 180 log.Printf("Warning: fileRenameHandler: failed to parse file id %s for property recompute: %v", fileID, err) 181 } 182 183 http.Redirect(w, r, "/file/"+fileID, http.StatusSeeOther) 184 } 185 186 func checkFileConflictStrict(filename string) (string, string, int64, error) { 187 finalPath := filepath.Join(config.UploadDir, filename) 188 if _, err := os.Stat(finalPath); err == nil { 189 existingID, dbErr := getFileIDByName(filename) 190 if dbErr != nil { 191 log.Printf("Warning: checkFileConflictStrict: file %s exists on disk but not in DB: %v", filename, dbErr) 192 return "", "", 0, fmt.Errorf("a file with that name already exists") 193 } 194 return "", "", existingID, nil 195 } else if !os.IsNotExist(err) { 196 return "", "", 0, fmt.Errorf("failed to check for existing file: %v", err) 197 } 198 return filename, finalPath, 0, nil 199 } 200 201 func getFileIDByName(filename string) (int64, error) { 202 var id int64 203 err := db.QueryRow("SELECT id FROM files WHERE filename = ?", filename).Scan(&id) 204 return id, err 205 }