diff --git a/cmd/serveg/main.go b/cmd/serveg/main.go index c6d0d1e..accf783 100644 --- a/cmd/serveg/main.go +++ b/cmd/serveg/main.go @@ -31,7 +31,8 @@ func run() int { return 1 } - // TODO: cleanup leftover .uploading files on restart + conf.Share.RemoveUploadingFiles() + srv := server.NewServer(conf) if err := srv.StartGoroutine(); err != nil { logger.Error(err) diff --git a/internal/server/PUT_OPTIONS.go b/internal/server/PUT_OPTIONS.go index 889cb52..3dcb2c7 100644 --- a/internal/server/PUT_OPTIONS.go +++ b/internal/server/PUT_OPTIONS.go @@ -24,7 +24,7 @@ var ErrIsUploadingAlready = http.NewHttpError(http.StatusConflict, var ErrFileIsDir = http.NewHttpError(http.StatusConflict, "there is a directory with such name already") -// PUT: accept request body and write it to filePath+".uploading" +// PUT: accept request body and write it to filePath+UploadingSuffix // OPTIONS: check if file can be uploaded and return without reading body // returns StatusCreated if upload is a new file, and StatusOK if it is replacing existing file func (conn *TcpConnection) PUT_OPTIONS(req *http.Request) *http.Response { @@ -87,7 +87,7 @@ func (conn *TcpConnection) handleUpload(req *http.Request, filePath string, file WithBodyStr(msg, http.TextPlain) } - tmpPath := filePath + ".uploading" + tmpPath := filePath + UploadingSuffix tmpFileExists, fi, err := DirOrFileExists(tmpPath) if err != nil { return errToResponse(err) @@ -161,6 +161,5 @@ func (conn *TcpConnection) handleUpload(req *http.Request, filePath string, file } renamed = true - // TODO: is it right status? return conn.http.CreateResponse(resultStatus, req, false) } diff --git a/internal/server/cleanup.go b/internal/server/cleanup.go new file mode 100644 index 0000000..24260b7 --- /dev/null +++ b/internal/server/cleanup.go @@ -0,0 +1,40 @@ +package server + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// PUT writes into filePath+UploadingSuffix and renames it when the body is received +const UploadingSuffix = ".uploading" + +// RemoveUploadingFiles deletes UploadingSuffix files left by interrupted uploads +// (server crash, kill -9). Call it before the server starts, while nothing is uploading. +// Errors are logged, not returned: a share that can't be cleaned is still usable. +func (shares Shares) RemoveUploadingFiles() { + for _, sh := range shares { + // WalkDir doesn't follow symlinks, so it doesn't leave the share + _ = filepath.WalkDir(sh.Path, func(path string, d fs.DirEntry, err error) error { + if err != nil { + confLogger.Warn("cleanup: ", err) + return nil // skip unreadable entry, continue walking + } + if !d.Type().IsRegular() || !strings.HasSuffix(d.Name(), UploadingSuffix) { + return nil + } + err = os.Remove(path) + switch { + case err == nil: + confLogger.Info("cleanup: removed ", path) + // nested shares (shared/ and shared/private) walk the same files twice + case errors.Is(err, fs.ErrNotExist): + default: + confLogger.Warn("cleanup: ", err) + } + return nil + }) + } +}