47
42
original file will not be affected. Also, any ongoing reads from the
48
43
old file will continue without iterruption.
50
# The time, in seconds, that an unreferenced file is allowed to
51
# persist in order to satisfy ongoing requests.
52
grace_time = 12 * 60 * 60
54
def get_existing_storage(self, filename):
55
"""Return an existing `FileStorage` of this name, or None."""
56
return get_one(self.filter(filename=filename))
58
46
def save_file(self, filename, file_object):
59
"""Save the file to the filesystem and persist to the database.
61
The file will end up in MEDIA_ROOT/storage/
47
"""Save the file to the database.
63
49
If a file of that name already existed, it will be replaced by the
66
52
# This probably ought to read in chunks but large files are
67
# not expected. Also note that uploading a file with the same
68
# name as an existing one will cause that file to be written
69
# with a new generated name, and the old one remains where it
70
# is. See https://code.djangoproject.com/ticket/6157 - the
71
# Django devs consider deleting things dangerous ... ha.
72
# HOWEVER - this operation would need to be atomic anyway so
73
# it's safest left how it is for now (reads can overlap with
75
content = ContentFile(file_object.read())
77
storage = self.get_existing_storage(filename)
79
storage = FileStorage(filename=filename)
80
storage.data.save(filename, content)
54
content = Bin(file_object.read())
55
storage, created = self.get_or_create(
56
filename=filename, defaults={'content': content})
58
storage.content = content
83
def list_stored_files(self):
84
"""Find the files stored in the filesystem."""
85
dirs, files = FileStorage.storage.listdir(FileStorage.upload_dir)
87
os.path.join(FileStorage.upload_dir, filename)
88
for filename in files]
90
def list_referenced_files(self):
91
"""Find the names of files that are referenced from `FileStorage`.
93
:return: All file paths within MEDIA ROOT (relative to MEDIA_ROOT)
94
that have `FileStorage` entries referencing them.
98
file_storage.data.name
99
for file_storage in self.all())
101
def is_old(self, storage_filename):
102
"""Is the named file in the filesystem storage old enough to be dead?
104
:param storage_filename: The name under which the file is stored in
105
the filesystem, relative to MEDIA_ROOT. This need not be the
106
same name as its filename as stored in the `FileStorage` object.
107
It includes the name of the upload directory.
109
file_path = os.path.join(settings.MEDIA_ROOT, storage_filename)
110
mtime = os.stat(file_path).st_mtime
111
expiry = mtime + self.grace_time
112
return expiry <= time.time()
114
def collect_garbage(self):
115
"""Clean up stored files that are no longer accessible."""
116
# Avoid circular imports.
117
from maasserver.models import logger
120
stored_files = self.list_stored_files()
122
if e.errno != ENOENT:
125
"Upload directory does not exist yet. "
126
"Skipping garbage collection.")
128
referenced_files = self.list_referenced_files()
129
for path in stored_files:
130
if path not in referenced_files and self.is_old(path):
131
FileStorage.storage.delete(path)
134
63
class FileStorage(CleanSave, Model):
135
64
"""A simple file storage keyed on file name.
137
66
:ivar filename: A unique file name to use for the data being stored.
138
:ivar data: The file's actual data.
67
:ivar content: The file's actual data.
141
70
class Meta(DefaultMeta):
142
71
"""Needed for South to recognize this model."""
144
storage = FileSystemStorage()
146
upload_dir = "storage"
148
# Unix filenames can be longer than this (e.g. 255 bytes), but leave
149
# some extra room for the full path, as well as a versioning suffix.
150
filename = CharField(max_length=200, unique=True, editable=False)
151
data = FileField(upload_to=upload_dir, storage=storage, max_length=255)
73
filename = CharField(max_length=255, unique=True, editable=False)
74
content = BinaryField(null=False)
153
76
objects = FileStorageManager()