3.3.0: lo que entra nuevo ya entra ordenado
Las cuatro funciones que eligió Munka tras la 3.2.0: - Nueva pieza: crea «NN- Título» en su mes con el número que toca y sus subcarpetas; si la pieza ya existe, lo dice. Se deshace mientras siga vacía. - Bandeja de entrada: lo que se deje en _Entrada se reparte por mes, día de rodaje y cámara. «Traer material» copia una tarjeta comprobando cada archivo (se vuelve a leer y se compara la huella), conserva la fecha del rodaje y no toca el origen. - Versiones del mismo clip: dos clips de cámara que se llaman igual, duran lo mismo y tienen la misma forma son el mismo clip; se queda el de más imagen y el otro va a Duplicados. - Orden automático, apagado de fábrica: una tarea del Programador de tareas de Windows lanza «automatico», que lee todas las carpetas y hace solo lo seguro. No toca una carpeta que no se pueda leer entera ni una cuyo plan mueva más de 3.000 archivos, y si el programa ya está abierto se lo pide a él. Desinstalar quita la tarea. De paso: una ruta que empieza por guion bajo ya no pierde el guion al ir a la papelera. Pruebas: prueba_orden.py 24, probar.py 19, prueba_funcional.py 29, prueba_ventana.py 18 desde el código y 17 con el paquete. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Este commit está contenido en:
@@ -78,7 +78,9 @@ if FF:
|
||||
with socket.socket() as s_:
|
||||
s_.bind(('127.0.0.1', 0))
|
||||
PUERTO = s_.getsockname()[1]
|
||||
env = dict(os.environ, ORGANIZADOR_PUERTO=str(PUERTO), PYTHONIOENCODING='utf-8')
|
||||
ENSAYO = os.path.join(TMP, 'equipo')
|
||||
os.makedirs(ENSAYO)
|
||||
env = dict(os.environ, ORGANIZADOR_PUERTO=str(PUERTO), PYTHONIOENCODING='utf-8', ORGANILAB_ENSAYO=ENSAYO)
|
||||
log = open(os.path.join(TMP, 'servidor.log'), 'w', encoding='utf-8')
|
||||
proc = subprocess.Popen([sys.executable, 'organizador.py', '--sin-navegador'], cwd=APP, env=env, stdout=log, stderr=subprocess.STDOUT)
|
||||
BASE = f'http://127.0.0.1:{PUERTO}'
|
||||
@@ -380,6 +382,84 @@ def p_asistente():
|
||||
return 'pasos + «hacer las N de una vez» terminan sin error'
|
||||
|
||||
|
||||
def p_pieza():
|
||||
hoy = time.localtime()
|
||||
v = get('/api/pieza/previa' + q(raiz=RAIZ, titulo='Reel de prueba', anio=hoy.tm_year, mes=hoy.tm_mon))
|
||||
assert v['rel'].endswith('Reel de prueba') and not v['existe'], v
|
||||
assert not os.path.exists(v['ruta']), 'la vista previa creó la carpeta'
|
||||
c, r = post('/api/pieza', {'raiz': RAIZ, 'titulo': 'Reel de prueba', 'anio': hoy.tm_year, 'mes': hoy.tm_mon})
|
||||
assert c == 200 and r.get('ok'), r
|
||||
assert sorted(os.listdir(r['ruta'])) == sorted(r['subcarpetas']) and len(r['subcarpetas']) >= 5, os.listdir(r['ruta'])
|
||||
c2, r2 = post('/api/pieza', {'raiz': RAIZ, 'titulo': 'reel de prueba', 'anio': hoy.tm_year, 'mes': hoy.tm_mon})
|
||||
assert c2 == 400 and 'ya existe' in r2.get('error', ''), (c2, r2)
|
||||
vac = [x['rel'] for x in get('/api/vacias' + q(raiz=RAIZ))['carpetas']]
|
||||
assert not any('Reel de prueba' in x for x in vac), 'ofrece borrar una pieza recién creada: %s' % vac
|
||||
c3, r3 = post('/api/deshacer', {'lote': r['lote']})
|
||||
assert c3 == 200 and not r3['errores'] and not os.path.exists(r['ruta']), r3
|
||||
return 'crea «%s» con %d subcarpetas, no repite, y se deshace' % (r['nombre'], len(r['subcarpetas']))
|
||||
|
||||
|
||||
def p_entrada():
|
||||
tarjeta = os.path.join(TMP, 'tarjeta')
|
||||
clip = os.path.join(tarjeta, 'DCIM', '100MEDIA', 'DJI_0007.MP4')
|
||||
os.makedirs(os.path.dirname(clip))
|
||||
with open(clip, 'wb') as fh:
|
||||
fh.write(os.urandom(700_000))
|
||||
t = time.mktime(time.struct_time((2026, 8, 10, 12, 0, 0, 0, 0, -1)))
|
||||
os.utime(clip, (t, t))
|
||||
c, p = post('/api/entrada/plan', {'raiz': RAIZ, 'origen': tarjeta})
|
||||
assert c == 200 and p['n'] == 1 and p['bytes'] == 700_000, p
|
||||
c, mal = post('/api/entrada/plan', {'raiz': RAIZ, 'origen': os.path.join(RAIZ, '2026')})
|
||||
assert c == 400 and mal.get('error'), mal
|
||||
c, r = post('/api/entrada/traer', {'raiz': RAIZ, 'origen': tarjeta})
|
||||
assert c == 200 and r.get('lanzado'), r
|
||||
assert esperar(lambda: get('/api/estado')['traer'].get('estado') not in ('copiando', 'ordenando'), 120)
|
||||
tr = get('/api/estado')['traer']
|
||||
assert tr['estado'] == 'listo' and tr['copiados'] == 1 and not tr['errores_n'], tr
|
||||
final = os.path.join(RAIZ, '2026', '08- Agosto', '00_Grabacion', 'DJI', 'DJI_0007.MP4')
|
||||
assert os.path.isfile(final), 'no llegó a su sitio: %s' % [x for x in arbol() if 'dji' in x]
|
||||
assert os.path.getsize(final) == 700_000 and abs(os.path.getmtime(final) - t) < 2, 'la copia no conserva peso o fecha'
|
||||
assert os.path.isfile(clip), 'tocó la tarjeta'
|
||||
e = get('/api/entrada' + q(raiz=RAIZ))
|
||||
assert e['n'] == 0 and e['existe'], e
|
||||
# y lo que se deja a mano en la bandeja sale en la lista y se reparte con su botón
|
||||
suelto = os.path.join(RAIZ, '_Entrada', 'C0099.MP4')
|
||||
with open(suelto, 'wb') as fh:
|
||||
fh.write(os.urandom(4096))
|
||||
os.utime(suelto, (t, t))
|
||||
a = get('/api/carpeta/acciones' + q(raiz=RAIZ))
|
||||
assert any(x['clave'] == 'entrada' and x['estado'] == 'toca' for x in a['acciones']), [x['clave'] for x in a['acciones']]
|
||||
c, o = post('/api/entrada/ordenar', {'raiz': RAIZ})
|
||||
assert c == 200 and o['movimientos'] >= 1 and not o['errores'] and o['quedan'] == 0, o
|
||||
assert os.path.isfile(os.path.join(RAIZ, '2026', '08- Agosto', '00_Grabacion', 'SONY', 'C0099.MP4')), [x for x in arbol() if 'c0099' in x]
|
||||
return 'trae de una tarjeta con copia comprobada y la reparte; lo dejado a mano en la bandeja también'
|
||||
|
||||
|
||||
def p_automatico():
|
||||
c, r = post('/api/automatico/config', {'activo': True, 'hora': '03:15'})
|
||||
assert c == 200 and r['activo'] and r['hora'] == '03:15', r
|
||||
tarea = json.load(open(os.path.join(ENSAYO, 'tarea.json'), encoding='utf-8'))
|
||||
assert tarea['activo'] and tarea['orden'].endswith(' automatico'), tarea
|
||||
c, mal = post('/api/automatico/config', {'activo': True, 'hora': 'a las tres'})
|
||||
assert c == 400, mal
|
||||
c, r = post('/api/automatico/ahora', {})
|
||||
assert c == 200 and r.get('lanzado'), r
|
||||
assert esperar(lambda: not get('/api/estado')['automatico'].get('activo'), 180)
|
||||
u = get('/api/automatico')['ultimo']
|
||||
assert u and len(u['carpetas']) == 1 and u['carpetas'][0]['estado'] in ('al dia', 'con dudas'), u
|
||||
assert not u['carpetas'][0]['errores'], u['carpetas'][0]
|
||||
# lo que lanza la tarea de Windows cuando el programa ya está abierto: se lo pide a él y se va, para
|
||||
# que no haya dos procesos moviendo lo mismo
|
||||
r = subprocess.run([sys.executable, 'organizador.py', 'automatico'], cwd=APP, env=env, timeout=120)
|
||||
assert r.returncode == 0, 'la orden «automatico» salió con %s' % r.returncode
|
||||
assert esperar(lambda: not get('/api/estado')['automatico'].get('activo'), 180)
|
||||
u2 = get('/api/automatico')['ultimo']
|
||||
assert u2['origen'] == 'programado' and u2['ts'] > u['ts'], u2
|
||||
c, r = post('/api/automatico/config', {'activo': False, 'hora': '03:15'})
|
||||
assert c == 200 and not r['activo'], r
|
||||
return 'se enciende (tarea de Windows en ensayo), hace una pasada y deja el resumen (%s); con el programa abierto, la tarea se lo pide a él' % u['carpetas'][0]['estado']
|
||||
|
||||
|
||||
def p_soltar_y_buscar():
|
||||
s = get('/api/soltado' + q(nombre='nada'))
|
||||
assert s['ruta'] is None
|
||||
@@ -424,6 +504,7 @@ for nombre, fn in (('Interfaz sin nada externo', p_estatico), ('Registrar carpet
|
||||
('Motores de conversión', p_motores), ('Leer clips (ffprobe)', p_analizar),
|
||||
('Convertir, verificar, reemplazar', p_convertir), ('Control de la cola', p_cola_control),
|
||||
('Respaldar', p_respaldo), ('Asistente / hacer de una vez', p_asistente),
|
||||
('Pieza nueva', p_pieza), ('Bandeja de entrada y tarjeta', p_entrada), ('Orden automático', p_automatico),
|
||||
('Arrastrar y buscar carpeta', p_soltar_y_buscar), ('Actualizaciones', p_actualizacion_offline),
|
||||
('Clientes, reglas', p_clientes_config), ('Quitar de la lista', p_quitar)):
|
||||
prueba(nombre, fn)
|
||||
|
||||
Referencia en una nueva incidencia
Block a user