Files
WarpBox/lib/server/manifest.go

178 lines
4.3 KiB
Go
Raw Normal View History

package server
import (
"encoding/json"
"fmt"
"mime"
"os"
"path/filepath"
"strings"
"sync"
"warpbox/lib/models"
)
var boxManifestMu sync.Mutex
func listBoxFiles(boxID string) ([]models.BoxFile, error) {
if manifest, err := reconcileBoxManifest(boxID); err == nil && len(manifest.Files) > 0 {
files := make([]models.BoxFile, 0, len(manifest.Files))
for _, file := range manifest.Files {
files = append(files, decorateBoxFile(boxID, file))
}
return files, nil
}
return listCompletedFilesFromDisk(boxID)
}
func reconcileBoxManifest(boxID string) (models.BoxManifest, error) {
boxManifestMu.Lock()
defer boxManifestMu.Unlock()
manifest, err := readBoxManifestUnlocked(boxID)
if err != nil {
return manifest, err
}
changed := false
for index, file := range manifest.Files {
path := filepath.Join(boxPath(boxID), file.Name)
info, err := os.Stat(path)
if err != nil {
continue
}
if file.Status == fileStatusReady && file.Size == info.Size() {
continue
}
// The manifest is the UI source of truth, but disk wins when an upload
// was saved and the final status write/response was interrupted.
manifest.Files[index].Size = info.Size()
manifest.Files[index].MimeType = mimeTypeForFile(path, file.Name)
manifest.Files[index].Status = fileStatusReady
changed = true
}
if changed {
if err := writeBoxManifestUnlocked(boxID, manifest); err != nil {
return manifest, err
}
}
return manifest, nil
}
func createBoxManifest(boxID string, requests []models.CreateBoxFileRequest) ([]models.BoxFile, error) {
usedNames := make(map[string]int, len(requests))
files := make([]models.BoxFile, 0, len(requests))
for _, request := range requests {
filename, ok := safeFilename(request.Name)
if !ok {
return nil, fmt.Errorf("Invalid filename")
}
filename = uniqueManifestFilename(filename, usedNames)
fileID, err := newFileID()
if err != nil {
return nil, fmt.Errorf("Could not create file id")
}
mimeType := mime.TypeByExtension(strings.ToLower(filepath.Ext(filename)))
if mimeType == "" {
mimeType = "application/octet-stream"
}
files = append(files, models.BoxFile{
ID: fileID,
Name: filename,
Size: request.Size,
MimeType: mimeType,
Status: fileStatusWait,
})
}
manifest := models.BoxManifest{Files: files}
if err := writeBoxManifest(boxID, manifest); err != nil {
return nil, err
}
decoratedFiles := make([]models.BoxFile, 0, len(files))
for _, file := range files {
decoratedFiles = append(decoratedFiles, decorateBoxFile(boxID, file))
}
return decoratedFiles, nil
}
func markManifestFileStatus(boxID string, fileID string, status string) (models.BoxFile, error) {
if status != fileStatusWait && status != fileStatusWork && status != fileStatusReady && status != fileStatusFailed {
return models.BoxFile{}, fmt.Errorf("Invalid file status")
}
boxManifestMu.Lock()
defer boxManifestMu.Unlock()
manifest, err := readBoxManifestUnlocked(boxID)
if err != nil {
return models.BoxFile{}, err
}
for index, file := range manifest.Files {
if file.ID != fileID {
continue
}
manifest.Files[index].Status = status
if err := writeBoxManifestUnlocked(boxID, manifest); err != nil {
return models.BoxFile{}, err
}
return decorateBoxFile(boxID, manifest.Files[index]), nil
}
return models.BoxFile{}, fmt.Errorf("File not found")
}
func readBoxManifest(boxID string) (models.BoxManifest, error) {
boxManifestMu.Lock()
defer boxManifestMu.Unlock()
return readBoxManifestUnlocked(boxID)
}
func readBoxManifestUnlocked(boxID string) (models.BoxManifest, error) {
var manifest models.BoxManifest
data, err := os.ReadFile(manifestPath(boxID))
if err != nil {
return manifest, err
}
if err := json.Unmarshal(data, &manifest); err != nil {
return manifest, err
}
return manifest, nil
}
func writeBoxManifest(boxID string, manifest models.BoxManifest) error {
boxManifestMu.Lock()
defer boxManifestMu.Unlock()
return writeBoxManifestUnlocked(boxID, manifest)
}
// Manifest writes are serialized because the browser can upload several files
// concurrently into the same box. Without this lock, status updates can race.
func writeBoxManifestUnlocked(boxID string, manifest models.BoxManifest) error {
data, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return err
}
return os.WriteFile(manifestPath(boxID), data, 0644)
}