workbench.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # GNU MediaGoblin -- federated, autonomous media hosting
  2. # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
  3. #
  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. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import os
  17. import shutil
  18. import tempfile
  19. import six
  20. from mediagoblin._compat import py2_unicode
  21. # Actual workbench stuff
  22. # ----------------------
  23. @py2_unicode
  24. class Workbench(object):
  25. """
  26. Represent the directory for the workbench
  27. WARNING: DO NOT create Workbench objects on your own,
  28. let the WorkbenchManager do that for you!
  29. """
  30. def __init__(self, dir):
  31. """
  32. WARNING: DO NOT create Workbench objects on your own,
  33. let the WorkbenchManager do that for you!
  34. """
  35. self.dir = dir
  36. def __str__(self):
  37. return six.text_type(self.dir)
  38. def __repr__(self):
  39. try:
  40. return str(self)
  41. except AttributeError:
  42. return 'None'
  43. def joinpath(self, *args):
  44. return os.path.join(self.dir, *args)
  45. def localized_file(self, storage, filepath,
  46. filename_if_copying=None,
  47. keep_extension_if_copying=True):
  48. """
  49. Possibly localize the file from this storage system (for read-only
  50. purposes, modifications should be written to a new file.).
  51. If the file is already local, just return the absolute filename of that
  52. local file. Otherwise, copy the file locally to the workbench, and
  53. return the absolute path of the new file.
  54. If it is copying locally, we might want to require a filename like
  55. "source.jpg" to ensure that we won't conflict with other filenames in
  56. our workbench... if that's the case, make sure filename_if_copying is
  57. set to something like 'source.jpg'. Relatedly, if you set
  58. keep_extension_if_copying, you don't have to set an extension on
  59. filename_if_copying yourself, it'll be set for you (assuming such an
  60. extension can be extacted from the filename in the filepath).
  61. Returns:
  62. localized_filename
  63. Examples:
  64. >>> wb_manager.localized_file(
  65. ... '/our/workbench/subdir', local_storage,
  66. ... ['path', 'to', 'foobar.jpg'])
  67. u'/local/storage/path/to/foobar.jpg'
  68. >>> wb_manager.localized_file(
  69. ... '/our/workbench/subdir', remote_storage,
  70. ... ['path', 'to', 'foobar.jpg'])
  71. '/our/workbench/subdir/foobar.jpg'
  72. >>> wb_manager.localized_file(
  73. ... '/our/workbench/subdir', remote_storage,
  74. ... ['path', 'to', 'foobar.jpg'], 'source.jpeg', False)
  75. '/our/workbench/subdir/foobar.jpeg'
  76. >>> wb_manager.localized_file(
  77. ... '/our/workbench/subdir', remote_storage,
  78. ... ['path', 'to', 'foobar.jpg'], 'source', True)
  79. '/our/workbench/subdir/foobar.jpg'
  80. """
  81. if storage.local_storage:
  82. return storage.get_local_path(filepath)
  83. else:
  84. if filename_if_copying is None:
  85. dest_filename = filepath[-1]
  86. else:
  87. orig_filename, orig_ext = os.path.splitext(filepath[-1])
  88. if keep_extension_if_copying and orig_ext:
  89. dest_filename = filename_if_copying + orig_ext
  90. else:
  91. dest_filename = filename_if_copying
  92. full_dest_filename = os.path.join(
  93. self.dir, dest_filename)
  94. # copy it over
  95. storage.copy_locally(
  96. filepath, full_dest_filename)
  97. return full_dest_filename
  98. def destroy(self):
  99. """
  100. Destroy this workbench! Deletes the directory and all its contents!
  101. WARNING: Does no checks for a sane value in self.dir!
  102. """
  103. # just in case
  104. workbench = os.path.abspath(self.dir)
  105. shutil.rmtree(workbench)
  106. del self.dir
  107. def __enter__(self):
  108. """Make Workbench a context manager so we can use `with Workbench() as bench:`"""
  109. return self
  110. def __exit__(self, *args):
  111. """Clean up context manager, aka ourselves, deleting the workbench"""
  112. self.destroy()
  113. class WorkbenchManager(object):
  114. """
  115. A system for generating and destroying workbenches.
  116. Workbenches are actually just subdirectories of a (local) temporary
  117. storage space for during the processing stage. The preferred way to
  118. create them is to use:
  119. with workbenchmger.create() as workbench:
  120. do stuff...
  121. This will automatically clean up all temporary directories even in
  122. case of an exceptions. Also check the
  123. @mediagoblin.decorators.get_workbench decorator for a convenient
  124. wrapper.
  125. """
  126. def __init__(self, base_workbench_dir):
  127. self.base_workbench_dir = os.path.abspath(base_workbench_dir)
  128. if not os.path.exists(self.base_workbench_dir):
  129. os.makedirs(self.base_workbench_dir)
  130. def create(self):
  131. """
  132. Create and return the path to a new workbench (directory).
  133. """
  134. return Workbench(tempfile.mkdtemp(dir=self.base_workbench_dir))