Files
SpotiFLAC/backend/history.go
T
afkarxyz f13359df7f v7.1.2
2026-03-25 21:06:45 +07:00

328 lines
6.6 KiB
Go

package backend
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"time"
bolt "go.etcd.io/bbolt"
)
type HistoryItem struct {
ID string `json:"id"`
SpotifyID string `json:"spotify_id"`
Title string `json:"title"`
Artists string `json:"artists"`
Album string `json:"album"`
DurationStr string `json:"duration_str"`
CoverURL string `json:"cover_url"`
Quality string `json:"quality"`
Format string `json:"format"`
Path string `json:"path"`
Source string `json:"source"`
Timestamp int64 `json:"timestamp"`
}
var historyDB *bolt.DB
const (
historyBucket = "DownloadHistory"
maxHistory = 10000
)
func InitHistoryDB(appName string) error {
appDir, err := GetFFmpegDir()
if err != nil {
return err
}
if _, err := os.Stat(appDir); os.IsNotExist(err) {
os.MkdirAll(appDir, 0755)
}
dbPath := filepath.Join(appDir, "history.db")
db, err := bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
return err
}
err = db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(historyBucket))
return err
})
if err != nil {
db.Close()
return err
}
historyDB = db
return nil
}
func CloseHistoryDB() {
if historyDB != nil {
historyDB.Close()
}
}
func AddHistoryItem(item HistoryItem, appName string) error {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return err
}
}
return historyDB.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte(historyBucket))
if err != nil {
return err
}
id, _ := b.NextSequence()
item.ID = fmt.Sprintf("%d-%d", time.Now().UnixNano(), id)
item.Timestamp = time.Now().Unix()
buf, err := json.Marshal(item)
if err != nil {
return err
}
if b.Stats().KeyN >= maxHistory {
c := b.Cursor()
toDelete := maxHistory / 20
if toDelete < 1 {
toDelete = 1
}
count := 0
for k, _ := c.First(); k != nil && count < toDelete; k, _ = c.Next() {
if err := b.Delete(k); err != nil {
return err
}
count++
}
}
return b.Put([]byte(item.ID), buf)
})
}
func GetHistoryItems(appName string) ([]HistoryItem, error) {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return nil, err
}
}
var items []HistoryItem
err := historyDB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(historyBucket))
if b == nil {
return nil
}
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var item HistoryItem
if err := json.Unmarshal(v, &item); err == nil {
items = append(items, item)
}
}
return nil
})
sort.Slice(items, func(i, j int) bool {
return items[i].Timestamp > items[j].Timestamp
})
return items, err
}
func ClearHistory(appName string) error {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return err
}
}
return historyDB.Update(func(tx *bolt.Tx) error {
return tx.DeleteBucket([]byte(historyBucket))
})
}
type FetchHistoryItem struct {
ID string `json:"id"`
URL string `json:"url"`
Type string `json:"type"`
Name string `json:"name"`
Info string `json:"info"`
Image string `json:"image"`
Data string `json:"data"`
Timestamp int64 `json:"timestamp"`
}
const (
fetchHistoryBucket = "FetchHistory"
)
func AddFetchHistoryItem(item FetchHistoryItem, appName string) error {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return err
}
}
return historyDB.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte(fetchHistoryBucket))
if err != nil {
return err
}
id, _ := b.NextSequence()
if item.URL != "" {
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var existing FetchHistoryItem
if err := json.Unmarshal(v, &existing); err == nil {
if existing.URL == item.URL && existing.Type == item.Type {
if err := b.Delete(k); err != nil {
return err
}
}
}
}
}
item.ID = fmt.Sprintf("%d-%d", time.Now().UnixNano(), id)
item.Timestamp = time.Now().Unix()
buf, err := json.Marshal(item)
if err != nil {
return err
}
if b.Stats().KeyN >= maxHistory {
c := b.Cursor()
toDelete := maxHistory / 20
if toDelete < 1 {
toDelete = 1
}
count := 0
for k, _ := c.First(); k != nil && count < toDelete; k, _ = c.Next() {
if err := b.Delete(k); err != nil {
return err
}
count++
}
}
return b.Put([]byte(item.ID), buf)
})
}
func GetFetchHistoryItems(appName string) ([]FetchHistoryItem, error) {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return nil, err
}
}
var items []FetchHistoryItem
err := historyDB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(fetchHistoryBucket))
if b == nil {
return nil
}
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var item FetchHistoryItem
if err := json.Unmarshal(v, &item); err == nil {
items = append(items, item)
}
}
return nil
})
sort.Slice(items, func(i, j int) bool {
return items[i].Timestamp > items[j].Timestamp
})
return items, err
}
func ClearFetchHistory(appName string) error {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return err
}
}
return historyDB.Update(func(tx *bolt.Tx) error {
return tx.DeleteBucket([]byte(fetchHistoryBucket))
})
}
func ClearFetchHistoryByType(itemType string, appName string) error {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return err
}
}
return historyDB.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(fetchHistoryBucket))
if b == nil {
return nil
}
var keysToDelete [][]byte
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var item FetchHistoryItem
if err := json.Unmarshal(v, &item); err == nil {
if item.Type == itemType {
keysToDelete = append(keysToDelete, k)
}
}
}
for _, k := range keysToDelete {
if err := b.Delete(k); err != nil {
return err
}
}
return nil
})
}
func DeleteHistoryItem(id string, appName string) error {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return err
}
}
return historyDB.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(historyBucket))
if b == nil {
return nil
}
return b.Delete([]byte(id))
})
}
func DeleteFetchHistoryItem(id string, appName string) error {
if historyDB == nil {
if err := InitHistoryDB(appName); err != nil {
return err
}
}
return historyDB.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(fetchHistoryBucket))
if b == nil {
return nil
}
return b.Delete([]byte(id))
})
}