Completed
Push — master ( e6f560...e37842 )
by Egor
01:00
created

pre_version_save()   B

Complexity

Conditions 5

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
c 1
b 0
f 0
dl 0
loc 18
rs 8.5454
1
# coding: utf8
2
3
"""
4
This software is licensed under the Apache 2 license, quoted below.
5
6
Copyright 2014 Crystalnix Limited
7
8
Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
use this file except in compliance with the License. You may obtain a copy of
10
the License at
11
12
    http://www.apache.org/licenses/LICENSE-2.0
13
14
Unless required by applicable law or agreed to in writing, software
15
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
License for the specific language governing permissions and limitations under
18
the License.
19
"""
20
21
from __future__ import unicode_literals
22
from django.utils.encoding import python_2_unicode_compatible
23
24
import os
25
import hashlib
26
import base64
27
28
from django.db import models
29
from django.conf import settings
30
from django.dispatch import receiver
31
from django.db.models.signals import pre_save, pre_delete, post_save
32
from django.utils.timezone import now as datetime_now
33
34
from omaha.managers import VersionManager
35
from omaha.fields import PercentField
36
37
from django_extensions.db.fields import (
38
    CreationDateTimeField, ModificationDateTimeField,
39
)
40
from jsonfield import JSONField
41
from versionfield import VersionField
42
from furl import furl
43
44
45
__all__ = ['Application', 'Channel', 'Platform', 'Version',
46
           'Action', 'EVENT_DICT_CHOICES', 'EVENT_CHOICES',
47
           'Data']
48
49
class BaseModel(models.Model):
50
    created = CreationDateTimeField('created')
51
    modified = ModificationDateTimeField('modified')
52
53
    class Meta:
54
        abstract = True
55
56
57
@python_2_unicode_compatible
58
class Application(BaseModel):
59
    id = models.CharField(max_length=38, primary_key=True)
60
    name = models.CharField(verbose_name='App', max_length=30, unique=True)
61
62
    class Meta:
63
        db_table = 'applications'
64
        ordering = ['id']
65
66
    def __str__(self):
67
        return self.name
68
69
70
@python_2_unicode_compatible
71
class Platform(BaseModel):
72
    name = models.CharField(verbose_name='Platform', max_length=10, unique=True, db_index=True)
73
    verbose_name = models.CharField(max_length=20, blank=True)
74
75
    class Meta:
76
        db_table = 'platforms'
77
78
    def __str__(self):
79
        return self.name
80
81
82
@python_2_unicode_compatible
83
class Channel(BaseModel):
84
    name = models.CharField(verbose_name='Channel', max_length=10, unique=True, db_index=True)
85
86
    class Meta:
87
        db_table = 'channels'
88
89
    def __str__(self):
90
        return self.name
91
92
93
def version_upload_to(obj, filename):
94
    return os.path.join('build', obj.app.name, obj.channel.name,
95
                        obj.platform.name, str(obj.version), filename)
96
97
98
def _version_upload_to(*args, **kwargs):
99
    return version_upload_to(*args, **kwargs)
100
101
102
@python_2_unicode_compatible
103
class Version(BaseModel):
104
    is_enabled = models.BooleanField(default=True)
105
    is_critical = models.BooleanField(default=False)
106
    app = models.ForeignKey(Application)
107
    platform = models.ForeignKey(Platform, db_index=True)
108
    channel = models.ForeignKey(Channel, db_index=True)
109
    version = VersionField(help_text='Format: 255.255.65535.65535', number_bits=(8, 8, 16, 16), db_index=True)
110
    release_notes = models.TextField(blank=True, null=True)
111
    file = models.FileField(upload_to=_version_upload_to, null=True)
112
    file_hash = models.CharField(verbose_name='Hash', max_length=140, null=True, blank=True)
113
    file_size = models.PositiveIntegerField(null=True, blank=True)
114
115
    objects = VersionManager()
116
117
    class Meta:
118
        db_table = 'versions'
119
        unique_together = (
120
            ('app', 'platform', 'channel', 'version'),
121
        )
122
        index_together = (
123
            ('app', 'platform', 'channel', 'version'),
124
        )
125
        ordering = ['id']
126
127
    def __str__(self):
128
        return "{app} {version}".format(app=self.app, version=self.version)
129
130
    @property
131
    def file_absolute_url(self):
132
        url = furl(self.file.url)
133
        if not url.scheme:
134
            url = '%s%s' % (settings.OMAHA_URL_PREFIX, url)
135
        return str(url)
136
137
    @property
138
    def file_package_name(self):
139
        url = furl(self.file_absolute_url)
