Skip to content

Commit b4ccef5

Browse files
author
Joseph Atkins-Turkish
committed
Refactored S3 model logic into abstract base classes
1 parent fb63bae commit b4ccef5

File tree

9 files changed

+159
-133
lines changed

9 files changed

+159
-133
lines changed

ide/api/project.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,10 @@ def create_project(request):
180180
template.copy_into_project(project)
181181
elif project_type == 'simplyjs':
182182
f = SourceFile.objects.create(project=project, file_name="app.js")
183-
f.save_file(open('{}/src/html/demo.js'.format(settings.SIMPLYJS_ROOT)).read())
183+
f.save_text(open('{}/src/html/demo.js'.format(settings.SIMPLYJS_ROOT)).read())
184184
elif project_type == 'pebblejs':
185185
f = SourceFile.objects.create(project=project, file_name="app.js")
186-
f.save_file(open('{}/src/js/app.js'.format(settings.PEBBLEJS_ROOT)).read())
186+
f.save_text(open('{}/src/js/app.js'.format(settings.PEBBLEJS_ROOT)).read())
187187
except IntegrityError as e:
188188
return json_failure(str(e))
189189
else:

ide/api/resource.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def create_resource(request, project_id):
5252
resources.append(ResourceIdentifier.objects.create(resource_file=rf, **resource_options))
5353
if posted_file is not None:
5454
variant = ResourceVariant.objects.create(resource_file=rf, tags=",".join(str(int(t)) for t in new_tags))
55-
variant.save_file(posted_file, posted_file.size)
55+
variant.save_file(posted_file, file_size=posted_file.size)
5656

5757
rf.save()
5858

@@ -189,13 +189,13 @@ def update_resource(request, project_id, resource_id):
189189
variant.save()
190190
if 'file' in request.FILES:
191191
variant = resource.variants.create(tags=",".join(str(int(t)) for t in new_tags))
192-
variant.save_file(request.FILES['file'], request.FILES['file'].size)
192+
variant.save_file(request.FILES['file'], file_size=request.FILES['file'].size)
193193

194194
# We may get sent a list of pairs telling us which variant gets which replacement file
195195
for tags, file_index in replacement_map:
196196
variant = resource.variants.get(tags=tags)
197197
replacement = replacement_files[int(file_index)]
198-
variant.save_file(replacement, replacement.size)
198+
variant.save_file(replacement, file_size=replacement.size)
199199

200200
if file_name and resource.file_name != file_name:
201201
resource.file_name = file_name

ide/api/source.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def create_source_file(request, project_id):
2323
f = SourceFile.objects.create(project=project,
2424
file_name=request.POST['name'],
2525
target=request.POST.get('target', 'app'))
26-
f.save_file(request.POST.get('content', ''))
26+
f.save_text(request.POST.get('content', ''))
2727
except IntegrityError as e:
2828
return json_failure(str(e))
2929
else:
@@ -136,7 +136,8 @@ def save_source_file(request, project_id, file_id):
136136
}
137137
}, request=request, project=project)
138138
raise Exception(_("Could not save: file has been modified since last save."))
139-
source_file.save_file(request.POST['content'], folded_lines=request.POST['folded_lines'])
139+
source_file.save_text(request.POST['content'])
140+
source_file.save_lines(folded_lines=request.POST['folded_lines'])
140141

141142
except Exception as e:
142143
return json_failure(str(e))

ide/models/files.py

Lines changed: 21 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from django.utils.translation import ugettext as _
1414
import utils.s3 as s3
1515

16+
from ide.models.s3file import S3File
17+
from ide.models.scriptfile import ScriptFile
1618
from ide.models.meta import IdeModel
1719

1820
__author__ = 'katharine'
@@ -89,7 +91,7 @@ class Meta(IdeModel.Meta):
8991
unique_together = (('project', 'file_name'),)
9092

9193

92-
class ResourceVariant(IdeModel):
94+
class ResourceVariant(S3File):
9395
resource_file = models.ForeignKey(ResourceFile, related_name='variants')
9496

9597
VARIANT_DEFAULT = 0
@@ -116,6 +118,19 @@ class ResourceVariant(IdeModel):
116118
tags = models.CommaSeparatedIntegerField(max_length=50, blank=True)
117119
is_legacy = models.BooleanField(default=False) # True for anything migrated out of ResourceFile
118120

121+
# The following three properties are overridden to support is_legacy
122+
@property
123+
def padded_id(self):
124+
return '%05d' % self.resource_file.id if self.is_legacy else '%09d' % self.id
125+
126+
@property
127+
def s3_id(self):
128+
return self.resource_file.id if self.is_legacy else self.id
129+
130+
@property
131+
def folder(self):
132+
return 'resources' if self.is_legacy else 'resources/variants'
133+
119134
def get_tags(self):
120135
return [int(tag) for tag in self.tags.split(",") if tag]
121136

@@ -128,65 +143,6 @@ def get_tag_names(self):
128143
def get_tags_string(self):
129144
return "".join(self.get_tag_names())
130145

