1.8.1: fase 2 de la auditoría
- Plan.marcar_colisiones: dos movimientos que acabarían en el mismo archivo se detectan antes de aplicar, se dejan sin marcar y se explican. - agregar(): los grupos de archivos también comprueban si el destino ya tiene un archivo con ese nombre (40 primeros, para no saturar la red). - actualizar_inventario: las filas de carpetas se reescriben con su ruta nueva en vez de borrarse, y se conserva la marca de basura/proxy heredada, para que aplicar por pasos no deje los siguientes con 0 archivos y sin fecha. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Este commit está contenido en:
@@ -34,7 +34,7 @@ import actualizador
|
||||
asistente.O = sys.modules[__name__]
|
||||
actualizador.O = sys.modules[__name__]
|
||||
|
||||
VERSION = "1.8.0"
|
||||
VERSION = "1.8.1"
|
||||
|
||||
CONGELADO = getattr(sys, "frozen", False)
|
||||
SIN_VENTANA = getattr(subprocess, "CREATE_NO_WINDOW", 0) # ffmpeg/ffprobe/net sin abrir consolas negras # empaquetado como .exe con PyInstaller
|
||||
@@ -618,7 +618,8 @@ class Plan:
|
||||
return r
|
||||
return None
|
||||
|
||||
def agregar(self, origen, destino, cubeta, accion, motivo, confianza, archivos=None, n=None, bytes_=None, marcado=None):
|
||||
def agregar(self, origen, destino, cubeta, accion, motivo, confianza, archivos=None, n=None, bytes_=None,
|
||||
marcado=None, kw_destinos=None):
|
||||
destino = os.path.normpath(destino)
|
||||
if archivos is None and os.path.normpath(origen).lower() == destino.lower():
|
||||
return
|
||||
@@ -630,7 +631,19 @@ class Plan:
|
||||
fila = self.con.execute("SELECT n, bytes FROM carpetas WHERE ruta = ?", (os.path.normpath(origen),)).fetchone()
|
||||
n, bytes_ = (fila["n"], fila["bytes"]) if fila else (0, 0)
|
||||
aviso_colision = ""
|
||||
if archivos is None and accion != "renombrar" and os.path.isfile(destino):
|
||||
if archivos is not None and accion != "renombrar":
|
||||
# también para los grupos de archivos: si el destino ya tiene un archivo con ese nombre,
|
||||
# mover chocaría. Antes solo se comprobaba en las filas de carpeta.
|
||||
especiales = kw_destinos or {}
|
||||
# solo los primeros: cada comprobación es una consulta al disco de red, y para avisar
|
||||
# basta con encontrar uno
|
||||
choques = [os.path.basename(a[0]) for a in archivos[:40]
|
||||
if os.path.isfile(especiales.get(a[0]) or os.path.join(destino, os.path.basename(a[0])))]
|
||||
if choques:
|
||||
aviso_colision = (f" · OJO: en el destino ya hay archivos con el mismo nombre "
|
||||
f"(«{choques[0]}»): esos no se moverían")
|
||||
confianza, marcado = "baja", False
|
||||
elif archivos is None and accion != "renombrar" and os.path.isfile(destino):
|
||||
aviso_colision = " · ya existe un archivo con ese nombre en el destino: no se moverá"
|
||||
confianza, marcado = "baja", False
|
||||
elif archivos is None and os.path.isdir(destino) and os.path.isdir(origen):
|
||||
@@ -675,10 +688,46 @@ class Plan:
|
||||
# primero las filas de archivos sueltos (rutas exactas), luego carpetas de más profunda a menos,
|
||||
# para que un renombrado de mes no deje rutas viejas en filas que aún no se aplicaron
|
||||
self.filas.sort(key=lambda f: (0 if f["archivos"] else 1, -len(os.path.normpath(f["origen"]).split(os.sep)), f["origen"].lower()))
|
||||
self.marcar_colisiones()
|
||||
for i, f in enumerate(self.filas, 1):
|
||||
f["id"] = i
|
||||
return {"raiz": self.raiz, "cliente": self.cliente, "tipo": self.tipo, "filas": self.filas, "avisos": self.avisos}
|
||||
|
||||
def marcar_colisiones(self):
|
||||
"""Dos movimientos del mismo plan que acaban en el mismo archivo. La comprobación de `agregar`
|
||||
solo mira el disco tal como está ahora, así que no ve lo que van a hacer las demás filas.
|
||||
Caso típico: «Grabacion mañana» y «Grabacion tarde» del mismo mes van las dos a
|
||||
00_Grabacion/SONY, y las dos tarjetas traen un C0001.MP4. Antes chocaban al aplicar."""
|
||||
destinos = {} # archivo destino (en minúsculas) -> primera fila que lo reclama
|
||||
carpetas = {} # carpeta destino -> primera fila que la reclama (solo renombrados)
|
||||
for f in self.filas:
|
||||
if f["archivos"]:
|
||||
especiales = f.get("destinos_archivo") or {}
|
||||
for a in f["archivos"]:
|
||||
d = (especiales.get(a) or os.path.join(f["destino"], os.path.basename(a))).lower()
|
||||
otra = destinos.get(d)
|
||||
if otra is not None and otra is not f:
|
||||
self.avisar_colision(f, otra, os.path.basename(d))
|
||||
break
|
||||
destinos.setdefault(d, f)
|
||||
elif f["accion"] == "renombrar":
|
||||
d = f["destino"].lower()
|
||||
otra = carpetas.get(d)
|
||||
if otra is not None:
|
||||
self.avisar_colision(f, otra, os.path.basename(f["destino"]))
|
||||
else:
|
||||
carpetas[d] = f
|
||||
|
||||
def avisar_colision(self, fila, otra, que):
|
||||
aviso = f" · OJO: «{otra['origen_rel']}» también va a «{fila['destino_rel']}» y las dos traen «{que}»"
|
||||
if aviso not in fila["motivo"]:
|
||||
fila["motivo"] += aviso
|
||||
fila["confianza"] = "baja"
|
||||
fila["marcado"] = False
|
||||
self.avisos.append(f"«{fila['origen_rel']}» y «{otra['origen_rel']}» acabarían las dos en "
|
||||
f"«{fila['destino_rel']}» con archivos del mismo nombre ({que}). Se dejan sin marcar: "
|
||||
f"cámbiale el destino a una de las dos, o aplícalas por separado.")
|
||||
|
||||
def entradas(self, ruta):
|
||||
try:
|
||||
with os.scandir(ruta) as it:
|
||||
@@ -1171,20 +1220,43 @@ def aplicar(raiz, filas):
|
||||
|
||||
|
||||
def actualizar_inventario(movidos):
|
||||
"""refleja en la base de datos los movimientos hechos, sin re-escanear"""
|
||||
"""Refleja en la base de datos los movimientos hechos, sin re-escanear.
|
||||
|
||||
Las filas de `carpetas` se reescriben con su ruta nueva en vez de borrarse: la interfaz recalcula el
|
||||
plan tras cada paso SIN volver a escanear, y si el subárbol movido desaparecía del inventario, los
|
||||
pasos siguientes salían con «0 archivos · 0 B» y sin fecha. La marca de basura/proxy heredada de una
|
||||
carpeta (por ejemplo lo que cuelga de «Adobe Premiere Pro Auto-Save») también se conserva: antes se
|
||||
recalculaba desde cero y se perdía."""
|
||||
con = db()
|
||||
marcas = [os.sep + p + os.sep for p in R.PAPELERAS]
|
||||
with con:
|
||||
for de, a in movidos:
|
||||
en_papelera = 1 if any(m in a for m in marcas) else 0
|
||||
con.execute("UPDATE archivos SET ruta = ?, dir = ?, nombre = ?, es_basura = CASE WHEN ? = 1 THEN 1 WHEN rol = 'basura' THEN 1 ELSE 0 END WHERE ruta = ?",
|
||||
con.execute("UPDATE archivos SET ruta = ?, dir = ?, nombre = ?, "
|
||||
"es_basura = CASE WHEN ? = 1 OR es_basura = 1 OR rol = 'basura' THEN 1 ELSE 0 END WHERE ruta = ?",
|
||||
(a, os.path.dirname(a), os.path.basename(a), en_papelera, de))
|
||||
pref = de + os.sep
|
||||
for r in con.execute("SELECT ruta FROM archivos WHERE ruta LIKE ? ESCAPE '!'", (esc_like(pref) + "%",)).fetchall():
|
||||
nueva = a + r["ruta"][len(de):]
|
||||
con.execute("UPDATE archivos SET ruta = ?, dir = ?, es_basura = CASE WHEN ? = 1 THEN 1 WHEN rol = 'basura' THEN 1 ELSE 0 END WHERE ruta = ?",
|
||||
con.execute("UPDATE archivos SET ruta = ?, dir = ?, "
|
||||
"es_basura = CASE WHEN ? = 1 OR es_basura = 1 OR rol = 'basura' THEN 1 ELSE 0 END WHERE ruta = ?",
|
||||
(nueva, os.path.dirname(nueva), en_papelera, r["ruta"]))
|
||||
# las carpetas siguen al material: se mueven de ruta, no se borran
|
||||
viejas = con.execute("SELECT * FROM carpetas WHERE ruta = ? OR ruta LIKE ? ESCAPE '!'",
|
||||
(de, esc_like(pref) + "%")).fetchall()
|
||||
con.execute("DELETE FROM carpetas WHERE ruta = ? OR ruta LIKE ? ESCAPE '!'", (de, esc_like(pref) + "%"))
|
||||
if os.path.isdir(a):
|
||||
for v in viejas:
|
||||
nueva = a if v["ruta"] == de else a + v["ruta"][len(de):]
|
||||
fila = dict(v)
|
||||
fila["ruta"] = nueva
|
||||
fila["nombre"] = os.path.basename(nueva)
|
||||
if en_papelera:
|
||||
fila["es_basura"] = 1
|
||||
con.execute("INSERT OR REPLACE INTO carpetas VALUES (?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(fila["ruta"], fila["raiz"], fila["nombre"], fila["n"], fila["bytes"],
|
||||
fila["anio_moda"], fila["mes_moda"], fila["frac_moda"], fila["dias_rango"],
|
||||
fila["es_proxy"], fila["es_basura"]))
|
||||
con.close()
|
||||
|
||||
|
||||
|
||||
Referencia en una nueva incidencia
Block a user