Python Library FS-Tools
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

__init__.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. """
  5. fstools (Filesystem Tools)
  6. ==========================
  7. **Author:**
  8. * Dirk Alders <sudo-dirk@mount-mockery.de>
  9. **Description:**
  10. This module supports functions and classes to handle files and paths
  11. **Submodules:**
  12. * :mod:`mmod.module.sub1`
  13. * :class:`mmod.module.sub2`
  14. * :func:`mmod.module.sub2`
  15. **Unittest:**
  16. See also the :download:`unittest <../../fstools/_testresults_/unittest.pdf>` documentation.
  17. """
  18. __DEPENDENCIES__ = []
  19. import glob
  20. import hashlib
  21. import logging
  22. from functools import partial
  23. import hmac
  24. import os
  25. import sys
  26. import time
  27. try:
  28. from config import APP_NAME as ROOT_LOGGER_NAME
  29. except ImportError:
  30. ROOT_LOGGER_NAME = 'root'
  31. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  32. __DESCRIPTION__ = """The Module {\\tt %s} is designed to help on all issues with files and folders.
  33. For more Information read the documentation.""" % __name__.replace('_', '\_')
  34. """The Module Description"""
  35. __INTERPRETER__ = (2, 3)
  36. """The Tested Interpreter-Versions"""
  37. def uid(pathname, max_staleness=3600):
  38. """
  39. Function returning a unique id for a given file or path.
  40. :param str pathname: File or Path name for generation of the uid.
  41. :param int max_staleness: If a file or path is older than that, we may consider
  42. it stale and return a different uid - this is a
  43. dirty trick to work around changes never being
  44. detected. Default is 3600 seconds, use None to
  45. disable this trickery. See below for more details.
  46. :returns: An object that changes value if the file changed,
  47. None is returned if there were problems accessing the file
  48. :rtype: str
  49. .. note:: Depending on the operating system capabilities and the way the
  50. file update is done, this function might return the same value
  51. even if the file has changed. It should be better than just
  52. using file's mtime though.
  53. max_staleness tries to avoid the worst for these cases.
  54. .. note:: If this function is used for a path, it will stat all pathes and files rekursively.
  55. Using just the file's mtime to determine if the file has changed is
  56. not reliable - if file updates happen faster than the file system's
  57. mtime granularity, then the modification is not detectable because
  58. the mtime is still the same.
  59. This function tries to improve by using not only the mtime, but also
  60. other metadata values like file size and inode to improve reliability.
  61. For the calculation of this value, we of course only want to use data
  62. that we can get rather fast, thus we use file metadata, not file data
  63. (file content).
  64. >>> print 'UID:', uid(__file__)
  65. UID: 16a65cc78e1344e596ef1c9536dab2193a402934
  66. """
  67. if os.path.isdir(pathname):
  68. pathlist = dirlist(pathname) + filelist(pathname)
  69. pathlist.sort()
  70. else:
  71. pathlist = [pathname]
  72. uid = []
  73. for element in pathlist:
  74. try:
  75. st = os.stat(element)
  76. except (IOError, OSError):
  77. uid.append(None) # for permanent errors on stat() this does not change, but
  78. # having a changing value would be pointless because if we
  79. # can't even stat the file, it is unlikely we can read it.
  80. else:
  81. fake_mtime = int(st.st_mtime)
  82. if not st.st_ino and max_staleness:
  83. # st_ino being 0 likely means that we run on a platform not
  84. # supporting it (e.g. win32) - thus we likely need this dirty
  85. # trick
  86. now = int(time.time())
  87. if now >= st.st_mtime + max_staleness:
  88. # keep same fake_mtime for each max_staleness interval
  89. fake_mtime = int(now / max_staleness) * max_staleness
  90. uid.append((
  91. st.st_mtime, # might have a rather rough granularity, e.g. 2s
  92. # on FAT, 1s on ext3 and might not change on fast
  93. # updates
  94. st.st_ino, # inode number (will change if the update is done
  95. # by e.g. renaming a temp file to the real file).
  96. # not supported on win32 (0 ever)
  97. st.st_size, # likely to change on many updates, but not
  98. # sufficient alone
  99. fake_mtime) # trick to workaround file system / platform
  100. # limitations causing permanent trouble
  101. )
  102. if sys.version_info < (3, 0):
  103. secret = ''
  104. return hmac.new(secret, repr(uid), hashlib.sha1).hexdigest()
  105. else:
  106. secret = b''
  107. return hmac.new(secret, bytes(repr(uid), 'latin-1'), hashlib.sha1).hexdigest()
  108. def uid_filelist(path='.', expression='*', rekursive=True):
  109. SHAhash = hashlib.md5()
  110. #
  111. fl = filelist(path, expression, rekursive)
  112. fl.sort()
  113. for f in fl:
  114. if sys.version_info < (3, 0):
  115. with open(f, 'rb') as fh:
  116. SHAhash.update(hashlib.md5(fh.read()).hexdigest())
  117. else:
  118. with open(f, mode='rb') as fh:
  119. d = hashlib.md5()
  120. for buf in iter(partial(fh.read, 128), b''):
  121. d.update(buf)
  122. SHAhash.update(d.hexdigest().encode())
  123. #
  124. return SHAhash.hexdigest()
  125. def filelist(path='.', expression='*', rekursive=True):
  126. """
  127. Function returning a list of files below a given path.
  128. :param str path: folder which is the basepath for searching files.
  129. :param str expression: expression to fit including shell-style wildcards.
  130. :param bool rekursive: search all subfolders if True.
  131. :returns: list of filenames including the pathe
  132. :rtype: list
  133. .. note:: The returned filenames could be relative pathes depending on argument path.
  134. >>> for filename in filelist(path='.', expression='*.py*', rekursive=True):
  135. ... print filename
  136. ./__init__.py
  137. ./__init__.pyc
  138. """
  139. li = list()
  140. if os.path.exists(path):
  141. logger.debug('FILELIST: path (%s) exists - looking for files to append', path)
  142. for filename in glob.glob(os.path.join(path, expression)):
  143. if os.path.isfile(filename):
  144. li.append(filename)
  145. for directory in os.listdir(path):
  146. directory = os.path.join(path, directory)
  147. if os.path.isdir(directory) and rekursive and not os.path.islink(directory):
  148. li.extend(filelist(directory, expression))
  149. else:
  150. logger.warning('FILELIST: path (%s) does not exist - empty filelist will be returned', path)
  151. return li
  152. def dirlist(path='.', rekursive=True):
  153. """
  154. Function returning a list of directories below a given path.
  155. :param str path: folder which is the basepath for searching files.
  156. :param bool rekursive: search all subfolders if True.
  157. :returns: list of filenames including the pathe
  158. :rtype: list
  159. .. note:: The returned filenames could be relative pathes depending on argument path.
  160. >>> for dirname in dirlist(path='..', rekursive=True):
  161. ... print dirname
  162. ../caching
  163. ../fstools
  164. """
  165. li = list()
  166. if os.path.exists(path):
  167. logger.debug('DIRLIST: path (%s) exists - looking for directories to append', path)
  168. for dirname in os.listdir(path):
  169. fulldir = os.path.join(path, dirname)
  170. if os.path.isdir(fulldir):
  171. li.append(fulldir)
  172. if rekursive:
  173. li.extend(dirlist(fulldir))
  174. else:
  175. logger.warning('DIRLIST: path (%s) does not exist - empty filelist will be returned', path)
  176. return li
  177. def is_writeable(path):
  178. """.. warning:: Needs to be documented
  179. """
  180. if os.access(path, os.W_OK):
  181. # path is writable whatever it is, file or directory
  182. return True
  183. else:
  184. # path is not writable whatever it is, file or directory
  185. return False
  186. def mkdir(path):
  187. """.. warning:: Needs to be documented
  188. """
  189. path = os.path.abspath(path)
  190. if not os.path.exists(os.path.dirname(path)):
  191. mkdir(os.path.dirname(path))
  192. if not os.path.exists(path):
  193. os.mkdir(path)
  194. return os.path.isdir(path)
  195. def open_locked_non_blocking(*args, **kwargs):
  196. """.. warning:: Needs to be documented (acquire exclusive lock file access). Throws an exception, if file is locked!
  197. """
  198. import fcntl
  199. locked_file_descriptor = open(*args, **kwargs)
  200. fcntl.lockf(locked_file_descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
  201. return locked_file_descriptor
  202. def open_locked_blocking(*args, **kwargs):
  203. """.. warning:: Needs to be documented (acquire exclusive lock file access). Blocks until file is free. deadlock!
  204. """
  205. import fcntl
  206. locked_file_descriptor = open(*args, **kwargs)
  207. fcntl.lockf(locked_file_descriptor, fcntl.LOCK_EX)
  208. return locked_file_descriptor