.improved recent fetches

This commit is contained in:
afkarxyz
2026-04-13 22:18:08 +07:00
parent 2684bc54bd
commit 66e3f0e572
3 changed files with 163 additions and 21 deletions
+28
View File
@@ -935,6 +935,34 @@ func (a *App) ClearFetchHistoryByType(itemType string) error {
return backend.ClearFetchHistoryByType(itemType, "SpotiFLAC")
}
func (a *App) GetRecentFetches() (string, error) {
items, err := backend.LoadRecentFetches()
if err != nil {
return "", err
}
data, err := json.Marshal(items)
if err != nil {
return "", err
}
return string(data), nil
}
func (a *App) SaveRecentFetches(payload string) error {
payload = strings.TrimSpace(payload)
if payload == "" {
payload = "[]"
}
var items []backend.RecentFetchItem
if err := json.Unmarshal([]byte(payload), &items); err != nil {
return err
}
return backend.SaveRecentFetches(items)
}
func (a *App) SaveSpectrumImage(audioFilePath string, base64Data string) (string, error) {
if audioFilePath == "" || base64Data == "" {
return "", fmt.Errorf("file path and image data are required")
+91
View File
@@ -0,0 +1,91 @@
package backend
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"sync"
)
const recentFetchesFileName = "recent_fetches.json"
type RecentFetchItem struct {
ID string `json:"id"`
URL string `json:"url"`
Type string `json:"type"`
Name string `json:"name"`
Artist string `json:"artist"`
Image string `json:"image"`
Timestamp int64 `json:"timestamp"`
}
var (
recentFetchesMu sync.Mutex
recentFetchesDirResolver = GetFFmpegDir
)
func recentFetchesFilePath() (string, error) {
baseDir, err := recentFetchesDirResolver()
if err != nil {
return "", err
}
if err := os.MkdirAll(baseDir, 0o755); err != nil {
return "", err
}
return filepath.Join(baseDir, recentFetchesFileName), nil
}
func LoadRecentFetches() ([]RecentFetchItem, error) {
recentFetchesMu.Lock()
defer recentFetchesMu.Unlock()
filePath, err := recentFetchesFilePath()
if err != nil {
return nil, err
}
data, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return []RecentFetchItem{}, nil
}
return nil, err
}
if strings.TrimSpace(string(data)) == "" {
return []RecentFetchItem{}, nil
}
var items []RecentFetchItem
if err := json.Unmarshal(data, &items); err != nil {
return nil, err
}
if items == nil {
return []RecentFetchItem{}, nil
}
return items, nil
}
func SaveRecentFetches(items []RecentFetchItem) error {
recentFetchesMu.Lock()
defer recentFetchesMu.Unlock()
filePath, err := recentFetchesFilePath()
if err != nil {
return err
}
if items == nil {
items = []RecentFetchItem{}
}
data, err := json.MarshalIndent(items, "", " ")
if err != nil {
return err
}
return os.WriteFile(filePath, data, 0o644)
}
+44 -21
View File
@@ -5,7 +5,7 @@ import { Search, X, ArrowUp } from "lucide-react";
import { TooltipProvider } from "@/components/ui/tooltip";
import { getSettings, getSettingsWithDefaults, loadSettings, saveSettings, applyThemeMode, applyFont, updateSettings } from "@/lib/settings";
import { applyTheme } from "@/lib/themes";
import { OpenFolder, CheckFFmpegInstalled, DownloadFFmpeg, GetBrewPath, InstallFFmpegWithBrew } from "../wailsjs/go/main/App";
import { OpenFolder, CheckFFmpegInstalled, DownloadFFmpeg, GetBrewPath, GetRecentFetches, InstallFFmpegWithBrew, SaveRecentFetches } from "../wailsjs/go/main/App";
import { EventsOn, EventsOff, Quit } from "../wailsjs/runtime/runtime";
import { toastWithSound as toast } from "@/lib/toast-with-sound";
import { TitleBar } from "@/components/TitleBar";
@@ -104,6 +104,25 @@ function dedupeHistoryItems(items: HistoryItem[]): HistoryItem[] {
}
return deduped;
}
function sortHistoryItems(items: HistoryItem[]): HistoryItem[] {
return [...items].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
}
function normalizeHistoryItems(items: HistoryItem[]): HistoryItem[] {
return dedupeHistoryItems(sortHistoryItems(items)).slice(0, MAX_HISTORY);
}
function parseStoredHistory(value: string | null): HistoryItem[] {
if (!value) {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
}
catch (err) {
console.error("Failed to parse stored history:", err);
return [];
}
}
function App() {
const [currentPage, setCurrentPage] = useState<PageType>("main");
const [spotifyUrl, setSpotifyUrl] = useState("");
@@ -182,7 +201,7 @@ function App() {
mediaQuery.addEventListener("change", handleChange);
checkForUpdates();
ensureApiStatusCheckStarted();
loadHistory();
void loadHistory();
const handleScroll = () => {
setShowScrollTop(window.scrollY > 300);
};
@@ -232,19 +251,29 @@ function App() {
console.error("Failed to check for updates:", err);
}
};
const loadHistory = () => {
const persistRecentHistory = useCallback(async (history: HistoryItem[]) => {
try {
const saved = localStorage.getItem(HISTORY_KEY);
if (saved) {
const deduped = dedupeHistoryItems(JSON.parse(saved));
setFetchHistory(deduped);
localStorage.setItem(HISTORY_KEY, JSON.stringify(deduped));
}
await SaveRecentFetches(JSON.stringify(history));
}
catch (err) {
console.error("Failed to save recent fetches:", err);
}
}, []);
const loadHistory = useCallback(async () => {
try {
const saved = parseStoredHistory(localStorage.getItem(HISTORY_KEY));
const persisted = parseStoredHistory(await GetRecentFetches());
const normalized = normalizeHistoryItems([...persisted, ...saved]);
setFetchHistory(normalized);
await persistRecentHistory(normalized);
}
catch (err) {
console.error("Failed to load history:", err);
}
};
finally {
localStorage.removeItem(HISTORY_KEY);
}
}, [persistRecentHistory]);
const handleInstallFFmpeg = async (useBrew: boolean = false) => {
setIsInstallingFFmpeg(true);
setFfmpegInstallProgress(0);
@@ -283,14 +312,6 @@ function App() {
setFfmpegInstallStatus("");
}
};
const saveHistory = (history: HistoryItem[]) => {
try {
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
}
catch (err) {
console.error("Failed to save history:", err);
}
};
const addToHistory = (item: Omit<HistoryItem, "id" | "timestamp">) => {
setFetchHistory((prev) => {
const normalizedUrl = normalizeHistoryURL(item.url);
@@ -302,15 +323,17 @@ function App() {
id: crypto.randomUUID(),
timestamp: Date.now(),
};
const updated = [newItem, ...filtered].slice(0, MAX_HISTORY);
saveHistory(updated);
const updated = normalizeHistoryItems([newItem, ...filtered]);
void persistRecentHistory(updated);
return updated;
});
};
const removeFromHistory = (id: string) => {
setFetchHistory((prev) => {
if (!prev.some((h) => h.id === id))
return prev;
const updated = prev.filter((h) => h.id !== id);
saveHistory(updated);
void persistRecentHistory(updated);
return updated;
});
};