chore (maintenance): prepare for delta sync

This commit is contained in:
MickaelK
2026-07-14 13:45:18 +10:00
parent d548d3237a
commit 968fd7b422
3 changed files with 68 additions and 50 deletions
+10
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
@@ -13,6 +14,15 @@ import (
"github.com/mickael-kerjean/filestash/server/pkg/permissions"
)
var file_cache AppCache
func init() {
file_cache = NewAppCache()
file_cache.OnEvict(func(key string, value interface{}) {
os.RemoveAll(filepath.Join(GetAbsolutePath(TMP_PATH), key))
})
}
func FileCat(ctx *App, res http.ResponseWriter, req *http.Request) {
var (
file io.ReadCloser
+58 -34
View File
@@ -68,54 +68,72 @@ func FileSave(ctx *App, res http.ResponseWriter, req *http.Request) {
}
}
// There is 2 ways to save something:
// - case1: regular upload, we just insert the file in the pipe
// There is a few ways to save something: plain upload, via tus, via delta sync
proto := ""
if _, ok := req.Header["Tus-Resumable"]; ok {
if strings.HasPrefix(req.Header.Get("Content-Type"), "application/vnd.filestash.delta") {
proto = "delta"
} else if _, ok := req.Header["Tus-Resumable"]; ok {
proto = "tus"
}
if proto == "" && req.Method == http.MethodPost {
since := req.Header.Get("If-Unmodified-Since")
if since != "" {
expected, err := http.ParseTime(since)
if err != nil {
Log.Debug("files::save action=precondition err=%s", err.Error())
SendErrorResult(res, ErrNotValid)
return
} else if finfo, err := ctx.Backend.Stat(path); err == nil && finfo.ModTime().Unix() != expected.Unix() {
SendErrorResult(res, NewError("Modified since", http.StatusPreconditionFailed))
return
}
}
err = ctx.Backend.Save(path, req.Body)
req.Body.Close()
if err != nil {
Log.Debug("files::save action=backend_save err=%s", err.Error())
SendErrorResult(res, NewError(err.Error(), 403))
return
}
if since != "" {
if finfo, err := ctx.Backend.Stat(path); err == nil && finfo.ModTime().Unix() > 0 {
h.Set("Last-Modified", finfo.ModTime().UTC().Format(http.TimeFormat))
}
}
SendSuccessResult(res, nil)
switch proto {
case "":
handlerClassic(ctx, res, req, path, h)
case "tus":
handlerTUS(ctx, res, req, path, h)
case "delta":
handlerDelta(ctx, res, req, path, h)
default:
SendErrorResult(res, ErrNotImplemented)
}
}
func handlerClassic(ctx *App, res http.ResponseWriter, req *http.Request, path string, h http.Header) {
if req.Method != http.MethodPost {
SendErrorResult(res, ErrNotFound)
return
}
// - case2: chunked upload using the TUS protocol: https://tus.io/protocols/resumable-upload
since := req.Header.Get("If-Unmodified-Since")
if since != "" {
expected, err := http.ParseTime(since)
if err != nil {
Log.Debug("files::save action=precondition err=%s", err.Error())
SendErrorResult(res, ErrNotValid)
return
} else if finfo, err := ctx.Backend.Stat(path); err == nil && finfo.ModTime().Unix() != expected.Unix() {
SendErrorResult(res, NewError("Modified since", http.StatusPreconditionFailed))
return
}
}
err := ctx.Backend.Save(path, req.Body)
req.Body.Close()
if err != nil {
Log.Debug("files::save action=backend_save err=%s", err.Error())
SendErrorResult(res, NewError(err.Error(), 403))
return
}
if since != "" {
if finfo, err := ctx.Backend.Stat(path); err == nil && finfo.ModTime().Unix() > 0 {
h.Set("Last-Modified", finfo.ModTime().UTC().Format(http.TimeFormat))
}
}
SendSuccessResult(res, nil)
return
}
func handlerTUS(ctx *App, res http.ResponseWriter, req *http.Request, path string, h http.Header) {
cacheKey := map[string]string{
"path": path,
"session": GenerateID(ctx.Session),
}
if proto == "tus" && req.Method == http.MethodOptions {
if req.Method == http.MethodOptions {
h.Set("Tus-Resumable", "1.0.0")
h.Set("Tus-Version", "1.0.0")
h.Set("Tus-Extension", "creation,checksum")
h.Set("Tus-Checksum-Algorithm", "sha1,crc32")
return
}
if proto == "tus" && req.Method == http.MethodHead {
if req.Method == http.MethodHead {
c := chunkedUploadCache.Get(cacheKey)
if c == nil {
SendErrorResult(res, ErrNotFound)
@@ -129,7 +147,7 @@ func FileSave(ctx *App, res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusNoContent)
return
}
if proto == "tus" && req.Method == http.MethodPost {
if req.Method == http.MethodPost {
if c := chunkedUploadCache.Get(cacheKey); c != nil {
chunkedUploadCache.Del(cacheKey)
}
@@ -154,7 +172,7 @@ func FileSave(ctx *App, res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusCreated)
return
}
if proto == "tus" && req.Method == http.MethodPatch {
if req.Method == http.MethodPatch {
if req.Header.Get("Content-Type") != "application/offset+octet-stream" {
SendErrorResult(res, NewError("Unsupported Media Type", 415))
return
@@ -230,6 +248,12 @@ func FileSave(ctx *App, res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusNoContent)
return
}
SendErrorResult(res, ErrNotFound)
}
func handlerDelta(ctx *App, res http.ResponseWriter, req *http.Request, path string, h http.Header) {
// TODO
SendErrorResult(res, ErrNotImplemented)
}
-16
View File
@@ -1,12 +1,5 @@
package files
import (
"os"
"path/filepath"
. "github.com/mickael-kerjean/filestash/server/common"
)
type FileInfo struct {
Name string `json:"name"`
Type string `json:"type"`
@@ -15,12 +8,3 @@ type FileInfo struct {
Mode uint32 `json:"mode,omitempty"`
Offline bool `json:"offline,omitempty"`
}
var file_cache AppCache
func init() {
file_cache = NewAppCache()
file_cache.OnEvict(func(key string, value interface{}) {
os.RemoveAll(filepath.Join(GetAbsolutePath(TMP_PATH), key))
})
}