143 lines
3.7 KiB
Go
143 lines
3.7 KiB
Go
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
||
|
|
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||
|
|
|
||
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"sort"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// LireTaille lit une taille : un nombre, suivi ou non d'une unité binaire.
|
||
|
|
//
|
||
|
|
// « 50G », « 50Gio », « 50GiB », « 1.5 G », « 500M », « 2T » ou un nombre
|
||
|
|
// d'octets. Les unités valent des puissances de 1024 : c'est ce qu'affiche
|
||
|
|
// HumanBytes, et un plafond lu autrement que la taille affichée ne se
|
||
|
|
// comparerait pas à elle.
|
||
|
|
func LireTaille(s string) (int64, error) {
|
||
|
|
brut := strings.TrimSpace(s)
|
||
|
|
if brut == "" {
|
||
|
|
return 0, fmt.Errorf("%s", T("taille vide"))
|
||
|
|
}
|
||
|
|
t := strings.ToUpper(strings.ReplaceAll(brut, " ", ""))
|
||
|
|
for _, suffixe := range []string{"IB", "IO", "B", "O"} {
|
||
|
|
if strings.HasSuffix(t, suffixe) && len(t) > len(suffixe) {
|
||
|
|
t = strings.TrimSuffix(t, suffixe)
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
facteur := int64(1)
|
||
|
|
if n := len(t); n > 0 {
|
||
|
|
switch t[n-1] {
|
||
|
|
case 'K':
|
||
|
|
facteur = 1 << 10
|
||
|
|
case 'M':
|
||
|
|
facteur = 1 << 20
|
||
|
|
case 'G':
|
||
|
|
facteur = 1 << 30
|
||
|
|
case 'T':
|
||
|
|
facteur = 1 << 40
|
||
|
|
}
|
||
|
|
if facteur > 1 {
|
||
|
|
t = t[:n-1]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
v, err := strconv.ParseFloat(t, 64)
|
||
|
|
if err != nil || v <= 0 {
|
||
|
|
return 0, fmt.Errorf(T("taille illisible %q : essayer 50G, 500M, 2T"), brut)
|
||
|
|
}
|
||
|
|
return int64(v * float64(facteur)), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// BilanPlafond dit ce qu'une purge au plafond a effacé, ou effacerait.
|
||
|
|
type BilanPlafond struct {
|
||
|
|
Objets int
|
||
|
|
OctetsObjets int64
|
||
|
|
Depots int
|
||
|
|
OctetsDepots int64
|
||
|
|
Avant int64
|
||
|
|
Apres int64
|
||
|
|
}
|
||
|
|
|
||
|
|
// candidat est un objet du magasin ou un dépôt en miroir, avec son dernier
|
||
|
|
// usage : ce qui décide de l'ordre d'éviction.
|
||
|
|
type candidat struct {
|
||
|
|
chemin string
|
||
|
|
octets int64
|
||
|
|
usage time.Time
|
||
|
|
depot bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// PurgerJusqua efface le moins récemment servi, objets et dépôts confondus,
|
||
|
|
// jusqu'à ce que l'ensemble tienne sous le plafond. aBlanc compte sans rien
|
||
|
|
// effacer.
|
||
|
|
//
|
||
|
|
// Confondus, parce que le plafond porte sur le disque et que les deux s'y
|
||
|
|
// disputent la place : un dépôt oublié depuis des mois pèse plus que mille
|
||
|
|
// paquets servis récemment. L'usage d'un objet est la date de son corps, remise à
|
||
|
|
// jour à chaque service ; celle d'un dépôt, son dernier rafraîchissement.
|
||
|
|
// Rien ne part quand le cache tient déjà sous le plafond.
|
||
|
|
func PurgerJusqua(store *Store, miroir *GitMirror, plafond int64, aBlanc bool) (BilanPlafond, error) {
|
||
|
|
var b BilanPlafond
|
||
|
|
var tous []candidat
|
||
|
|
if store != nil && store.Dir != "" {
|
||
|
|
filepath.Walk(store.Dir, func(p string, info os.FileInfo, err error) error {
|
||
|
|
if err != nil || info == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
if horsCasier(store.Dir, p, info) {
|
||
|
|
return filepath.SkipDir
|
||
|
|
}
|
||
|
|
if info.IsDir() || filepath.Ext(p) != ".body" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
tous = append(tous, candidat{p, info.Size(), info.ModTime(), false})
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
}
|
||
|
|
for _, d := range miroir.Depots() {
|
||
|
|
tous = append(tous, candidat{d.Chemin, d.Octets, d.Maj, true})
|
||
|
|
}
|
||
|
|
for _, c := range tous {
|
||
|
|
b.Avant += c.octets
|
||
|
|
}
|
||
|
|
b.Apres = b.Avant
|
||
|
|
if b.Avant <= plafond {
|
||
|
|
return b, nil
|
||
|
|
}
|
||
|
|
sort.SliceStable(tous, func(i, j int) bool { return tous[i].usage.Before(tous[j].usage) })
|
||
|
|
for _, c := range tous {
|
||
|
|
if b.Apres <= plafond {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
if !aBlanc {
|
||
|
|
if c.depot {
|
||
|
|
if err := miroir.Retirer(c.chemin); err != nil {
|
||
|
|
return b, err
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if err := os.Remove(c.chemin); err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
// Le méta part AVEC le corps : orphelin, il ferait croire à
|
||
|
|
// une copie présente.
|
||
|
|
os.Remove(strings.TrimSuffix(c.chemin, ".body") + ".meta")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
b.Apres -= c.octets
|
||
|
|
if c.depot {
|
||
|
|
b.Depots++
|
||
|
|
b.OctetsDepots += c.octets
|
||
|
|
} else {
|
||
|
|
b.Objets++
|
||
|
|
b.OctetsObjets += c.octets
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return b, nil
|
||
|
|
}
|