Add rm -r for folders, and make ls list one folder level

rm -r FOLDER deletes a folder and everything under it, records and
objects, so the space is freed. It lists what will go (the first 20
names, then a count) with the total size and asks on the terminal; no
answer or no terminal deletes nothing. -f skips the question and makes a
missing name a no-op, -rf and -fr combine them, and rm -r / is refused.
Objects still in use are worked out once per delete, not once per file.

ls now shows one level, like ls(1): sub-folders once with a trailing
slash, their total size and newest date, then the files, with names
relative to the folder. list keeps the recursive listing of full names
that scripts pipe through cut -c31-, and ls -r is the same.

Docs, man pages, the guide (new rm-r screenshot) and README follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andy Dixon
2026-10-02 18:15:28 +01:00
co-authored by Claude Opus 5.5
parent 730877823e
commit 0d45ecea59
22 changed files with 476 additions and 57 deletions
+47 -1
View File
@@ -537,7 +537,53 @@ func (s *store) delete(name string) error {
if err != nil {
return err
}
return s.remove(cat, name, true)
return s.removeAll(cat, []string{name}, func(string) {})
}
// under returns the names in cat at or beneath the folder prefix, sorted.
func under(cat map[string][]*record, prefix string) []string {
var names []string
for name := range cat {
if name == prefix || strings.HasPrefix(name, prefix+"/") {
names = append(names, name)
}
}
sort.Strings(names)
return names
}
// removeAll deletes every record for names and the objects they hold, then
// drops them from cat, calling done after each name. Like remove, but it
// works out once which objects other records still use, so deleting a large
// folder stays linear.
func (s *store) removeAll(cat map[string][]*record, names []string, done func(name string)) error {
gone := map[string]bool{}
for _, name := range names {
gone[name] = true
}
inUse := map[string]bool{}
for name, rs := range cat {
if !gone[name] {
for _, r := range rs {
inUse[r.Seed] = true
}
}
}
for _, name := range names {
for _, r := range cat[name] {
if err := s.removeFile(recordsDir, r.id); err != nil {
return err
}
if !inUse[r.Seed] {
if err := s.dropObjects(r.Seed, r.Size); err != nil {
return err
}
}
}
delete(cat, name)
done(name)
}
return nil
}
func (s *store) rename(oldName, newName string) error {