Commit 1f52b8ab by Denys Tito

fix: conservar nombre y tipo MIME reales en descargas cacheadas

DownloadFile devolvia el ID del archivo como nombre y deducia el tipo MIME
de la extension cuando la descarga salia de la cache, porque el archivo en
disco se guarda como <fileID><ext> y el nombre real nunca se conservaba.
La misma URL respondia distinto segun si la cache estaba fria o caliente.

Ahora el Service guarda path, nombre y tipo MIME en un indice en memoria al
cachear, y el TTL limpia indice y archivo juntos, de modo que un archivo
sobrante de una ejecucion anterior ya no se sirve indefinidamente.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent c7eccee7
......@@ -5,11 +5,11 @@ import (
"encoding/json"
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"sync"
"time"
"golang.org/x/oauth2"
......@@ -24,8 +24,16 @@ const oauthRedirectPath = "/oauth/callback"
const downloadCacheDir = "/var/tmp"
const downloadCacheTTL = 10 * time.Minute
type cachedDownload struct {
path string
name string
mimeType string
}
type Service struct {
svc *drive.Service
svc *drive.Service
mu sync.RWMutex
cached map[string]cachedDownload
}
func NewService(ctx context.Context, credentialsFile, tokenFile string) (*Service, error) {
......@@ -57,7 +65,7 @@ func NewService(ctx context.Context, credentialsFile, tokenFile string) (*Servic
return nil, fmt.Errorf("creating drive service: %w", err)
}
return &Service{svc: svc}, nil
return &Service{svc: svc, cached: make(map[string]cachedDownload)}, nil
}
func tokenFromFile(path string) (*oauth2.Token, error) {
......@@ -149,16 +157,11 @@ func (s *Service) UploadFile(ctx context.Context, folderID string, fileHeader *m
}
func (s *Service) DownloadFile(ctx context.Context, fileID string) (io.ReadCloser, string, string, error) {
if path, ok := findCachedDownload(fileID); ok {
f, err := os.Open(path)
if err == nil {
name := filepath.Base(path)
mimeType := mime.TypeByExtension(filepath.Ext(path))
if mimeType == "" {
mimeType = "application/octet-stream"
}
return f, name, mimeType, nil
if entry, ok := s.lookupCachedDownload(fileID); ok {
if f, err := os.Open(entry.path); err == nil {
return f, entry.name, entry.mimeType, nil
}
s.forgetCachedDownload(fileID)
}
meta, err := s.svc.Files.Get(fileID).SupportsAllDrives(true).Context(ctx).Fields("name, mimeType").Do()
......@@ -177,18 +180,42 @@ func (s *Service) DownloadFile(ctx context.Context, fileID string) (io.ReadClose
return nil, "", "", fmt.Errorf("caching downloaded file: %w", err)
}
s.rememberCachedDownload(fileID, cachedDownload{
path: cachePath,
name: meta.Name,
mimeType: meta.MimeType,
})
return body, meta.Name, meta.MimeType, nil
}
func findCachedDownload(fileID string) (string, bool) {
if matches, err := filepath.Glob(filepath.Join(downloadCacheDir, fileID+".*")); err == nil && len(matches) > 0 {
return matches[0], true
}
path := filepath.Join(downloadCacheDir, fileID)
if _, err := os.Stat(path); err == nil {
return path, true
func (s *Service) lookupCachedDownload(fileID string) (cachedDownload, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
entry, ok := s.cached[fileID]
return entry, ok
}
func (s *Service) rememberCachedDownload(fileID string, entry cachedDownload) {
s.mu.Lock()
s.cached[fileID] = entry
s.mu.Unlock()
time.AfterFunc(downloadCacheTTL, func() {
s.forgetCachedDownload(fileID)
})
}
func (s *Service) forgetCachedDownload(fileID string) {
s.mu.Lock()
entry, ok := s.cached[fileID]
delete(s.cached, fileID)
s.mu.Unlock()
if ok {
os.Remove(entry.path)
}
return "", false
}
func cacheDownload(body io.ReadCloser, path string) (io.ReadCloser, error) {
......@@ -205,9 +232,5 @@ func cacheDownload(body io.ReadCloser, path string) (io.ReadCloser, error) {
}
f.Close()
time.AfterFunc(downloadCacheTTL, func() {
os.Remove(path)
})
return os.Open(path)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment