controller.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. '''
  2. this file is part of "El Botadero"
  3. copyright 2018 Rodrigo Garcia <strysg@riseup.net>
  4. AGPL liberated.
  5. '''
  6. # El objetivo de este archivo es albergar la logica de gestion de archivos y comportamiento general de la aplicacion
  7. import os
  8. from datetime import datetime as dt
  9. from werkzeug.utils import secure_filename
  10. from flask import g
  11. from .shared import globalParams, gr
  12. from . import utils as u
  13. from .database.models import Archivo, HtmlPage
  14. log = g.log
  15. def descargaPermitida(cat, nombreArchivo):
  16. if '..' in nombreArchivo or nombreArchivo.startswith(os.path.sep):
  17. return False
  18. if cat not in u.categorias() and cat != 'Misc':
  19. return False
  20. return True
  21. def tienePassword(nombreArchivo):
  22. regDb = Archivo.query.filter_by(name=nombreArchivo).first()
  23. if regDb is not None:
  24. return len(regDb.hashedPassword) > 0
  25. else:
  26. return False
  27. def descargarArchivo(cat, nombreArchivo, password=''):
  28. ''' Ayuda a descargar un archivo, retornando la ruta del archivo
  29. - cat (string): Categoria del archivo.
  30. - nombreArchivo (string): Nombre del archivo.
  31. - password (string): (opcional) Si el archivo esta protegido requiere
  32. un password
  33. :return (string) pathfile o en caso de error un diccionario de la forma
  34. {
  35. 'tipoError': 2,
  36. 'mensaje': 'Contraseña incorrecta'
  37. }
  38. '''
  39. # obteniendo ruta del archivo
  40. pathf = u.descargarArchivo(cat, nombreArchivo)
  41. if pathf is None:
  42. return {
  43. 'tipoError': 1,
  44. 'mensaje': 'Error buscando el archivo'
  45. }
  46. # comprobando password si es necesario
  47. if password != '':
  48. if u.comprobarPassword(pathf, password) is False:
  49. return {
  50. 'tipoError': 2,
  51. 'mensaje': 'Contraseña incorrecta'
  52. }
  53. # marcando para que se actualice el renderizado de la pagina
  54. marcarPaginaListaParaRenderizar(categoria=cat)
  55. return pathf
  56. def subirArchivo(cat, file, password=''):
  57. ''' Verifica el archivo siendo subido, lo guarda en el directorio de
  58. almacenamiento y lo registra en la BD. Tambien marca la categoria a la
  59. que pertenece el archivo para que se renderize el html de listado.
  60. :param cat: Categoria o subcarpeta donde se guarda el archivo.
  61. :param file: objeto instancia de 'FileStorage' (werkzeug) del archivo.
  62. :param password: Cadena con el password si es '' no se usa.
  63. :return: Si la subida es exitosa, retorna el registro en la base de
  64. datos recien creado. Si no, retorna un diccionario de la forma:
  65. { tipoError: (entero),
  66. mensaje: 'mensaje de error',
  67. redirect: 'link redireccion en caso de ya existir un archivo'
  68. }
  69. NOTA: En caso de subir exitosamente, la funcion que lo llama deberia
  70. llamar a sincronizarArchivo() para actualizar los registros.
  71. '''
  72. log.debug('^ subirArchivo(cat="{0}", file="{1}"'.format(cat, file))
  73. filename = secure_filename(file.filename)
  74. categoria = ''
  75. if cat != 'Misc':
  76. categoria = cat
  77. # comprobando existencia
  78. filepath = os.path.join(globalParams.uploadDirectory, categoria, filename)
  79. filepath = u.addRelativeFileName(filepath)
  80. # nombre del archivo
  81. if len(filename) < 1:
  82. log.debug('El archivo "{0}" se ha traducido en el nombre "{1}" que tiene un nombre válido'
  83. .format(file.filename, filename))
  84. return {
  85. 'tipoError': 5,
  86. 'mensaje': 'El archivo no tiene un nombre válido',
  87. 'redirect': categoria
  88. }
  89. try:
  90. f = open(filepath, 'r')
  91. f.close()
  92. log.debug('Archivo siendo subido ya existe: {0}'.format(filepath))
  93. return {
  94. 'tipoError': 1,
  95. 'mensaje': 'El archivo "' + filename + '" ya existen en ' + filepath,
  96. 'redirect': categoria
  97. }
  98. except IOError as E:
  99. pass
  100. # comprobando hash
  101. digestCheck = ''
  102. if globalParams.digestCheck:
  103. digestCheck = u.hashFileStorage(file,
  104. accelerateHash=globalParams.digestAccelerated)
  105. regDb = Archivo.query.filter_by(digestCheck=digestCheck).first()
  106. if regDb is not None:
  107. # existe un registro con el mismo digestCheck
  108. file.close()
  109. cat = u.categoriaArchivo(regDb.path)
  110. if cat == globalParams.uploadDirectory:
  111. cat = ''
  112. cat += '/'
  113. log.debug('Ya existe un archivo con el mismo digestCheck {0} encontrado {1}'
  114. .format(digestCheck, str(regDb)))
  115. return {
  116. 'tipoError': 2,
  117. 'mensaje': 'Ya existe un archivo con el mismo digestCheck ' + digestCheck + ' con nombre ' + regDb.name,
  118. 'redirect': categoria + regDb.name
  119. }
  120. # procediendo a registrar el archivo en BD
  121. file.seek(0, os.SEEK_END)
  122. fsize = file.tell()
  123. file.seek(0)
  124. remainingTime = u.tiempoBorradoArchivo(fsize)
  125. uploadedAtTime = dt.now()
  126. # comprobando espacio de almacenamiento disponible
  127. if gr['storageUsed'] == 0:
  128. u.actualizarEstadisticasGenerales()
  129. if gr['storageUsed'] + fsize > gr['storageTotal']:
  130. log.warning('No se cuenta con espacio de almacenamiento suficiente, requiere {0} se cuenta {1}'
  131. .format(str(fsize), str(gr['storageTotal'] - gr['storageUsed'])))
  132. return {
  133. 'tipoError': 3,
  134. 'mensaje': 'No se cuenta con espacio suficiente',
  135. 'redirect': categoria
  136. }
  137. # guardando en el sistema de archivos
  138. try:
  139. file.save(os.path.join(globalParams.uploadDirectory, categoria, filename))
  140. log.info('✓ Archivo guardado en sistema de archivos: {0}'
  141. .format(os.path.join(globalParams.uploadDirectory, categoria, filename)))
  142. except Exception as E:
  143. log.error('✕ Excepcion al guardar archivo %r en el sistema de archivos: {0}\n{1}'
  144. .format((filename, str(E))))
  145. return {
  146. 'tipoError': 4,
  147. 'mensaje': 'Error interno al guardar el archivo ' + filename,
  148. 'redirect': categoria
  149. }
  150. if len(Archivo.query.filter_by(name=filename).all()) > 1:
  151. log.warning('Ya existe un archivo en la BD con nombre: {0} '.format(filename))
  152. return {
  153. 'tipoError': 1,
  154. 'mensaje': 'Ya existe (BD) un archivo con nombre ' + filename,
  155. 'redirect': categoria
  156. }
  157. hashedPassword = ''
  158. if password != '':
  159. hashedPassword = u.hashPassword(password)
  160. # creando registro en la BD
  161. arch = Archivo.create(name=filename,
  162. path=filepath, size=fsize,
  163. extension=u.extensionArchivo(filename),
  164. digestCheck=digestCheck,
  165. digestAlgorithm=globalParams.digestAlgorithm,
  166. uploadedAtTime=uploadedAtTime,
  167. remainingTime=remainingTime,
  168. hashedPassword=hashedPassword)
  169. # sincronizarArchivos(['.gitkeep', '.gitkeep~', '#.gitkeep', '#.gitkeep#'])
  170. log.info('✓ Archivo registrado en BD {0} {1}'.format(str(arch), str(len(arch.hashedPassword))))
  171. return arch
  172. # definir una funcion para comprobar la lista de archivos y su tiempo de
  173. # borrado
  174. def comprobarTiempoBorradoListaArchivos(categoria, hdd=False):
  175. ''' Verifica si es necesario borrar archivos en los archivos dados en
  176. la carpeta (categoria) guradada en el almacen
  177. :param categoria: La carpeta (categoria) dentro el almacen donde se hace
  178. la busqueda.
  179. :param hdd: Hace que la busqueda se haga en el almacenamiento fisico (HDD tipicamente).
  180. :return borrados: Lista de archivos que se han borrado (directorios)
  181. '''
  182. # ajuste
  183. if categoria == 'Misc':
  184. categoria = globalParams.uploadDirectory
  185. lista = None
  186. if hdd:
  187. lista = u.listaDeArchivos(categoria)
  188. else:
  189. lista = u.listaDeArchivosEnBd(categoria)
  190. # print ('LISTA de archivos:::::::::::', str(lista))
  191. borrados = []
  192. for archivo in lista:
  193. tiempoBorrado = u.tiempoBorradoArchivo(archivo.size)
  194. edad = u.edadArchivo(archivo.path, archivo)
  195. log.info('archivo: {0} edad: {1} borrado max: {2}'
  196. .format(archivo.name, str(edad), str(tiempoBorrado)))
  197. if (tiempoBorrado < edad):
  198. r = u.borrarArchivo(archivo.path)
  199. log.warning(' xx Borrando archivo {0} = {1}'.format(archivo.name,r))
  200. borrados.append(archivo.path)
  201. return borrados
  202. def marcarPaginaListaParaRenderizar(categoria='Misc'):
  203. ''' Marca la pagina de la lista para renderizar de la categoria dada
  204. para que se vuelva a renderizar el template usando jinja2
  205. :param: True si se ha marcado correctamente, False en otro caso
  206. '''
  207. if categoria == globalParams.uploadDirectory or categoria == '':
  208. categoria = 'Misc' # ajuste por conveniencia
  209. # buscando el registro
  210. name = 'lista_archivos_' + categoria
  211. html_page = HtmlPage.query.filter_by(name=name).first()
  212. if html_page is not None:
  213. # modificando
  214. try:
  215. html_page.save(renderHtml=True)
  216. log.debug('marcado para generar html: {0} {1}'
  217. .format(html_page.name, str(html_page.renderHtml)))
  218. return True
  219. except Exception as E:
  220. log.error('Excepcion modificando html_page {0}'.format(name))
  221. return False
  222. return False
  223. def marcarTodasLasPaginasParaRenderizar():
  224. ''' Usa la funcion marcarPaginaListaParaRenderizar para marcar
  225. todas las paginas y que se vuelva a renderizar el template usando jinja2
  226. '''
  227. for cat in u.categorias():
  228. marcarPaginaListaParaRenderizar(categoria=cat)
  229. marcarPaginaListaParaRenderizar() # para categoria Misc
  230. def sincronizarArchivos(ignorar=[]):
  231. ''' Funcion encargada sincronizar y actualizar la BD segun los archivos
  232. que se encuentran en el directorio de subidas en el sistema de archivos.
  233. Lista los archivos en el directorio de subidas y los introduce en
  234. la base de datos si estos no estan registrados. Tambien borra los
  235. registros de archivos que se encuentran en la BD pero no en el sistema
  236. de archivos. Cuando detecta un cambio marca la pagina web necesaria para
  237. que se renderize.
  238. :param ignorar: Una lista con nombres de archivos a ignorar
  239. :return ([registrados],[borrados],[actualizados]): Retorna tres listas, segun registra nuevos archivos en BD, los borra o actualiza su tiempo restante.
  240. '''
  241. log.debug('** Sincronizando archivos **')
  242. log.debug('\nParametros\n {0}'.format(str(globalParams)))
  243. listaEnBd = u.listaDeArchivosEnBd()
  244. archivosEnBd = []
  245. for reg in listaEnBd:
  246. archivosEnBd.append(reg.path)
  247. # print('i ', reg.path)
  248. archivos = []
  249. listaLsArchivos = []
  250. registrados = []
  251. borrados = []
  252. actualizados = []
  253. # obteniendo lista de archivos en el sistema de archivos
  254. log.debug('# Misc')
  255. lista = u.listaDeArchivos()
  256. for archivo in lista:
  257. if u.nombreArchivo(archivo) not in ignorar:
  258. # estandarizando nombre
  259. log.debug(os.path.join(os.path.curdir + os.path.sep, archivo))
  260. archivos.append(os.path.join(os.path.curdir + os.path.sep, archivo))
  261. for cat in u.categorias():
  262. log.debug('# {0}'.format(cat))
  263. lista = u.listaDeArchivos(categoria=cat)
  264. for archivo in lista:
  265. if u.nombreArchivo(archivo) not in ignorar:
  266. # estandarizando nombre
  267. log.debug(os.path.join(os.path.curdir + os.path.sep, archivo))
  268. archivos.append(os.path.join(os.path.curdir + os.path.sep, archivo))
  269. # actualizando BD
  270. for archivo in archivos:
  271. # print('o ', archivo, archivo not in archivosEnBd)
  272. if archivo in ignorar:
  273. continue
  274. if archivo not in archivosEnBd:
  275. log.debug ('(+) {0} {1}'.format(str(archivo), u.categoriaArchivo(archivo)))
  276. arch = u.registrarArchivo(archivo)
  277. marcarTodasLasPaginasParaRenderizar()
  278. #marcarPaginaListaParaRenderizar(categoria=u.categoriaArchivo(archivo))
  279. registrados.append(arch)
  280. else:
  281. if u.archivoDebeBorrarsePorTiempo(archivo):
  282. log.debug('(-) {0} {1}'.format(str(archivo), u.categoriaArchivo(archivo)))
  283. r = u.borrarArchivo(archivo) # del sistema de archivos y BD
  284. borrados.append(archivo)
  285. log.debug(' ✗ Registro de archivo borrado {0} = {1}'
  286. .format(archivo, r))
  287. marcarTodasLasPaginasParaRenderizar()
  288. # marcarPaginaListaParaRenderizar(categoria=u.categoriaArchivo(archivo))
  289. else:
  290. if u.actualizarTiempoRestanteArchivo(archivo):
  291. log.debug('(+-) {0} {1}'.format(str(archivo), u.categoriaArchivo(archivo)))
  292. actualizados.append(archivo)
  293. marcarTodasLasPaginasParaRenderizar()
  294. # marcarPaginaListaParaRenderizar(categoria=u.categoriaArchivo(archivo))
  295. for reg in archivosEnBd:
  296. # caso de que un archivo se borro del sistema de archivos
  297. if reg not in archivos:
  298. log.debug('(bd -) {0} {1}'.format(str(reg), u.categoriaArchivo(reg)))
  299. r = u.borrarRegistroArchivoEnBd(u.nombreArchivo(reg))
  300. borrados.append(reg)
  301. marcarTodasLasPaginasParaRenderizar()
  302. # marcarPaginaListaParaRenderizar(categoria=u.categoriaArchivo(reg))
  303. log.debug('\nsincronización completa')
  304. return registrados, borrados, actualizados