Estadisticas_Archivos.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. '''
  2. Botadero, una aplicacion para compartir archivos libremente.
  3. Copyright (C) 2016 Rodrigo Garcia <strysg@riseup.net>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU Affero General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Affero General Public License for more details.
  12. You should have received a copy of the GNU Affero General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. '''
  15. import pickle
  16. from botadero.Parametros_Servidor import *
  17. from botadero.datos_archivo import *
  18. '''
  19. Nota acerca de 'descincronizacion' cuando esta en produccion
  20. Si no se usa self.CargarDesdeArchivo() , self.GuardarCambiosEnArchivo()
  21. El objeto EstadisticaArchivos toma valores distintos a cada actualizacion
  22. en el server web (por ejemplo NGINX) , como si el objeto estuviese siendo
  23. compartido por varios procesos o hilos.
  24. TODO: Averiguar como manejar un unico objeto en RAM comun a todos los hilos
  25. para no tener que cargarlo y guardarlo en disco duro como se hace
  26. actualmente.
  27. '''
  28. class EstadisticaArchivos:
  29. def __init__(self, NombreArchivoConfig, DebugLevel):
  30. self.Parametros = ParametrosServidor(NombreArchivoConfig \
  31. , DebugLevel)
  32. self.AlmacenDisponible = 0
  33. self.PorcentajeAlmacenDisponible = 0
  34. self.NumArchivos = 0
  35. self.PilaArchivos = []
  36. self.Inicializar()
  37. def GetIndexArchivo(self, Nombre_con_ruta):
  38. i = 0
  39. for pa in self.PilaArchivos:
  40. if Nombre_con_ruta == os.path.join(self.Parametros.UploadFolder, \
  41. pa.categoria, pa.Nombre):
  42. return i
  43. i += 1
  44. return -1
  45. def GetDatosArchivo(self, Nombre):
  46. '''
  47. Devuelve el objeto datos_archivo correspondiente al Nombre de archivo dado
  48. NOTA: Se supone que ningun otro archivo tiene el mismo nombre.
  49. '''
  50. i = 0
  51. for pa in self.PilaArchivos:
  52. if Nombre == pa.Nombre:
  53. return self.PilaArchivos[i]
  54. i+=1
  55. return None
  56. def ExisteNombre(self, Nombre_con_ruta):
  57. '''
  58. Comprueba si el nombre de un archivo en la ruta dada ya existe
  59. en los registros de archivos
  60. '''
  61. for da in self.PilaArchivos:
  62. if Nombre_con_ruta == os.path.join(self.Parametros.UploadFolder\
  63. ,da.categoria, da.Nombre):
  64. return True
  65. return False
  66. def ExisteNombreEstricto(self, Nombre):
  67. '''
  68. Comprueba si solamente el nombre del archivo ya existe en los
  69. registros de archivos
  70. '''
  71. for da in self.PilaArchivos:
  72. if Nombre == da.Nombre:
  73. return True
  74. return False
  75. # TODO: analizar necesidad de esta funcion
  76. def ExisteArchivo(self, Nombre_con_ruta, sha1sum):
  77. '''
  78. Comprueba si existe el archivo de un archivo en la ruta dada existe
  79. ademas comprueba si el sha1sum corresponde a otro archivo,
  80. en los registros de archivos
  81. '''
  82. if self.ExisteNombre(Nombre_con_ruta):
  83. if self.GetDatosArchivo(Nombre_con_ruta).sha1sum == \
  84. sha1sum:
  85. return True
  86. else:
  87. return False
  88. return False
  89. def ExisteArchivoEstricto(self, Nombre, sha1sum):
  90. '''
  91. Comprueba si existe el nombre del archivo y si el sha1sum,
  92. corresponde a otro archivo en los registros de archivos.
  93. '''
  94. if self.ExisteNombreEstricto(Nombre):
  95. if self.GetDatosArchivo(Nombre).sha1sum == \
  96. sha1sum:
  97. return True
  98. else:
  99. return False
  100. return False
  101. def ExisteArchivoConTamanyo(self, tam):
  102. for da in self.PilaArchivos:
  103. if tam == da.Tam:
  104. return True
  105. return False
  106. def ListaArchivosCategoria(self, categoria):
  107. '''
  108. Devuelve una lista con los nombres de los archivos
  109. del registro de archivos que coincidan con la categoria dada
  110. '''
  111. lista_nombres = []
  112. for da in self.PilaArchivos:
  113. if da.categoria == categoria:
  114. lista_nombres.append(da.Nombre)
  115. #print "cat: %s |" %categoria , "la= %s" %lista_nombres
  116. return lista_nombres
  117. def IncrementarNumDescargas(self, Nombre_con_ruta):
  118. self.CargarDesdeArchivo()
  119. i = self.GetIndexArchivo(Nombre_con_ruta)
  120. if i != -1:
  121. self.PilaArchivos[i].NumDescargas += 1
  122. print "[REG] - Download: Count increased to %d" \
  123. % self.PilaArchivos[i].NumDescargas,\
  124. " of file %s" % Nombre_con_ruta
  125. self.GuardarCambiosEnArchivo()
  126. else:
  127. print "[REG] - Error: File %s" % Nombre_con_ruta,\
  128. " could not be found!"
  129. def AgregarArchivo(self, Nombre_con_ruta, sha1sum, file):
  130. '''
  131. Agrega un nuevo archivo comprobando condiciones
  132. de tamanyo, espacio disponible, nombre, sha1sum.
  133. Si pasa estas pruebas el archivo se guarda en el disco
  134. y se crea un nuevo registro
  135. '''
  136. self.CargarDesdeArchivo()
  137. # comprobacion de espacio disponible
  138. fsize = len(file.read())
  139. file.seek(0) # restuarando puntero
  140. if (self.Parametros.TotalStorage - self.AlmacenDisponible)\
  141. + fsize > self.Parametros.TotalStorage:
  142. print "[STORAGE] - Error non free space: filesize %d"\
  143. % fsize, " only %d(ts) " % self.AlmacenDisponible,\
  144. " of space available."
  145. file.close()
  146. return 1
  147. # comprobacion de nombre
  148. elif self.ExisteNombreEstricto(nombre_archivo(Nombre_con_ruta)): # comprobacion de no duplicados
  149. print "[STORAGE] - Warning: File with name: %s "\
  150. %nombre_archivo(Nombre_con_ruta), \
  151. " exists, not uploaded."
  152. file.close()
  153. return 2
  154. # comprobacion de tamanyo
  155. elif self.ExisteArchivoConTamanyo(fsize):
  156. # comprobacion de sha1sum
  157. if self.ExisteArchivoEstricto(nombre_archivo(Nombre_con_ruta), sha1sum):
  158. print "[STORAGE] - Warning: sha1sum %s exists," % sha1sum, \
  159. " not uploaded."
  160. file.close()
  161. return 3
  162. else:
  163. file.save(Nombre_con_ruta)
  164. file.close()
  165. # agrega el nuevo registro a las estadisticas
  166. da = DatosDeArchivo()
  167. da.auto_init(Nombre_con_ruta, sha1sum)
  168. self.PilaArchivos.append(da)
  169. self.GuardarCambiosEnArchivo()
  170. self.ComprobarTiempoArchivos()
  171. print '[REG] - New: File %(na)s size %(sz)d'\
  172. % {'na': self.PilaArchivos[-1].categoria +'/'+ self.PilaArchivos[-1].Nombre ,\
  173. 'sz': self.PilaArchivos[-1].Tam},\
  174. ' created at', self.PilaArchivos[-1].FechaYHoraDeSubida
  175. self.GuardarCambiosEnArchivo()
  176. return 0
  177. def BorrarArchivo(self, Nombre):
  178. '''
  179. Borra un archivo dado el nombre, del disco y del registro de archivos.
  180. '''
  181. if self.ExisteNombreEstricto(Nombre):
  182. da = self.GetDatosArchivo(Nombre)
  183. cat = da.categoria
  184. pathf = os.path.abspath(self.Parametros.UploadFolder)
  185. # borra el archivo de disco
  186. try:
  187. os.remove(os.path.join(pathf, cat, Nombre))
  188. except:
  189. print "[DEL] File %s Not Found" %Nombre
  190. # borra el registro del archivo de la pila de registros
  191. del self.PilaArchivos[self.PilaArchivos.index(self.GetDatosArchivo(Nombre))]
  192. self.GuardarCambiosEnArchivo()
  193. def Inicializar(self):
  194. '''
  195. Lee el objeto serializado en disco y si existe, copia sus configs
  196. en si mismo y retorna True.
  197. LLama tambien a la funcion ComprobrarTiempoArchivos()
  198. '''
  199. print "[REG] - Initializating..."
  200. self.Actualizar() # carga nuevos archivos si no estaban en el registro
  201. def Actualizar(self):
  202. '''
  203. comprueba las lista de archivos viendo si existen en el registro,
  204. crea nuevos registros si hay archivos nuevos. Llama a
  205. ComprobarTiempoArchivos()
  206. '''
  207. self.CargarDesdeArchivo()
  208. ow = os.walk(self.Parametros.UploadFolder)
  209. '''
  210. NOTA: os.walk(top, topdown=True, onerror=None, followlinks=False)
  211. Generate the file names in a directory tree by walking the tree either top-down or bottom-up. For each directory in the tree rooted at directory top (including top itself), it yields a 3-tuple (dirpath, dirnames, filenames).'''
  212. p , directorios , archs = ow.next()
  213. '''NOTA: Solo se listan los archivos en una profundidad de directorios = 1 '''
  214. directorios += [p]
  215. for direc in directorios:
  216. if direc != self.Parametros.UploadFolder:
  217. direc = os.path.join(self.Parametros.UploadFolder, direc)
  218. nombres_con_rutas = self.ArchOrdenadosFechaSubida(direc)
  219. for nomb_con_ruta in nombres_con_rutas:
  220. if self.ExisteNombre(nomb_con_ruta) == False:
  221. '''actualizar registro del nuevo archivo
  222. este caso se deberia dar cuando se copia manualmente
  223. archivos en la carpeta `UploadFolder' '''
  224. dt_arch = DatosDeArchivo()
  225. dt_arch.auto_init(nomb_con_ruta)
  226. # agrega nuevo registro
  227. self.PilaArchivos.append(dt_arch)
  228. print '[REG] - New: File %(na)s size %(sz)d'\
  229. % {'na': self.PilaArchivos[-1].categoria+'/'+self.PilaArchivos[-1].Nombre, \
  230. 'sz': self.PilaArchivos[-1].Tam},\
  231. 'created at', self.PilaArchivos[-1].FechaYHoraDeSubida
  232. self.GuardarCambiosEnArchivo()
  233. else:
  234. pass
  235. '''comprobacion si un archivo lo ha borrado un administrador
  236. esto se detecta cuando existe un archivo en el registro pero
  237. no se encuentra en el directorio despues de listar archivos'''
  238. categoria = ""
  239. if len(direc.split(os.path.sep)) > 1: # ej: almacen/Imagenes
  240. categoria = direc.split(os.path.sep)[-1]
  241. for nombre_arch in self.ListaArchivosCategoria(categoria):
  242. # se hace join por que `nombres_con_rutas' es del formato:
  243. # almacen/categoria/nombre_arch | almacen/nombre_arch
  244. if os.path.join(self.Parametros.UploadFolder, categoria, nombre_arch)\
  245. not in nombres_con_rutas:
  246. print '[REG] - Delete: File %(ca)s/%(na)s , because it was manually deleted!'\
  247. % {'ca':categoria , 'na': nombre_arch }
  248. # borra el registro del archivo de la pila de registros
  249. del self.PilaArchivos[self.PilaArchivos.index(self.GetDatosArchivo(nombre_arch))]
  250. self.GuardarCambiosEnArchivo()
  251. self.ComprobarTiempoArchivos()
  252. # determinacion de otros parametros estadisticos
  253. tam_total =0
  254. for da in self.PilaArchivos:
  255. tam_total = tam_total + da.Tam
  256. self.AlmacenDisponible = self.Parametros.TotalStorage - tam_total
  257. self.PorcentajeAlmacenDisponible = 100 - (tam_total * 100)\
  258. / self.AlmacenDisponible
  259. self.NumArchivos = len(self.PilaArchivos)
  260. self.GuardarCambiosEnArchivo()
  261. print '[REG] - Updated.' # log
  262. #self.MostrarRegistros() # muy verboso
  263. def ComprobarTiempoArchivo(self, Nombre):
  264. '''Comprueba si el registro del Nombre de archivo ha sobrepasado
  265. el tiempo permitido.
  266. Calcula los dias restantes del archivo.
  267. Retorna True si se ha sobrepasado y False si no.
  268. '''
  269. da = self.GetDatosArchivo(Nombre)
  270. edad = da.edad()
  271. vt = 0
  272. tamanyo = da.Tam
  273. if tamanyo < self.Parametros.Size1:
  274. vt = self.Parametros.TimeToDel0
  275. elif tamanyo >= self.Parametros.Size1 and \
  276. tamanyo <= self.Parametros.Size2:
  277. vt = self.Parametros.TimeToDel1
  278. elif tamanyo > self.Parametros.Size2:
  279. vt = self.Parametros.TimeToDel2
  280. elif tamanyo >= self.Parametros.SizeMaxToUpload:
  281. vt = -1 # para borrar inmediatamente
  282. # marca si se ha excedido el tiempo
  283. if vt - edad < 0:
  284. return True
  285. else:
  286. da.DiasRestantes = vt - edad
  287. return False
  288. def ComprobarTiempoArchivos(self):
  289. '''Comprueba si uno o mas archivos han estado almacenados por mas
  290. dias de los especificados para su eliminacion.
  291. Los elimina automaticamente y los borra del registro, si no actualiza
  292. el registro de dias restantes'''
  293. self.CargarDesdeArchivo()
  294. archivos_a_borrar = []
  295. for da in self.PilaArchivos:
  296. if self.ComprobarTiempoArchivo(da.Nombre):
  297. archivos_a_borrar.append(da.Nombre)
  298. # borrado
  299. for na in archivos_a_borrar:
  300. # log
  301. print '[REG] - Delete: File %(na)s size %(sz)d'\
  302. % {'na': na , 'sz': self.GetDatosArchivo(na).Tam} , \
  303. 'created at: %s' %self.GetDatosArchivo(na).FechaYHoraDeSubida , \
  304. 'passed allowed time.'
  305. self.BorrarArchivo(na)
  306. self.GuardarCambiosEnArchivo()
  307. def ArchOrdenadosFechaSubida(self, ruta):
  308. '''
  309. Devuelve la lista de los nonbres de archivos (con su ruta)
  310. ordenados por fecha de subida (los mas nuevos al final)
  311. --> basdado en http://stackoverflow.com/questions/168409/how-do-you-get-a-directory-listing-sorted-by-creation-date-in-python?lq=1
  312. '''
  313. try:
  314. ow = os.walk(ruta)
  315. p,d,files=ow.next()
  316. except OSError:
  317. print "[REG] - Error: Can't os.walk() on %s except OSError." %ruta
  318. else:
  319. nombs_rutas = [os.path.join(ruta, f) for f in files]
  320. nombs_rutas.sort(key=lambda x: os.path.getmtime(x))
  321. return nombs_rutas
  322. def GuardarCambiosEnArchivo(self):
  323. '''
  324. Guarda todas las modificaciones hechas al registro en un archivo serializado
  325. en disco duro.
  326. '''
  327. try:
  328. Eaf = open('EstadisticaArchivos.pkl', 'wb')
  329. pickle.dump(self ,Eaf)
  330. Eaf.close()
  331. print '[REG] - Saved to file EstadisticaArchivos.pkl'
  332. return True
  333. except:
  334. print '[REG] - Error: Object file EstadisticaArchivos.pkl could not be writen.'
  335. return False
  336. def CargarDesdeArchivo(self):
  337. '''
  338. Carga el objeto con los datos guardados en el archivo serializado
  339. en disco duro
  340. '''
  341. try:
  342. Eaf = open('EstadisticaArchivos.pkl', 'rb')
  343. Ea = pickle.load(Eaf)
  344. # copia el objeto guardado
  345. self.PilaArchivos = Ea.PilaArchivos
  346. self.AlmacenDisponible = Ea.AlmacenDisponible
  347. self.PorcentajeAlmacenDisponible = Ea.PorcentajeAlmacenDisponible
  348. self.NumArchivos = self.NumArchivos
  349. Eaf.close()
  350. print '[REG] - Loaded: Data from object file '\
  351. , ' EstadisticaArchivos.pkl'
  352. self.Parametros.Reload_configs()
  353. return True
  354. except:
  355. print '[REG] - Warning: Not found object file '\
  356. , ' EstadisticaArchivos.pkl. Restarting, creating registers.'
  357. return False
  358. def MostrarRegistros(self):
  359. '''
  360. Mostrar todos los registros, para propositos de debug
  361. '''
  362. print '[REG] - ---------------------'
  363. print '[REG] - Showing all registers'
  364. print '[REG] - ---------------------'
  365. print 'Available: %s ' %self.AlmacenDisponible
  366. print 'Porcentaje Available: %s ' %self.PorcentajeAlmacenDisponible
  367. print 'Show: Number of files: %s ' %self.NumArchivos
  368. for pa in self.PilaArchivos:
  369. print 'File %(na)s size %(sz)d'\
  370. % {'na': '#'+pa.categoria+' '+pa.Nombre, \
  371. 'sz': pa.Tam},\
  372. 'created at', pa.FechaYHoraDeSubida
  373. print '---'