Enhance avatar upload handling by providing immediate preview data and improving cache write logic
This commit is contained in:
@@ -113,6 +113,19 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
||||
_avatarUploadProgress = 0.0;
|
||||
});
|
||||
|
||||
// Immediately show a preview if the server returned the avatar bytes
|
||||
try {
|
||||
final avatarData = response['avatarData'] as String?;
|
||||
final contentType = response['contentType'] as String?;
|
||||
if (avatarData != null && contentType != null) {
|
||||
setState(() {
|
||||
_avatarUrl = 'data:$contentType;base64,$avatarData';
|
||||
});
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Fetch latest profile from backend so we get the canonical avatar URL and version
|
||||
try {
|
||||
final apiClient = GetIt.I<ApiClient>();
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -84,7 +85,11 @@ func writeAvatarCache(cfg *config.Config, userID string, version string, ext str
|
||||
dir = fallback
|
||||
}
|
||||
p := avatarCachePath(&config.Config{AvatarCacheDir: dir}, userID, version, ext)
|
||||
return os.WriteFile(p, data, 0644)
|
||||
if err := os.WriteFile(p, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("[INFO] Wrote avatar cache for user=%s v=%s path=%s size=%d\n", userID, version, p, len(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
// readAvatarCache attempts to read a cached avatar for a user and optional version. If version is provided, it looks for an exact match; otherwise it returns the latest available cached avatar (if any).
|
||||
@@ -96,9 +101,12 @@ func readAvatarCache(cfg *config.Config, userID string, version string) ([]byte,
|
||||
// Also check fallback tmp dir
|
||||
checkDirs = append(checkDirs, filepath.Join(os.TempDir(), "b0esche_avatars"))
|
||||
|
||||
fmt.Printf("[DEBUG] readAvatarCache checking dirs=%v for user=%s version=%s\n", checkDirs, userID, version)
|
||||
|
||||
for _, dir := range checkDirs {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
fmt.Printf("[DEBUG] readAvatarCache cannot read dir %s: %v\n", dir, err)
|
||||
continue
|
||||
}
|
||||
if version != "" {
|
||||
@@ -107,8 +115,10 @@ func readAvatarCache(cfg *config.Config, userID string, version string) ([]byte,
|
||||
for _, e := range entries {
|
||||
if strings.HasPrefix(e.Name(), prefix) {
|
||||
p := filepath.Join(dir, e.Name())
|
||||
fmt.Printf("[INFO] Found cached avatar for user=%s v=%s path=%s\n", userID, version, p)
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
fmt.Printf("[WARN] failed to read cached avatar %s: %v\n", p, err)
|
||||
return nil, "", err
|
||||
}
|
||||
ext := filepath.Ext(e.Name())
|
||||
@@ -120,6 +130,7 @@ func readAvatarCache(cfg *config.Config, userID string, version string) ([]byte,
|
||||
}
|
||||
}
|
||||
// not found
|
||||
fmt.Printf("[DEBUG] no exact cached avatar match in %s for prefix=%s\n", dir, prefix)
|
||||
continue
|
||||
}
|
||||
// no version specified: return latest by modtime of files starting with userID.
|
||||
@@ -139,8 +150,10 @@ func readAvatarCache(cfg *config.Config, userID string, version string) ([]byte,
|
||||
}
|
||||
if latest != nil {
|
||||
p := filepath.Join(dir, latestName)
|
||||
fmt.Printf("[INFO] Found latest cached avatar for user=%s path=%s\n", userID, p)
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
fmt.Printf("[WARN] failed to read cached avatar %s: %v\n", p, err)
|
||||
return nil, "", err
|
||||
}
|
||||
ext := filepath.Ext(latestName)
|
||||
@@ -4213,8 +4226,11 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
}
|
||||
// Save avatar bytes to local cache keyed by version so it survives restarts and avoids unnecessary re-downloads
|
||||
versionStr := fmt.Sprintf("%d", version)
|
||||
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)
|
||||
} else {
|
||||
cached = true
|
||||
}
|
||||
|
||||
// Audit log
|
||||
@@ -4228,14 +4244,20 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
},
|
||||
})
|
||||
|
||||
// Provide avatarData for immediate preview (small images only)
|
||||
avatarData := base64.StdEncoding.EncodeToString(fileBytes)
|
||||
|
||||
publicURL := fmt.Sprintf("https://go.b0esche.cloud/user/avatar?v=%d", version)
|
||||
if token, ok := middleware.GetToken(r.Context()); ok && token != "" {
|
||||
publicURL = fmt.Sprintf("%s&token=%s", publicURL, token)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"message": "Avatar uploaded successfully",
|
||||
"avatarUrl": publicURL,
|
||||
"message": "Avatar uploaded successfully",
|
||||
"avatarUrl": publicURL,
|
||||
"cached": cached,
|
||||
"avatarData": avatarData,
|
||||
"contentType": contentType,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user