Increase avatar download timeout and retries; add verification for uploaded avatars with fallback caching
This commit is contained in:
@@ -39,8 +39,8 @@ func Load() *Config {
|
||||
NextcloudBase: getEnv("NEXTCLOUD_BASEPATH", "/"),
|
||||
AllowedOrigins: getEnv("ALLOWED_ORIGINS", "https://b0esche.cloud,https://www.b0esche.cloud,https://*.b0esche.cloud,http://localhost:8080"),
|
||||
AvatarCacheDir: getEnv("AVATAR_CACHE_DIR", "/var/cache/b0esche/avatars"),
|
||||
AvatarDownloadTimeoutSeconds: getEnvInt("AVATAR_DOWNLOAD_TIMEOUT_SECONDS", 10),
|
||||
AvatarDownloadRetries: getEnvInt("AVATAR_DOWNLOAD_RETRIES", 2),
|
||||
AvatarDownloadTimeoutSeconds: getEnvInt("AVATAR_DOWNLOAD_TIMEOUT_SECONDS", 20),
|
||||
AvatarDownloadRetries: getEnvInt("AVATAR_DOWNLOAD_RETRIES", 3),
|
||||
}
|
||||
log.Printf("[CONFIG] Nextcloud URL: %q, User: %q, BasePath: %q, AvatarCacheDir: %q, AvatarDownloadTimeoutSeconds: %d, AvatarDownloadRetries: %d\n", cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudBase, cfg.AvatarCacheDir, cfg.AvatarDownloadTimeoutSeconds, cfg.AvatarDownloadRetries)
|
||||
return cfg
|
||||
|
||||
@@ -4216,6 +4216,29 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
return
|
||||
}
|
||||
|
||||
// Verify uploaded avatar is available on WebDAV (best-effort, retry)
|
||||
verified := false
|
||||
verifyRetries := 3
|
||||
verifyTimeout := 5 // seconds
|
||||
for i := 0; i < verifyRetries; i++ {
|
||||
vctx, vcancel := context.WithTimeout(r.Context(), time.Duration(verifyTimeout)*time.Second)
|
||||
resp, derr := client.Download(vctx, avatarPath, "")
|
||||
vcancel()
|
||||
if derr == nil && resp != nil {
|
||||
resp.Body.Close()
|
||||
verified = true
|
||||
break
|
||||
}
|
||||
fmt.Printf("[WARN] avatar verification attempt %d/%d failed for %s: %v\n", i+1, verifyRetries, avatarPath, derr)
|
||||
time.Sleep(time.Duration(300*(1<<i)) * time.Millisecond) // 300ms, 600ms, 1.2s
|
||||
}
|
||||
// If verification failed, log detailed message (but proceed to respond with preview/cache)
|
||||
if !verified {
|
||||
fmt.Printf("[ERROR] avatar verification failed after %d attempts for %s\n", verifyRetries, avatarPath)
|
||||
} else {
|
||||
fmt.Printf("[INFO] avatar verification succeeded for %s\n", avatarPath)
|
||||
}
|
||||
|
||||
// Build public URL including version based on updated_at and include token if available
|
||||
var version int64 = time.Now().Unix()
|
||||
// Try to use updated_at from DB to be more accurate
|
||||
@@ -4229,9 +4252,31 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
cached := false
|
||||
if err := writeAvatarCache(cfg, userID.String(), versionStr, ext, fileBytes); err != nil {
|
||||
fmt.Printf("[WARN] failed to write avatar cache for user=%s version=%s: %v\n", userID.String(), versionStr, err)
|
||||
// Attempt to write to an additional local data dir as a fallback
|
||||
fallbackDir := "./data/avatars"
|
||||
if err2 := os.MkdirAll(fallbackDir, 0755); err2 == nil {
|
||||
fallbackPath := filepath.Join(fallbackDir, fmt.Sprintf("%s.%s%s", userID.String(), versionStr, ext))
|
||||
if err3 := os.WriteFile(fallbackPath, fileBytes, 0644); err3 == nil {
|
||||
fmt.Printf("[INFO] Wrote avatar cache to fallback path %s\n", fallbackPath)
|
||||
cached = true
|
||||
} else {
|
||||
fmt.Printf("[WARN] failed to write avatar cache to fallback path %s: %v\n", fallbackPath, err3)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[WARN] failed to create fallback avatar dir %s: %v\n", fallbackDir, err2)
|
||||
}
|
||||
} else {
|
||||
cached = true
|
||||
}
|
||||
// Verify cache is readable
|
||||
if cached {
|
||||
if b, ct, cerr := readAvatarCache(cfg, userID.String(), versionStr); cerr != nil {
|
||||
fmt.Printf("[WARN] cache write verification failed for user=%s v=%s: %v\n", userID.String(), versionStr, cerr)
|
||||
cached = false
|
||||
} else {
|
||||
fmt.Printf("[INFO] cache write verified for user=%s v=%s (content-type=%s size=%d)\n", userID.String(), versionStr, ct, len(b))
|
||||
}
|
||||
}
|
||||
|
||||
// Audit log
|
||||
auditLogger.Log(r.Context(), audit.Entry{
|
||||
|
||||
Reference in New Issue
Block a user