erplibre/script/qemu_cache/plafond.go

143 lines
3.7 KiB
Go
Raw Normal View History

// © 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
}