2019-01-05 22:44:33 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2021-03-10 15:14:01 +00:00
|
|
|
"context"
|
2019-01-05 22:44:33 +00:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
2020-11-03 12:30:56 +00:00
|
|
|
"path"
|
2020-06-16 19:56:44 +00:00
|
|
|
"path/filepath"
|
2024-12-02 17:14:50 +00:00
|
|
|
"strconv"
|
2019-01-05 22:44:33 +00:00
|
|
|
"strings"
|
|
|
|
|
2022-06-02 10:52:24 +00:00
|
|
|
"github.com/shirou/gopsutil/v3/disk"
|
2020-07-20 17:38:43 +00:00
|
|
|
|
2025-01-21 14:02:43 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/backend/cache"
|
2024-12-17 00:01:55 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
|
|
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
2025-01-26 00:31:40 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
2019-01-05 22:44:33 +00:00
|
|
|
)
|
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
// resourceGetHandler retrieves information about a resource.
|
|
|
|
// @Summary Get resource information
|
|
|
|
// @Description Returns metadata and optionally file contents for a specified resource path.
|
|
|
|
// @Tags Resources
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Param path query string true "Path to the resource"
|
2025-01-05 19:05:33 +00:00
|
|
|
// @Param source query string false "Source name for the desired source, default is used if not provided"
|
2024-11-21 00:15:30 +00:00
|
|
|
// @Param source query string false "Name for the desired source, default is used if not provided"
|
|
|
|
// @Param content query string false "Include file content if true"
|
|
|
|
// @Param checksum query string false "Optional checksum validation"
|
|
|
|
// @Success 200 {object} files.FileInfo "Resource metadata"
|
|
|
|
// @Failure 404 {object} map[string]string "Resource not found"
|
|
|
|
// @Failure 500 {object} map[string]string "Internal server error"
|
|
|
|
// @Router /api/resources [get]
|
|
|
|
func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
2024-12-17 00:01:55 +00:00
|
|
|
encodedPath := r.URL.Query().Get("path")
|
2025-01-05 19:05:33 +00:00
|
|
|
source := r.URL.Query().Get("source")
|
|
|
|
if source == "" {
|
|
|
|
source = "default"
|
|
|
|
}
|
2024-12-17 00:01:55 +00:00
|
|
|
// Decode the URL-encoded path
|
|
|
|
path, err := url.QueryUnescape(encodedPath)
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
fileInfo, err := files.FileInfoFaster(files.FileOptions{
|
2025-01-27 00:21:12 +00:00
|
|
|
Path: filepath.Join(d.user.Scope, path),
|
|
|
|
Modify: d.user.Perm.Modify,
|
|
|
|
Source: source,
|
|
|
|
Expand: true,
|
|
|
|
Checker: d.user,
|
|
|
|
Content: r.URL.Query().Get("content") == "true",
|
2019-01-05 22:44:33 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errToStatus(err), err
|
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
if fileInfo.Type == "directory" {
|
|
|
|
return renderJSON(w, r, fileInfo)
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
if algo := r.URL.Query().Get("checksum"); algo != "" {
|
2025-01-26 00:31:40 +00:00
|
|
|
idx := files.GetIndex(source)
|
|
|
|
realPath, _, _ := idx.GetRealPath(d.user.Scope, path)
|
|
|
|
checksums, err := files.GetChecksum(realPath, algo)
|
2024-11-21 00:15:30 +00:00
|
|
|
if err == errors.ErrInvalidOption {
|
|
|
|
return http.StatusBadRequest, nil
|
|
|
|
} else if err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
fileInfo.Checksums = checksums
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
return renderJSON(w, r, fileInfo)
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
// resourceDeleteHandler deletes a resource at a specified path.
|
|
|
|
// @Summary Delete a resource
|
|
|
|
// @Description Deletes a resource located at the specified path.
|
|
|
|
// @Tags Resources
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Param path query string true "Path to the resource"
|
2025-01-05 19:05:33 +00:00
|
|
|
// @Param source query string false "Source name for the desired source, default is used if not provided"
|
2024-11-21 00:15:30 +00:00
|
|
|
// @Param source query string false "Name for the desired source, default is used if not provided"
|
|
|
|
// @Success 200 "Resource deleted successfully"
|
|
|
|
// @Failure 403 {object} map[string]string "Forbidden"
|
|
|
|
// @Failure 404 {object} map[string]string "Resource not found"
|
|
|
|
// @Failure 500 {object} map[string]string "Internal server error"
|
|
|
|
// @Router /api/resources [delete]
|
|
|
|
func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
|
|
|
// TODO source := r.URL.Query().Get("source")
|
2024-12-17 00:01:55 +00:00
|
|
|
encodedPath := r.URL.Query().Get("path")
|
2025-01-05 19:05:33 +00:00
|
|
|
source := r.URL.Query().Get("source")
|
|
|
|
if source == "" {
|
|
|
|
source = "default"
|
|
|
|
}
|
2024-12-17 00:01:55 +00:00
|
|
|
// Decode the URL-encoded path
|
|
|
|
path, err := url.QueryUnescape(encodedPath)
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
if path == "/" || !d.user.Perm.Delete {
|
|
|
|
return http.StatusForbidden, nil
|
|
|
|
}
|
|
|
|
fileOpts := files.FileOptions{
|
2025-01-27 00:21:12 +00:00
|
|
|
Path: filepath.Join(d.user.Scope, path),
|
|
|
|
Source: source,
|
|
|
|
Modify: d.user.Perm.Modify,
|
|
|
|
Expand: false,
|
|
|
|
Checker: d.user,
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
fileInfo, err := files.FileInfoFaster(fileOpts)
|
2024-11-21 00:15:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errToStatus(err), err
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete thumbnails
|
2025-01-26 00:31:40 +00:00
|
|
|
delThumbs(r.Context(), fileCache, fileInfo)
|
2024-11-21 00:15:30 +00:00
|
|
|
|
2025-01-05 19:05:33 +00:00
|
|
|
err = files.DeleteFiles(source, fileInfo.RealPath, filepath.Dir(fileInfo.RealPath))
|
2024-11-21 00:15:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errToStatus(err), err
|
|
|
|
}
|
|
|
|
return http.StatusOK, nil
|
|
|
|
|
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
// resourcePostHandler creates or uploads a new resource.
|
|
|
|
// @Summary Create or upload a resource
|
|
|
|
// @Description Creates a new resource or uploads a file at the specified path. Supports file uploads and directory creation.
|
|
|
|
// @Tags Resources
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Param path query string true "Path to the resource"
|
2025-01-05 19:05:33 +00:00
|
|
|
// @Param source query string false "Source name for the desired source, default is used if not provided"
|
2024-11-21 00:15:30 +00:00
|
|
|
// @Param source query string false "Name for the desired source, default is used if not provided"
|
|
|
|
// @Param override query bool false "Override existing file if true"
|
|
|
|
// @Success 200 "Resource created successfully"
|
|
|
|
// @Failure 403 {object} map[string]string "Forbidden"
|
|
|
|
// @Failure 404 {object} map[string]string "Resource not found"
|
|
|
|
// @Failure 409 {object} map[string]string "Conflict - Resource already exists"
|
|
|
|
// @Failure 500 {object} map[string]string "Internal server error"
|
|
|
|
// @Router /api/resources [post]
|
|
|
|
func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
2024-12-17 00:01:55 +00:00
|
|
|
encodedPath := r.URL.Query().Get("path")
|
2025-01-05 19:05:33 +00:00
|
|
|
source := r.URL.Query().Get("source")
|
|
|
|
if source == "" {
|
|
|
|
source = "default"
|
|
|
|
}
|
2024-12-17 00:01:55 +00:00
|
|
|
// Decode the URL-encoded path
|
|
|
|
path, err := url.QueryUnescape(encodedPath)
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
if !d.user.Perm.Create || !d.user.Check(path) {
|
|
|
|
return http.StatusForbidden, nil
|
|
|
|
}
|
|
|
|
fileOpts := files.FileOptions{
|
2024-11-26 17:21:41 +00:00
|
|
|
Path: filepath.Join(d.user.Scope, path),
|
2025-01-05 19:05:33 +00:00
|
|
|
Source: source,
|
2024-11-26 17:21:41 +00:00
|
|
|
Modify: d.user.Perm.Modify,
|
|
|
|
Expand: false,
|
|
|
|
Checker: d.user,
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
|
|
|
// Directories creation on POST.
|
|
|
|
if strings.HasSuffix(path, "/") {
|
2024-12-17 00:01:55 +00:00
|
|
|
err = files.WriteDirectory(fileOpts)
|
2020-07-28 09:57:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return errToStatus(err), err
|
|
|
|
}
|
|
|
|
return http.StatusOK, nil
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
fileInfo, err := files.FileInfoFaster(fileOpts)
|
2024-11-21 00:15:30 +00:00
|
|
|
if err == nil {
|
|
|
|
if r.URL.Query().Get("override") != "true" {
|
|
|
|
return http.StatusConflict, nil
|
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
// Permission for overwriting the file
|
|
|
|
if !d.user.Perm.Modify {
|
2021-03-10 15:14:01 +00:00
|
|
|
return http.StatusForbidden, nil
|
|
|
|
}
|
2021-03-26 13:30:14 +00:00
|
|
|
|
2025-01-26 00:31:40 +00:00
|
|
|
delThumbs(r.Context(), fileCache, fileInfo)
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
|
|
|
err = files.WriteFile(fileOpts, r.Body)
|
2024-11-26 17:21:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return errToStatus(err), err
|
|
|
|
|
|
|
|
}
|
|
|
|
return http.StatusOK, nil
|
2021-03-10 15:14:01 +00:00
|
|
|
}
|
2020-06-16 19:56:44 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
// resourcePutHandler updates an existing file resource.
|
|
|
|
// @Summary Update a file resource
|
|
|
|
// @Description Updates an existing file at the specified path.
|
|
|
|
// @Tags Resources
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Param path query string true "Path to the resource"
|
2025-01-05 19:05:33 +00:00
|
|
|
// @Param source query string false "Source name for the desired source, default is used if not provided"
|
2024-11-21 00:15:30 +00:00
|
|
|
// @Param source query string false "Name for the desired source, default is used if not provided"
|
|
|
|
// @Success 200 "Resource updated successfully"
|
|
|
|
// @Failure 403 {object} map[string]string "Forbidden"
|
|
|
|
// @Failure 404 {object} map[string]string "Resource not found"
|
|
|
|
// @Failure 405 {object} map[string]string "Method not allowed"
|
|
|
|
// @Failure 500 {object} map[string]string "Internal server error"
|
|
|
|
// @Router /api/resources [put]
|
|
|
|
func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
2025-01-05 19:05:33 +00:00
|
|
|
source := r.URL.Query().Get("source")
|
|
|
|
if source == "" {
|
|
|
|
source = "default"
|
|
|
|
}
|
|
|
|
|
2024-12-17 00:01:55 +00:00
|
|
|
encodedPath := r.URL.Query().Get("path")
|
2025-01-05 19:05:33 +00:00
|
|
|
|
2024-12-17 00:01:55 +00:00
|
|
|
// Decode the URL-encoded path
|
|
|
|
path, err := url.QueryUnescape(encodedPath)
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
if !d.user.Perm.Modify || !d.user.Check(path) {
|
2021-03-10 13:32:11 +00:00
|
|
|
return http.StatusForbidden, nil
|
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2021-03-10 13:32:11 +00:00
|
|
|
// Only allow PUT for files.
|
2024-11-21 00:15:30 +00:00
|
|
|
if strings.HasSuffix(path, "/") {
|
2021-03-10 13:32:11 +00:00
|
|
|
return http.StatusMethodNotAllowed, nil
|
|
|
|
}
|
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
fileOpts := files.FileOptions{
|
2025-01-27 00:21:12 +00:00
|
|
|
Path: filepath.Join(d.user.Scope, path),
|
|
|
|
Source: source,
|
|
|
|
Modify: d.user.Perm.Modify,
|
|
|
|
Expand: false,
|
|
|
|
Checker: d.user,
|
2021-07-24 13:16:09 +00:00
|
|
|
}
|
2024-08-24 22:02:33 +00:00
|
|
|
err = files.WriteFile(fileOpts, r.Body)
|
2019-01-05 22:44:33 +00:00
|
|
|
return errToStatus(err), err
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2021-04-14 15:20:38 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
// resourcePatchHandler performs a patch operation (e.g., move, rename) on a resource.
|
|
|
|
// @Summary Patch resource (move/rename)
|
|
|
|
// @Description Moves or renames a resource to a new destination.
|
|
|
|
// @Tags Resources
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Param from query string true "Path from resource"
|
2025-01-05 19:05:33 +00:00
|
|
|
// @Param source query string false "Source name for the desired source, default is used if not provided"
|
2024-11-21 00:15:30 +00:00
|
|
|
// @Param destination query string true "Destination path for the resource"
|
|
|
|
// @Param action query string true "Action to perform (copy, rename)"
|
|
|
|
// @Param overwrite query bool false "Overwrite if destination exists"
|
|
|
|
// @Param rename query bool false "Rename if destination exists"
|
|
|
|
// @Success 200 "Resource moved/renamed successfully"
|
|
|
|
// @Failure 403 {object} map[string]string "Forbidden"
|
|
|
|
// @Failure 404 {object} map[string]string "Resource not found"
|
|
|
|
// @Failure 409 {object} map[string]string "Conflict - Destination exists"
|
|
|
|
// @Failure 500 {object} map[string]string "Internal server error"
|
|
|
|
// @Router /api/resources [patch]
|
|
|
|
func resourcePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
|
|
|
// TODO source := r.URL.Query().Get("source")
|
|
|
|
action := r.URL.Query().Get("action")
|
2025-01-05 19:05:33 +00:00
|
|
|
source := r.URL.Query().Get("source")
|
|
|
|
if source == "" {
|
|
|
|
source = "default"
|
|
|
|
}
|
2024-12-17 00:01:55 +00:00
|
|
|
encodedFrom := r.URL.Query().Get("from")
|
|
|
|
// Decode the URL-encoded path
|
|
|
|
src, err := url.QueryUnescape(encodedFrom)
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2024-12-17 00:01:55 +00:00
|
|
|
dst := r.URL.Query().Get("destination")
|
|
|
|
dst, err = url.QueryUnescape(dst)
|
2024-11-21 00:15:30 +00:00
|
|
|
if err != nil {
|
2021-04-14 15:20:38 +00:00
|
|
|
return errToStatus(err), err
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2024-12-17 00:01:55 +00:00
|
|
|
if !d.user.Check(src) || !d.user.Check(dst) {
|
2025-01-09 01:02:57 +00:00
|
|
|
return http.StatusForbidden, fmt.Errorf("forbidden: user rules deny access to source or destination")
|
2024-12-17 00:01:55 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
if dst == "/" || src == "/" {
|
2025-01-09 01:02:57 +00:00
|
|
|
return http.StatusForbidden, fmt.Errorf("forbidden: source or destination is attempting to modify root")
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
|
|
|
|
2025-01-05 19:05:33 +00:00
|
|
|
idx := files.GetIndex(source)
|
2024-11-21 00:15:30 +00:00
|
|
|
// check target dir exists
|
2025-01-05 19:05:33 +00:00
|
|
|
parentDir, _, err := idx.GetRealPath(d.user.Scope, filepath.Dir(dst))
|
2024-11-21 00:15:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusNotFound, err
|
|
|
|
}
|
|
|
|
realDest := parentDir + "/" + filepath.Base(dst)
|
2025-01-05 19:05:33 +00:00
|
|
|
realSrc, isSrcDir, err := idx.GetRealPath(d.user.Scope, src)
|
2024-11-21 00:15:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusNotFound, err
|
|
|
|
}
|
|
|
|
overwrite := r.URL.Query().Get("overwrite") == "true"
|
|
|
|
rename := r.URL.Query().Get("rename") == "true"
|
|
|
|
if rename {
|
|
|
|
realDest = addVersionSuffix(realDest)
|
|
|
|
}
|
|
|
|
// Permission for overwriting the file
|
|
|
|
if overwrite && !d.user.Perm.Modify {
|
2025-01-09 01:02:57 +00:00
|
|
|
return http.StatusForbidden, fmt.Errorf("forbidden: user does not have permission to overwrite file")
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
2025-01-27 00:21:12 +00:00
|
|
|
err = d.Runner.RunHook(func() error {
|
2025-01-05 19:05:33 +00:00
|
|
|
return patchAction(r.Context(), action, realSrc, realDest, d, fileCache, isSrcDir, source)
|
2024-11-21 00:15:30 +00:00
|
|
|
}, action, realSrc, realDest, d.user)
|
2025-01-26 00:31:40 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Debug(fmt.Sprintf("Could not run patch action. src=%v dst=%v err=%v", realSrc, realDest, err))
|
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
return errToStatus(err), err
|
2021-04-14 15:20:38 +00:00
|
|
|
}
|
2020-07-20 17:38:43 +00:00
|
|
|
|
2024-08-24 22:02:33 +00:00
|
|
|
func addVersionSuffix(source string) string {
|
2020-07-20 17:38:43 +00:00
|
|
|
counter := 1
|
2020-11-03 12:30:56 +00:00
|
|
|
dir, name := path.Split(source)
|
2020-07-20 17:38:43 +00:00
|
|
|
ext := filepath.Ext(name)
|
|
|
|
base := strings.TrimSuffix(name, ext)
|
|
|
|
for {
|
2024-08-24 22:02:33 +00:00
|
|
|
if _, err := os.Stat(source); err != nil {
|
2020-07-20 17:38:43 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
renamed := fmt.Sprintf("%s(%d)%s", base, counter, ext)
|
2020-11-03 12:30:56 +00:00
|
|
|
source = path.Join(dir, renamed)
|
2020-07-20 17:38:43 +00:00
|
|
|
counter++
|
|
|
|
}
|
2020-11-03 12:30:56 +00:00
|
|
|
return source
|
2020-07-20 17:38:43 +00:00
|
|
|
}
|
2021-03-10 13:32:11 +00:00
|
|
|
|
2025-01-26 00:31:40 +00:00
|
|
|
func delThumbs(ctx context.Context, fileCache FileCache, file files.ExtendedFileInfo) {
|
|
|
|
err := fileCache.Delete(ctx, previewCacheKey(file.RealPath, "small", file.FileInfo.ModTime))
|
|
|
|
if err != nil {
|
|
|
|
logger.Debug(fmt.Sprintf("Could not delete small thumbnail: %v", err))
|
2021-03-10 15:14:01 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-16 12:04:06 +00:00
|
|
|
|
2025-01-05 19:05:33 +00:00
|
|
|
func patchAction(ctx context.Context, action, src, dst string, d *requestContext, fileCache FileCache, isSrcDir bool, index string) error {
|
2021-04-16 12:04:06 +00:00
|
|
|
switch action {
|
|
|
|
case "copy":
|
|
|
|
if !d.user.Perm.Create {
|
|
|
|
return errors.ErrPermissionDenied
|
|
|
|
}
|
2025-01-05 19:05:33 +00:00
|
|
|
err := files.CopyResource(index, src, dst, isSrcDir)
|
2024-12-17 00:01:55 +00:00
|
|
|
return err
|
2024-11-21 00:15:30 +00:00
|
|
|
case "rename", "move":
|
2021-04-16 12:04:06 +00:00
|
|
|
if !d.user.Perm.Rename {
|
|
|
|
return errors.ErrPermissionDenied
|
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
fileInfo, err := files.FileInfoFaster(files.FileOptions{
|
2024-11-21 00:15:30 +00:00
|
|
|
Path: src,
|
2025-01-05 19:05:33 +00:00
|
|
|
Source: index,
|
2024-11-21 00:15:30 +00:00
|
|
|
IsDir: isSrcDir,
|
2021-04-16 12:04:06 +00:00
|
|
|
Modify: d.user.Perm.Modify,
|
|
|
|
Expand: false,
|
|
|
|
ReadHeader: false,
|
2024-11-21 00:15:30 +00:00
|
|
|
Checker: d.user,
|
2021-04-16 12:04:06 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete thumbnails
|
2025-01-26 00:31:40 +00:00
|
|
|
delThumbs(ctx, fileCache, fileInfo)
|
2025-01-05 19:05:33 +00:00
|
|
|
return files.MoveResource(index, src, dst, isSrcDir)
|
2021-04-16 12:04:06 +00:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
|
|
|
|
}
|
|
|
|
}
|
2022-06-02 10:52:24 +00:00
|
|
|
|
|
|
|
type DiskUsageResponse struct {
|
|
|
|
Total uint64 `json:"total"`
|
|
|
|
Used uint64 `json:"used"`
|
|
|
|
}
|
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
// diskUsage returns the disk usage information for a given directory.
|
|
|
|
// @Summary Get disk usage
|
|
|
|
// @Description Returns the total and used disk space for a specified directory.
|
|
|
|
// @Tags Resources
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
2025-01-05 19:05:33 +00:00
|
|
|
// @Param source query string false "Source name for the desired source, default is used if not provided"
|
2024-11-21 00:15:30 +00:00
|
|
|
// @Success 200 {object} DiskUsageResponse "Disk usage details"
|
|
|
|
// @Failure 404 {object} map[string]string "Directory not found"
|
|
|
|
// @Failure 500 {object} map[string]string "Internal server error"
|
|
|
|
// @Router /api/usage [get]
|
|
|
|
func diskUsage(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
|
|
|
source := r.URL.Query().Get("source")
|
|
|
|
if source == "" {
|
2025-01-05 19:05:33 +00:00
|
|
|
source = "default"
|
2024-08-24 22:02:33 +00:00
|
|
|
}
|
2025-01-21 14:02:43 +00:00
|
|
|
value, ok := cache.DiskUsage.Get(source).(DiskUsageResponse)
|
2024-11-26 17:21:41 +00:00
|
|
|
if ok {
|
|
|
|
return renderJSON(w, r, &value)
|
|
|
|
}
|
|
|
|
|
2025-01-05 19:05:33 +00:00
|
|
|
rootPath, ok := files.RootPaths[source]
|
|
|
|
if !ok {
|
|
|
|
return 400, fmt.Errorf("bad source path provided: %v", source)
|
2022-06-13 10:50:39 +00:00
|
|
|
}
|
2025-01-05 19:05:33 +00:00
|
|
|
|
|
|
|
usage, err := disk.UsageWithContext(r.Context(), rootPath)
|
2022-06-02 10:52:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return errToStatus(err), err
|
|
|
|
}
|
2024-11-26 17:21:41 +00:00
|
|
|
latestUsage := DiskUsageResponse{
|
2022-06-02 10:52:24 +00:00
|
|
|
Total: usage.Total,
|
|
|
|
Used: usage.Used,
|
2024-11-26 17:21:41 +00:00
|
|
|
}
|
2025-01-21 14:02:43 +00:00
|
|
|
cache.DiskUsage.Set(source, latestUsage)
|
2024-11-26 17:21:41 +00:00
|
|
|
return renderJSON(w, r, &latestUsage)
|
2024-11-21 00:15:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func inspectIndex(w http.ResponseWriter, r *http.Request) {
|
2024-12-17 00:01:55 +00:00
|
|
|
encodedPath := r.URL.Query().Get("path")
|
2025-01-05 19:05:33 +00:00
|
|
|
source := r.URL.Query().Get("source")
|
|
|
|
if source == "" {
|
|
|
|
source = "default"
|
|
|
|
}
|
2024-12-17 00:01:55 +00:00
|
|
|
// Decode the URL-encoded path
|
|
|
|
path, _ := url.QueryUnescape(encodedPath)
|
2025-01-05 19:05:33 +00:00
|
|
|
isNotDir := r.URL.Query().Get("isDir") == "false" // default to isDir true
|
|
|
|
index := files.GetIndex(source)
|
|
|
|
info, _ := index.GetReducedMetadata(path, !isNotDir)
|
2024-11-21 00:15:30 +00:00
|
|
|
renderJSON(w, r, info) // nolint:errcheck
|
|
|
|
}
|
2024-12-02 17:14:50 +00:00
|
|
|
|
|
|
|
func mockData(w http.ResponseWriter, r *http.Request) {
|
|
|
|
d := r.URL.Query().Get("numDirs")
|
|
|
|
f := r.URL.Query().Get("numFiles")
|
|
|
|
NumDirs, err := strconv.Atoi(d)
|
|
|
|
numFiles, err2 := strconv.Atoi(f)
|
|
|
|
if err != nil || err2 != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
mockDir := files.CreateMockData(NumDirs, numFiles)
|
|
|
|
renderJSON(w, r, mockDir) // nolint:errcheck
|
|
|
|
}
|