mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Restructure go project (#2356)
* Move main to cmd * Move api to internal * Move logger and manager to internal * Move shell hiding code to separate package * Decouple job from desktop and utils * Decouple session from config * Move static into internal * Decouple config from dlna * Move desktop to internal * Move dlna to internal * Decouple remaining packages from config * Move config into internal * Move jsonschema and paths to models * Make ffmpeg functions private * Move file utility methods into fsutil package * Move symwalk into fsutil * Move single-use util functions into client package * Move slice functions to separate packages * Add env var to suppress windowsgui arg * Move hash functions into separate package * Move identify to internal * Move autotag to internal * Touch UI when generating backend
This commit is contained in:
113
internal/manager/downloads.go
Normal file
113
internal/manager/downloads.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/hash"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
)
|
||||
|
||||
// DownloadStore manages single-use generated files for the UI to download.
|
||||
type DownloadStore struct {
|
||||
m map[string]*storeFile
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type storeFile struct {
|
||||
path string
|
||||
contentType string
|
||||
keep bool
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func NewDownloadStore() *DownloadStore {
|
||||
return &DownloadStore{
|
||||
m: make(map[string]*storeFile),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DownloadStore) RegisterFile(fp string, contentType string, keep bool) (string, error) {
|
||||
const keyLength = 4
|
||||
const attempts = 100
|
||||
|
||||
// keep generating random keys until we get a free one
|
||||
// prevent infinite loop by only attempting a finite amount of times
|
||||
var h string
|
||||
generate := true
|
||||
a := 0
|
||||
|
||||
s.mutex.Lock()
|
||||
for generate && a < attempts {
|
||||
var err error
|
||||
h, err = hash.GenerateRandomKey(keyLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, generate = s.m[h]
|
||||
a++
|
||||
}
|
||||
|
||||
s.m[h] = &storeFile{
|
||||
path: fp,
|
||||
contentType: contentType,
|
||||
keep: keep,
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (s *DownloadStore) Serve(hash string, w http.ResponseWriter, r *http.Request) {
|
||||
s.mutex.Lock()
|
||||
f, ok := s.m[hash]
|
||||
|
||||
if !ok {
|
||||
s.mutex.Unlock()
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if !f.keep {
|
||||
s.waitAndRemoveFile(hash, &w, r)
|
||||
}
|
||||
|
||||
s.mutex.Unlock()
|
||||
|
||||
if f.contentType != "" {
|
||||
w.Header().Add("Content-Type", f.contentType)
|
||||
}
|
||||
http.ServeFile(w, r, f.path)
|
||||
}
|
||||
|
||||
func (s *DownloadStore) waitAndRemoveFile(hash string, w *http.ResponseWriter, r *http.Request) {
|
||||
f := s.m[hash]
|
||||
notify := r.Context().Done()
|
||||
f.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
<-notify
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
f.wg.Done()
|
||||
}()
|
||||
|
||||
go f.once.Do(func() {
|
||||
// leave it up for 30 seconds after the first request to allow for multiple requests
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
f.wg.Wait()
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
delete(s.m, hash)
|
||||
err := os.Remove(f.path)
|
||||
if err != nil {
|
||||
logger.Errorf("error removing %s after downloading: %s", f.path, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user