Poubelle par utilisateur (backend) : dossier virtuel .poubelle, restauration, suppression définitive, purge automatique (trashDays)
This commit is contained in:
1 parent
8c29eb35e7
commit
8a1417055b
9 files changed
+634
-37
No files matched your search
+136
-9
@@ -22,16 +22,20 @@ import (
|
||||
// DATA_DIR/.users/<id>/favoris liens symboliques relatifs vers des schémas
|
||||
//
|
||||
// Côté API, l'arborescence commune garde ses chemins. L'espace personnel est
|
||||
// exposé sous le préfixe virtuel ".perso" et les favoris sous ".favoris" : un
|
||||
// préfixe caché ne peut pas entrer en conflit avec un dossier réel. Un favori
|
||||
// pointe vers le schéma d'origine ; il suit ses renommages et déplacements et
|
||||
// disparaît quand le schéma est supprimé.
|
||||
// exposé sous le préfixe virtuel ".perso", les favoris sous ".favoris" et la
|
||||
// poubelle sous ".poubelle" : un préfixe caché ne peut pas entrer en conflit
|
||||
// avec un dossier réel. Un favori pointe vers le schéma d'origine ; il suit ses
|
||||
// renommages et déplacements et disparaît quand le schéma est supprimé. La
|
||||
// poubelle réunit les corbeilles de l'arborescence commune (".poubelle/c-<id>",
|
||||
// visibles de tous) et de l'espace personnel (".poubelle/p-<id>").
|
||||
|
||||
const (
|
||||
// PersoPrefix est la racine virtuelle de l'espace personnel.
|
||||
PersoPrefix = ".perso"
|
||||
// FavPrefix est le dossier virtuel des favoris.
|
||||
FavPrefix = ".favoris"
|
||||
// TrashPrefix est le dossier virtuel de la poubelle.
|
||||
TrashPrefix = ".poubelle"
|
||||
|
||||
usersDir = ".users"
|
||||
homeName = "home"
|
||||
@@ -76,6 +80,9 @@ type Spaces struct {
|
||||
homes map[string]*Store
|
||||
|
||||
favMu sync.Mutex // dossiers de favoris (tous utilisateurs)
|
||||
|
||||
purgeMu sync.Mutex
|
||||
lastPurge time.Time // dernière purge automatique de la corbeille
|
||||
}
|
||||
|
||||
// NewSpaces crée le gestionnaire d'espaces autour du Store principal.
|
||||
@@ -309,6 +316,8 @@ func (u *Space) route(p string) (*Store, string, error) {
|
||||
return u.home, c[len(PersoPrefix)+1:], nil
|
||||
case c == FavPrefix || strings.HasPrefix(c, FavPrefix+"/"):
|
||||
return nil, "", fmt.Errorf("%w : le dossier Favoris ne contient que des liens", ErrInvalidPath)
|
||||
case c == TrashPrefix || strings.HasPrefix(c, TrashPrefix+"/"):
|
||||
return nil, "", fmt.Errorf("%w : élément de la poubelle (à restaurer d'abord)", ErrInvalidPath)
|
||||
}
|
||||
return u.sp.Main, p, nil
|
||||
}
|
||||
@@ -446,10 +455,14 @@ func isRoot(rel string) bool {
|
||||
}
|
||||
|
||||
// List renvoie le contenu d'un dossier. La racine commune commence par les
|
||||
// dossiers virtuels Favoris et Personnel.
|
||||
// dossiers virtuels Favoris, Personnel et Poubelle.
|
||||
func (u *Space) List(p string) ([]Entry, error) {
|
||||
if cleanVirtual(p) == FavPrefix {
|
||||
u.sp.autoPurge()
|
||||
switch cleanVirtual(p) {
|
||||
case FavPrefix:
|
||||
return u.Favorites()
|
||||
case TrashPrefix:
|
||||
return u.Trash()
|
||||
}
|
||||
st, rel, err := u.route(p)
|
||||
if err != nil {
|
||||
@@ -464,7 +477,9 @@ func (u *Space) List(p string) ([]Entry, error) {
|
||||
if st == u.sp.Main && isRoot(rel) {
|
||||
out = append(out,
|
||||
Entry{Name: "Favoris", Path: FavPrefix, IsDir: true, Special: "favoris"},
|
||||
Entry{Name: "Personnel", Path: PersoPrefix, IsDir: true, Special: "perso"})
|
||||
Entry{Name: "Personnel", Path: PersoPrefix, IsDir: true, Special: "perso"},
|
||||
Entry{Name: "Poubelle", Path: TrashPrefix, IsDir: true, Special: "poubelle",
|
||||
Count: u.sp.Main.TrashCount() + u.home.TrashCount()})
|
||||
}
|
||||
for _, e := range es {
|
||||
out = append(out, u.entry(st, e, favs))
|
||||
@@ -747,11 +762,18 @@ func (u *Space) Copy(from, to string) (Entry, error) {
|
||||
}
|
||||
|
||||
// Delete met un élément à la corbeille de son espace ; les favoris qui le
|
||||
// visent disparaissent. Sur .favoris/<lien>, seul le lien est retiré.
|
||||
// visent disparaissent. Sur .favoris/<lien>, seul le lien est retiré. Dans la
|
||||
// poubelle, l'élément est supprimé définitivement (.poubelle seul : la vide).
|
||||
func (u *Space) Delete(p string, recursive bool) error {
|
||||
if _, ok := favLinkName(p); ok {
|
||||
return u.SetFavorite([]string{p}, false)
|
||||
}
|
||||
if cleanVirtual(p) == TrashPrefix {
|
||||
return u.EmptyTrash()
|
||||
}
|
||||
if st, id, ok := u.trashItem(p); ok {
|
||||
return st.DropTrash(id)
|
||||
}
|
||||
st, rel, err := u.route(p)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -760,7 +782,7 @@ func (u *Space) Delete(p string, recursive bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := st.Delete(rel, recursive); err != nil {
|
||||
if err := st.deleteAs(rel, recursive, u.id); err != nil {
|
||||
return err
|
||||
}
|
||||
u.sp.unlink(abs)
|
||||
@@ -942,3 +964,108 @@ func (s *Store) putShares(c string, add map[string]shareDisk) {
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// --- Poubelle ---------------------------------------------------------------------
|
||||
|
||||
// trashItem traduit .poubelle/c-<id> ou .poubelle/p-<id> en Store et identifiant.
|
||||
func (u *Space) trashItem(p string) (*Store, string, bool) {
|
||||
key, ok := strings.CutPrefix(cleanVirtual(p), TrashPrefix+"/")
|
||||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(key, "c-"):
|
||||
return u.sp.Main, key[2:], true
|
||||
case strings.HasPrefix(key, "p-"):
|
||||
return u.home, key[2:], true
|
||||
}
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
// Trash liste la poubelle : suppressions de l'arborescence commune et de
|
||||
// l'espace personnel, les plus récentes en premier. ModTime est la date de
|
||||
// suppression, Origin le chemin d'origine.
|
||||
func (u *Space) Trash() ([]Entry, error) {
|
||||
out := []Entry{}
|
||||
for _, st := range []*Store{u.sp.Main, u.home} {
|
||||
items, err := st.TrashItems()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix := "c-"
|
||||
if st == u.home {
|
||||
prefix = "p-"
|
||||
}
|
||||
for _, it := range items {
|
||||
out = append(out, Entry{
|
||||
Name: it.Name,
|
||||
Path: TrashPrefix + "/" + prefix + it.ID,
|
||||
IsDir: it.IsDir,
|
||||
Size: it.Size,
|
||||
ModTime: it.Deleted,
|
||||
Origin: u.virt(st, it.Path),
|
||||
By: it.By,
|
||||
})
|
||||
}
|
||||
}
|
||||
sort.SliceStable(out, func(i, j int) bool { return out[i].ModTime.After(out[j].ModTime) })
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// RestoreTrash remet des éléments de la poubelle à leur emplacement d'origine ;
|
||||
// To est le chemin restauré (renommé si la place était prise).
|
||||
func (u *Space) RestoreTrash(paths []string) []MoveResult {
|
||||
out := make([]MoveResult, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
r := MoveResult{From: p}
|
||||
st, id, ok := u.trashItem(p)
|
||||
if !ok {
|
||||
r.Error = ErrInvalidPath.Error()
|
||||
} else if e, err := st.RestoreTrash(id); err != nil {
|
||||
r.Error = err.Error()
|
||||
} else {
|
||||
r.To = u.virt(st, e.Path)
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// EmptyTrash vide la poubelle (corbeille commune comprise).
|
||||
func (u *Space) EmptyTrash() error {
|
||||
return errors.Join(u.sp.Main.EmptyTrash(), u.home.EmptyTrash())
|
||||
}
|
||||
|
||||
// purgeEvery espace les purges automatiques déclenchées par List.
|
||||
const purgeEvery = 10 * time.Minute
|
||||
|
||||
func (sp *Spaces) autoPurge() {
|
||||
sp.purgeMu.Lock()
|
||||
due := time.Since(sp.lastPurge) >= purgeEvery
|
||||
if due {
|
||||
sp.lastPurge = time.Now()
|
||||
}
|
||||
sp.purgeMu.Unlock()
|
||||
if due {
|
||||
_ = sp.PurgeTrash()
|
||||
}
|
||||
}
|
||||
|
||||
// PurgeTrash supprime définitivement, dans toutes les corbeilles, les éléments
|
||||
// plus anciens que Settings.TrashDays (rien si 0).
|
||||
func (sp *Spaces) PurgeTrash() error {
|
||||
st, err := sp.Main.ReadSettings()
|
||||
if err != nil || st.TrashDays <= 0 {
|
||||
return err
|
||||
}
|
||||
maxAge := time.Duration(st.TrashDays) * 24 * time.Hour
|
||||
errs := []error{sp.Main.PurgeTrashOlder(maxAge)}
|
||||
for _, id := range sp.users() {
|
||||
h, err := sp.home(id)
|
||||
if err == nil {
|
||||
err = h.PurgeTrashOlder(maxAge)
|
||||
}
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
Reference in new issue
Block a user