131-
def get_local_filename(self, create=False):
132-
if self.is_legacy:
133-
padded_id = '%05d' % self.resource_file.id
134-
filename = '%sresources/%s/%s/%s' % (settings.FILE_STORAGE, padded_id[0], padded_id[1], padded_id)
135-
else:
136-
padded_id = '%09d' % self.id
137-
filename = '%sresources/variants/%s/%s/%s' % (settings.FILE_STORAGE, padded_id[0], padded_id[1], padded_id)
138-
if create:
139-
if not os.path.exists(os.path.dirname(filename)):
140-
os.makedirs(os.path.dirname(filename))
141-
return filename
142-
143-
def get_s3_path(self):
144-
if self.is_legacy:
145-
return 'resources/%s' % self.resource_file.id
146-
else:
147-
return 'resources/variants/%s' % self.id
148-
149-
local_filename = property(get_local_filename)
150-
s3_path = property(get_s3_path)
151-
152-
def save_file(self, stream, file_size=0):
153-
if file_size > 5*1024*1024:
154-
raise Exception(_("Uploaded file too big."))
155-
if not settings.AWS_ENABLED:
156-
if not os.path.exists(os.path.dirname(self.local_filename)):
157-
os.makedirs(os.path.dirname(self.local_filename))
158-
with open(self.local_filename, 'wb') as out:
159-
out.write(stream.read())
160-
else:
161-
s3.save_file('source', self.s3_path, stream.read())
162-
163-
self.resource_file.project.last_modified = now()
164-
self.resource_file.project.save()
165-
166-
def save_string(self, string):
167-
if not settings.AWS_ENABLED:
168-
if not os.path.exists(os.path.dirname(self.local_filename)):
169-
os.makedirs(os.path.dirname(self.local_filename))
170-
with open(self.local_filename, 'wb') as out:
171-
out.write(string)
172-
else:
173-
s3.save_file('source', self.s3_path, string)
174-
175-
self.resource_file.project.last_modified = now()
176-
self.resource_file.project.save()
177-
178-
def copy_to_path(self, path):
179-
if not settings.AWS_ENABLED:
180-
shutil.copy(self.local_filename, path)
181-
else:
182-
s3.read_file_to_filesystem('source', self.s3_path, path)
183-
184-
def get_contents(self):
185-
if not settings.AWS_ENABLED:
186-
return open(self.local_filename).read()
187-
else:
188-
return s3.read_file('source', self.s3_path)
189-
190146
def save(self, *args, **kwargs):
191147
self.full_clean()
192148
self.resource_file.save()
@@ -209,8 +165,7 @@ def get_root_path(self):
209165
path = property(get_path)
210166
root_path = property(get_root_path)
211167

212-
213-
class Meta(IdeModel.Meta):
168+
class Meta(S3File.Meta):
214169
unique_together = (('resource_file', 'tags'),)
215170

216171

@@ -271,89 +226,34 @@ def save(self, *args, **kwargs):
271226
super(ResourceIdentifier, self).save(*args, **kwargs)
272227

273228

274-
class SourceFile(IdeModel):
229+
class SourceFile(ScriptFile):
275230
project = models.ForeignKey('Project', related_name='source_files')
276231
file_name = models.CharField(max_length=100, validators=[RegexValidator(r"^[/a-zA-Z0-9_.-]+\.(c|h|js)$")])
277-
last_modified = models.DateTimeField(blank=True, null=True, auto_now=True)
278-
folded_lines = models.TextField(default="[]")
232+
folder = 'sources'
279233

280234
TARGETS = (
281235
('app', _('App')),
282236
('worker', _('Worker')),
283237
)
284238
target = models.CharField(max_length=10, choices=TARGETS, default='app')
285239

286-
def get_local_filename(self):
287-
padded_id = '%05d' % self.id
288-
return '%ssources/%s/%s/%s' % (settings.FILE_STORAGE, padded_id[0], padded_id[1], padded_id)
289-
290-
def get_s3_path(self):
291-
return 'sources/%d' % self.id
292-
293-
def get_contents(self):
294-
if not settings.AWS_ENABLED:
295-
try:
296-
return open(self.local_filename).read()
297-
except IOError:
298-
return ''
299-
else:
300-
return s3.read_file('source', self.s3_path)
301-
302-
def was_modified_since(self, expected_modification_time):
303-
if isinstance(expected_modification_time, int):
304-
expected_modification_time = datetime.datetime.fromtimestamp(expected_modification_time)
305-
assert isinstance(expected_modification_time, datetime.datetime)
306-
return self.last_modified.replace(tzinfo=None, microsecond=0) > expected_modification_time
307-
308-
def save_file(self, content, folded_lines=None):
309-
if not settings.AWS_ENABLED:
310-
if not os.path.exists(os.path.dirname(self.local_filename)):
311-
os.makedirs(os.path.dirname(self.local_filename))
312-
open(self.local_filename, 'w').write(content.encode('utf-8'))
313-
else:
314-
s3.save_file('source', self.s3_path, content.encode('utf-8'))
315-
if folded_lines:
316-
self.folded_lines = folded_lines
317-
self.save()
318-
319-
def copy_to_path(self, path):
320-
if not settings.AWS_ENABLED:
321-
try:
322-
shutil.copy(self.local_filename, path)
323-
except IOError as err:
324-
if err.errno == 2:
325-
open(path, 'w').close() # create the file if it's missing.
326-
else:
327-
raise
328-
else:
329-
s3.read_file_to_filesystem('source', self.s3_path, path)
330-
331-
def save(self, *args, **kwargs):
332-
self.full_clean()
333-
self.project.last_modified = now()
334-
self.project.save()
335-
super(SourceFile, self).save(*args, **kwargs)
336-
337240
@property
338241
def project_path(self):
339242
if self.target == 'app':
340243
return 'src/%s' % self.file_name
341244
else:
342245
return 'worker_src/%s' % self.file_name
343246