140
        return os.path.basename(url.pathstr)
141
142
    @property
143
    def file_url(self):
144
        url = furl(self.file_absolute_url)
145
        if url.port and url.port != 80:
146
            return '%s://%s:%d%s/' % (url.scheme, url.host, url.port, os.path.dirname(url.pathstr))
147
        else:
148
            return '%s://%s%s/' % (url.scheme, url.host, os.path.dirname(url.pathstr))
149
150
    @property
151
    def size(self):
152
         return self.file_size
153
154
EVENT_DICT_CHOICES = dict(
155
    preinstall=0,
156
    install=1,
157
    postinstall=2,
158
    update=3,
159
)
160
161
EVENT_CHOICES = zip(EVENT_DICT_CHOICES.values(), EVENT_DICT_CHOICES.keys())
162
163
164
class Action(BaseModel):
165
    version = models.ForeignKey(Version, db_index=True, related_name='actions')
166
    event = models.PositiveSmallIntegerField(
167
        choices=EVENT_CHOICES,
168
        help_text='Contains a fixed string denoting when this action should be run.')
169
    run = models.CharField(
170
        max_length=255, null=True, blank=True,
171
        help_text='The name of an installer binary to run.')
172
    arguments = models.CharField(
173
        max_length=255, null=True, blank=True,
174
        help_text='Arguments to be passed to that installer binary.')
175
    successurl = models.URLField(
176
        null=True, blank=True,
177
        help_text="A URL to be opened using the system's "
178
                  "default web browser on a successful install.")
179
    terminateallbrowsers = models.BooleanField(
180
        default=False,
181
        help_text='If "true", close all browser windows before starting the installer binary.')
182
    successsaction = models.CharField(
183
        null=True, max_length=255, blank=True,
184
        help_text='Contains a fixed string denoting some action to take '
185
                  'in response to a successful install')
186
    other = JSONField(verbose_name='Other attributes', help_text='JSON format', null=True, blank=True,)
187
188
    class Meta:
189
        db_table = 'actions'
190
        ordering = ['id']
191
192
    def get_attributes(self):
193
        exclude_fields = ('id', 'version', 'event', 'other', 'created',
194
                          'modified', 'terminateallbrowsers')
195
        attrs = dict([(field.name, str(getattr(self, field.name)))
196
                      for field in self._meta.fields
197
                      if field.name not in exclude_fields
198
                      and getattr(self, field.name)])
199
        if self.terminateallbrowsers:
200
            attrs['terminateallbrowsers'] = 'true'
201
        attrs.update(self.other or {})
202
        return attrs
203
204
205
ACTIVE_USERS_DICT_CHOICES = dict(
206
    all=0,
207
    week=1,
208
    month=2,
209
)
210
211
ACTIVE_USERS_CHOICES = zip(ACTIVE_USERS_DICT_CHOICES.values(), ACTIVE_USERS_DICT_CHOICES.keys())
212
213
214
class PartialUpdate(models.Model):
215
    is_enabled = models.BooleanField(default=True, db_index=True)
216
    version = models.OneToOneField(Version, db_index=True)
217
    percent = PercentField()
218
    start_date = models.DateField(db_index=True)
219
    end_date = models.DateField(db_index=True)
220
    exclude_new_users = models.BooleanField(default=True)
221
    active_users = models.PositiveSmallIntegerField(
222
        help_text='Active users in the past ...',
223
        choices=ACTIVE_USERS_CHOICES, default=1)
224
225
226
NAME_DATA_DICT_CHOICES = dict(
227
    install=0,
228
    untrusted=1,
229
)
230
231
NAME_DATA_CHOICES = zip(NAME_DATA_DICT_CHOICES.values(), NAME_DATA_DICT_CHOICES.keys())
232
233
234
class Data(BaseModel):
235
    app = models.ForeignKey(Application, db_index=True)
236
    name = models.PositiveSmallIntegerField(choices=NAME_DATA_CHOICES)
237
    index = models.CharField(max_length=255, null=True, blank=True)
238
    value = models.TextField(null=True, blank=True)
239
240
241
class Os(models.Model):
242
    platform = models.CharField(max_length=10, null=True, blank=True)
243
    version = models.CharField(max_length=10, null=True, blank=True)
244
    sp = models.CharField(max_length=40, null=True, blank=True)
245
    arch = models.CharField(max_length=10, null=True, blank=True)
246
247
    class Meta:
248
        unique_together = (
249
            ('platform', 'version', 'sp', 'arch'),
250
        )
251
252
class Hw(models.Model):
253
    sse = models.PositiveIntegerField(null=True, blank=True)
