gates / gates (push) Successful in 6m30s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
48 lines
2.2 KiB
Python
48 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
"""where.py — S4: where every media file sits, per drive. Run INSIDE the guest (run_in_guest.py where.py).
|
|
|
|
For each enrolled drive under /mnt/felhom-drives/*/userdata/media: every regular file (hidden temp folders included)
|
|
with its size, link count and inode, grouped by top folder (tv, movies, torrents, transmission, ...). Then the pairs
|
|
that are ONE file (same device + inode = a hardlink: no extra copy) and the library files that are NOT linked to any
|
|
download (link count 1 = a copy, or a download already removed). Stdlib only; reads, never writes.
|
|
"""
|
|
import collections, glob, os
|
|
|
|
ROOTS = sorted(glob.glob("/mnt/felhom-drives/*/userdata/media"))
|
|
by_ino = collections.defaultdict(list)
|
|
total = 0
|
|
for root in ROOTS:
|
|
drive = root.split("/")[3]
|
|
print(f"== drive {drive} ({root})")
|
|
for top in sorted(os.listdir(root)):
|
|
rows = []
|
|
for dp, dn, fn in os.walk(os.path.join(root, top)):
|
|
for f in fn:
|
|
p = os.path.join(dp, f)
|
|
try:
|
|
s = os.lstat(p)
|
|
except OSError as e:
|
|
rows.append(f" ?? {p}: {e}")
|
|
continue
|
|
if not os.path.isfile(p) or os.path.islink(p):
|
|
continue
|
|
total += 1
|
|
by_ino[(s.st_dev, s.st_ino)].append((drive, top, p, s.st_size, s.st_nlink))
|
|
rows.append(f" {s.st_size/1e6:10.1f} MB links={s.st_nlink} inode={s.st_ino:<10} {os.path.relpath(p, root)}")
|
|
if rows:
|
|
print(f" -- {top}/ ({len(rows)} file(s))")
|
|
print("\n".join(rows))
|
|
print(f"\n== {total} file(s) on {len(ROOTS)} drive(s)")
|
|
print("== ONE FILE, TWO NAMES (hardlink — no extra copy):")
|
|
n = 0
|
|
for k, v in by_ino.items():
|
|
if len(v) > 1:
|
|
n += 1
|
|
print(f" drive {v[0][0]} inode {k[1]} ({v[0][3]/1e6:.1f} MB): " + " <-> ".join(f"{x[1]}/…/{os.path.basename(x[2])}" for x in v))
|
|
print(f" {n} pair(s)")
|
|
print("== LIBRARY FILES WITH LINK COUNT 1 (a copy, or its download is gone):")
|
|
for k, v in by_ino.items():
|
|
for d, top, p, size, nl in v:
|
|
if top in ("tv", "movies") and nl == 1:
|
|
print(f" drive {d}: {os.path.relpath(p, '/mnt/felhom-drives')} ({size/1e6:.1f} MB)")
|