344-
local_filename = property(get_local_filename)
345-
s3_path = property(get_s3_path)
346-
347247
class Meta(IdeModel.Meta):
348248
unique_together = (('project', 'file_name'))
349249

350250

351251
@receiver(post_delete)
352252
def delete_file(sender, instance, **kwargs):
353-
if sender == SourceFile or sender == ResourceVariant:
253+
if issubclass(sender, S3File):
354254
if settings.AWS_ENABLED:
355255
try:
356-
s3.delete_file('source', instance.s3_path)
256+
s3.delete_file(sender.bucket_name, instance.s3_path)
357257
except:
358258
traceback.print_exc()
359259
else:

ide/models/project.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def copy_into_project(self, project):
120120

121121
for source_file in self.source_files.all():
122122
new_file = SourceFile.objects.create(project=project, file_name=source_file.file_name)
123-
new_file.save_file(source_file.get_contents().replace("__UUID_GOES_HERE__", uuid_string))
123+
new_file.save_text(source_file.get_contents().replace("__UUID_GOES_HERE__", uuid_string))
124124

125125
# Copy over relevant project properties.
126126
# NOTE: If new, relevant properties are added, they must be copied here.

ide/models/s3file.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import shutil
2+
import os
3+
4+
from django.utils.translation import ugettext as _
5+
from django.conf import settings
6+
from django.utils.timezone import now
7+
8+
import utils.s3 as s3
9+
from ide.models.meta import IdeModel
10+
11+
12+
class S3File(IdeModel):
13+
bucket_name = 'source'
14+
folder = None
15+
project = None
16+
_create_local_if_not_exists = False
17+
18+
@property
19+
def padded_id(self):
20+
return '%05d' % self.id
21+
22+
@property
23+
def local_filename(self):
24+
padded_id = self.paded_id
25+
return '%s%s/%s/%s/%s' % (settings.FILE_STORAGE, self.folder, padded_id[0], padded_id[1], padded_id)
26+
27+
@property
28+
def s3_id(self):
29+
return self.id
30+
31+
@property
32+
def s3_path(self):
33+
return '%s/%s' % (self.folder, self.s3_id)
34+
35+
def _get_contents_local(self):
36+
try:
37+
return open(self.local_filename).read()
38+
except IOError:
39+
if self._create_local_if_not_exists:
40+
return ''
41+
else:
42+
raise
43+
44+
def _save_string_local(self, string):
45+
if not os.path.exists(os.path.dirname(self.local_filename)):
46+
os.makedirs(os.path.dirname(self.local_filename))
47+
with open(self.local_filename, 'wb') as out:
48+
out.write(string)
49+
50+
def _copy_to_path_local(self, path):
51+
try:
52+
shutil.copy(self.local_filename, path)
53+
except IOError as err:
54+
if err.errno == 2 and self._crete_local_if_not_exists:
55+
open(path, 'w').close() # create the file if it's missing.
56+
else:
57+
raise
58+
59+
def get_contents(self):
60+
if not settings.AWS_ENABLED:
61+
return self._get_contents_local()
62+
else:
63+
return s3.read_file(self.bucket_name, self.s3_path)
64+
65+
def save_string(self, string):
66+
if not settings.AWS_ENABLED:
67+
self._save_string_local(string)
68+
else:
69+
s3.save_file(self.bucket_name, self.s3_path, string)
70+
if self.project:
71+
self.project.last_modified = now()
72+
self.project.save()
73+
74+
def save_file(self, stream, file_size=0):
75+
if file_size > 5 * 1024 * 1024:
76+
raise Exception(_("Uploaded file too big."))
77+
self.save_string(stream.read())
78+
79+
def save_text(self, content):
80+
self.save_string(content.encode('utf-8'))
81+
82+
def copy_to_path(self, path):
83+
if not settings.AWS_ENABLED:
84+
self._copy_to_path_local(path)
85+
else:
86+
s3.read_file_to_filesystem(self.bucket_name, self.s3_path, path)
87+
88+
class Meta(IdeModel.Meta):
89+
abstract = True

0 commit comments

Comments
 (0)