Fix #5382 - Add migration and fix so tombstones are removed from collections
[larjonas-mediagoblin.git] / mediagoblin / storage / filestorage.py
blob89f4327640d192737355a4746c53d516702008c1
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
17 import io
18 import os
19 import shutil
21 import six.moves.urllib.parse as urlparse
23 from mediagoblin.storage import (
24 StorageInterface,
25 clean_listy_filepath,
26 NoWebServing)
28 class FileObjectAwareFile(io.FileIO):
29 def write(self, data):
30 if hasattr(data, 'read'):
31 # We can call data.read(). It means that the data is a file-like
32 # object, which should be saved RAM-friendly way
33 shutil.copyfileobj(data, self)
34 else:
35 super(FileObjectAwareFile, self).write(data)
38 class BasicFileStorage(StorageInterface):
39 """
40 Basic local filesystem implementation of storage API
41 """
43 local_storage = True
45 def __init__(self, base_dir, base_url=None, **kwargs):
46 """
47 Keyword arguments:
48 - base_dir: Base directory things will be served out of. MUST
49 be an absolute path.
50 - base_url: URL files will be served from
51 """
52 self.base_dir = base_dir
53 self.base_url = base_url
55 def _resolve_filepath(self, filepath):
56 """
57 Transform the given filepath into a local filesystem filepath.
58 """
59 return os.path.join(
60 self.base_dir, *clean_listy_filepath(filepath))
62 def file_exists(self, filepath):
63 return os.path.exists(self._resolve_filepath(filepath))
65 def get_file(self, filepath, mode='r'):
66 # Make directories if necessary
67 if len(filepath) > 1:
68 directory = self._resolve_filepath(filepath[:-1])
69 if not os.path.exists(directory):
70 os.makedirs(directory)
72 # Grab and return the file in the mode specified
73 return FileObjectAwareFile(self._resolve_filepath(filepath), mode)
75 def delete_file(self, filepath):
76 """Delete file at filepath
78 Raises OSError in case filepath is a directory."""
79 #TODO: log error
80 os.remove(self._resolve_filepath(filepath))
82 def delete_dir(self, dirpath, recursive=False):
83 """returns True on succes, False on failure"""
85 dirpath = self._resolve_filepath(dirpath)
87 # Shortcut the default and simple case of nonempty=F, recursive=F
88 if recursive:
89 try:
90 shutil.rmtree(dirpath)
91 except OSError as e:
92 #TODO: log something here
93 return False
94 else: # recursively delete everything
95 try:
96 os.rmdir(dirpath)
97 except OSError as e:
98 #TODO: log something here
99 return False
100 return True
102 def file_url(self, filepath):
103 if not self.base_url:
104 raise NoWebServing(
105 "base_url not set, cannot provide file urls")
107 return urlparse.urljoin(
108 self.base_url,
109 '/'.join(clean_listy_filepath(filepath)))
111 def get_local_path(self, filepath):
112 return self._resolve_filepath(filepath)
114 def copy_local_to_storage(self, filename, filepath):
116 Copy this file from locally to the storage system.
118 # Make directories if necessary
119 if len(filepath) > 1:
120 directory = self._resolve_filepath(filepath[:-1])
121 if not os.path.exists(directory):
122 os.makedirs(directory)
123 # This uses chunked copying of 16kb buffers (Py2.7):
124 shutil.copy(filename, self.get_local_path(filepath))
126 def get_file_size(self, filepath):
127 return os.stat(self._resolve_filepath(filepath)).st_size