254
    sse2 = models.PositiveIntegerField(null=True, blank=True)
255
    sse3 = models.PositiveIntegerField(null=True, blank=True)
256
    ssse3 = models.PositiveIntegerField(null=True, blank=True)
257
    sse41 = models.PositiveIntegerField(null=True, blank=True)
258
    sse42 = models.PositiveIntegerField(null=True, blank=True)
259
    avx = models.PositiveIntegerField(null=True, blank=True)
260
    physmemory = models.PositiveIntegerField(null=True, blank=True)
261
262
263
class Request(models.Model):
264
    os = models.ForeignKey(Os, null=True, blank=True)
265
    hw = models.ForeignKey(Hw, null=True, blank=True)
266
    version = VersionField(help_text='Format: 255.255.65535.65535', number_bits=(8, 8, 16, 16))
267
    ismachine = models.PositiveSmallIntegerField(null=True, blank=True)
268
    sessionid = models.CharField(max_length=40, null=True, blank=True)
269
    userid = models.CharField(max_length=40, null=True, blank=True)
270
    installsource = models.CharField(max_length=40, null=True, blank=True)
271
    originurl = models.URLField(null=True, blank=True)
272
    testsource = models.CharField(max_length=40, null=True, blank=True)
273
    updaterchannel = models.CharField(max_length=10, null=True, blank=True)
274
    created = models.DateTimeField(db_index=True, default=datetime_now, editable=False, blank=True)
275
    ip = models.GenericIPAddressField(blank=True, null=True)
276
277
278
class Event(models.Model):
279
    eventtype = models.PositiveSmallIntegerField(db_index=True)
280
    eventresult = models.PositiveSmallIntegerField()
281
    errorcode = models.IntegerField(null=True, blank=True)
282
    extracode1 = models.IntegerField(null=True, blank=True)
283
    download_time_ms = models.PositiveIntegerField(null=True, blank=True)
284
    downloaded = models.PositiveIntegerField(null=True, blank=True)
285
    total = models.PositiveIntegerField(null=True, blank=True)
286
    update_check_time_ms = models.PositiveIntegerField(null=True, blank=True)
287
    install_time_ms = models.PositiveIntegerField(null=True, blank=True)
288
    source_url_index = models.URLField(null=True, blank=True)
289
    state_cancelled = models.PositiveIntegerField(null=True, blank=True)
290
    time_since_update_available_ms = models.PositiveIntegerField(null=True, blank=True)
291
    time_since_download_start_ms = models.PositiveIntegerField(null=True, blank=True)
292
    nextversion = models.CharField(max_length=40, null=True, blank=True)
293
    previousversion = models.CharField(max_length=40, null=True, blank=True)
294
295
    @property
296
    def is_error(self):
297
        if self.eventtype in (100, 102, 103):
298
            return True
299
        elif self.eventresult not in (1, 2, 3):
300
            return True
301
        elif self.errorcode != 0:
302
            return True
303
        return False
304
305
306
class AppRequest(models.Model):
307
    request = models.ForeignKey(Request, db_index=True)
308
    appid = models.CharField(max_length=38, db_index=True)
309
    version = VersionField(help_text='Format: 255.255.65535.65535',
310
                           number_bits=(8, 8, 16, 16), default=0, null=True, blank=True)
311
    nextversion = VersionField(help_text='Format: 255.255.65535.65535',
312
                               number_bits=(8, 8, 16, 16), default=0, null=True, blank=True)
313
    lang = models.CharField(max_length=40, null=True, blank=True)
314
    tag = models.CharField(max_length=40, null=True, blank=True)
315
    installage = models.SmallIntegerField(null=True, blank=True)
316
    events = models.ManyToManyField(Event)
317
318
319
@receiver(pre_save, sender=Version)
320
def pre_version_save(sender, instance, *args, **kwargs):
321
    if instance.pk:
322
        old = sender.objects.get(pk=instance.pk)
323
        if old.file == instance.file:
324
            return
325
        else:
326
            try:
327
                old.file.delete(save=False)
328
            except:
329
                pass
330
            finally:
331
                old.file_size = 0
332
    sha1 = hashlib.sha1()
333
    for chunk in instance.file.chunks():
334
        sha1.update(chunk)
335
    instance.file.seek(0)
336
    instance.file_hash = base64.b64encode(sha1.digest()).decode()
337
338
339
340
341
@receiver(pre_delete, sender=Version)
342
def pre_version_delete(sender, instance, **kwargs):
343
    storage, name = instance.file.storage, instance.file.name
344
    if name:
345
        storage.delete(name)
346