Completed
Pull Request — master (#260)
by Kirill
01:25
created

post_version_save()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
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
74
    class Meta:
75
        db_table = 'platforms'
76
77
    def __str__(self):
78
        return self.name
79
80
81
@python_2_unicode_compatible
82
class Channel(BaseModel):
83
    name = models.CharField(verbose_name='Channel', max_length=10, unique=True, db_index=True)
84
85
    class Meta:
86
        db_table = 'channels'
87
88
    def __str__(self):
89
        return self.name
90
91
92
def version_upload_to(obj, filename):
93
    return os.path.join('build', obj.app.name, obj.channel.name,
94
                        obj.platform.name, str(obj.version), filename)
95
96
97
def _version_upload_to(*args, **kwargs):
98
    return version_upload_to(*args, **kwargs)
99
100
101
@python_2_unicode_compatible
102
class Version(BaseModel):
103
    is_enabled = models.BooleanField(default=True)
104
    is_critical = models.BooleanField(default=False)
105
    app = models.ForeignKey(Application)
106
    platform = models.ForeignKey(Platform, db_index=True)
107
    channel = models.ForeignKey(Channel, db_index=True)
108
    version = VersionField(help_text='Format: 255.255.65535.65535', number_bits=(8, 8, 16, 16), db_index=True)
109
    release_notes = models.TextField(blank=True, null=True)
110
    file = models.FileField(upload_to=_version_upload_to, null=True)
111
    file_hash = models.CharField(verbose_name='Hash', max_length=140, null=True, blank=True)
112
    file_size = models.PositiveIntegerField(null=True, blank=True)
113
114
    objects = VersionManager()
115
116
    class Meta:
117
        db_table = 'versions'
118
        unique_together = (
119
            ('app', 'platform', 'channel', 'version'),
120
        )
121
        index_together = (
122
            ('app', 'platform', 'channel', 'version'),
123
        )
124
        ordering = ['id']
125
126
    def __str__(self):
127
        return "{app} {version}".format(app=self.app, version=self.version)
128
129
    @property
130
    def file_absolute_url(self):
131
        url = furl(self.file.url)
132
        if not url.scheme:
133
            url = '%s%s' % (settings.OMAHA_URL_PREFIX, url)
134
        return str(url)
135
136
    @property
137
    def file_package_name(self):
138
        url = furl(self.file_absolute_url)
139
        return os.path.basename(url.pathstr)
140
141
    @property
142
    def file_url(self):
143
        url = furl(self.file_absolute_url)
144
        if url.port and url.port != 80:
145
            return '%s://%s:%d%s/' % (url.scheme, url.host, url.port, os.path.dirname(url.pathstr))
146
        else:
147
            return '%s://%s%s/' % (url.scheme, url.host, os.path.dirname(url.pathstr))
148
149
    @property
150
    def size(self):
151
         return self.file_size
152
153
EVENT_DICT_CHOICES = dict(
154
    preinstall=0,
155
    install=1,
156
    postinstall=2,
157
    update=3,
158
)
159
160
EVENT_CHOICES = zip(EVENT_DICT_CHOICES.values(), EVENT_DICT_CHOICES.keys())
161
162
163
class Action(BaseModel):
164
    version = models.ForeignKey(Version, db_index=True, related_name='actions')
165
    event = models.PositiveSmallIntegerField(
166
        choices=EVENT_CHOICES,
167
        help_text='Contains a fixed string denoting when this action should be run.')
168
    run = models.CharField(
169
        max_length=255, null=True, blank=True,
170
        help_text='The name of an installer binary to run.')
171
    arguments = models.CharField(
172
        max_length=255, null=True, blank=True,
173
        help_text='Arguments to be passed to that installer binary.')
174
    successurl = models.URLField(
175
        null=True, blank=True,
176
        help_text="A URL to be opened using the system's "
177
                  "default web browser on a successful install.")
178
    terminateallbrowsers = models.BooleanField(
179
        default=False,
180
        help_text='If "true", close all browser windows before starting the installer binary.')
181
    successsaction = models.CharField(
182
        null=True, max_length=255, blank=True,
183
        help_text='Contains a fixed string denoting some action to take '
184
                  'in response to a successful install')
185
    other = JSONField(verbose_name='Other attributes', help_text='JSON format', null=True, blank=True,)
186
187
    class Meta:
188
        db_table = 'actions'
189
        ordering = ['id']
190
191
    def get_attributes(self):
192
        exclude_fields = ('id', 'version', 'event', 'other', 'created',
193
                          'modified', 'terminateallbrowsers')
194
        attrs = dict([(field.name, str(getattr(self, field.name)))
195
                      for field in self._meta.fields
196
                      if field.name not in exclude_fields
197
                      and getattr(self, field.name)])
198
        if self.terminateallbrowsers:
199
            attrs['terminateallbrowsers'] = 'true'
200
        attrs.update(self.other or {})
201
        return attrs
202
203
204
ACTIVE_USERS_DICT_CHOICES = dict(
205
    all=0,
206
    week=1,
207
    month=2,
208
)
209
210
ACTIVE_USERS_CHOICES = zip(ACTIVE_USERS_DICT_CHOICES.values(), ACTIVE_USERS_DICT_CHOICES.keys())
211
212
213
class PartialUpdate(models.Model):
214
    is_enabled = models.BooleanField(default=True, db_index=True)
215
    version = models.OneToOneField(Version, db_index=True)
216
    percent = PercentField()
217
    start_date = models.DateField(db_index=True)
218
    end_date = models.DateField(db_index=True)
219
    exclude_new_users = models.BooleanField(default=True)
220
    active_users = models.PositiveSmallIntegerField(
221
        help_text='Active users in the past ...',
222
        choices=ACTIVE_USERS_CHOICES, default=1)
223
224
225
NAME_DATA_DICT_CHOICES = dict(
226
    install=0,
227
    untrusted=1,
228
)
229
230
NAME_DATA_CHOICES = zip(NAME_DATA_DICT_CHOICES.values(), NAME_DATA_DICT_CHOICES.keys())
231
232
233
class Data(BaseModel):
234
    app = models.ForeignKey(Application, db_index=True)
235
    name = models.PositiveSmallIntegerField(choices=NAME_DATA_CHOICES)
236
    index = models.CharField(max_length=255, null=True, blank=True)
237
    value = models.TextField(null=True, blank=True)
238
239
240
class Os(models.Model):
241
    platform = models.CharField(max_length=10, null=True, blank=True)
242
    version = models.CharField(max_length=10, null=True, blank=True)
243
    sp = models.CharField(max_length=40, null=True, blank=True)
244
    arch = models.CharField(max_length=10, null=True, blank=True)
245
246
    class Meta:
247
        unique_together = (
248
            ('platform', 'version', 'sp', 'arch'),
249
        )
250
251
class Hw(models.Model):
252
    sse = models.PositiveIntegerField(null=True, blank=True)
253
    sse2 = models.PositiveIntegerField(null=True, blank=True)
254
    sse3 = models.PositiveIntegerField(null=True, blank=True)
255
    ssse3 = models.PositiveIntegerField(null=True, blank=True)
256
    sse41 = models.PositiveIntegerField(null=True, blank=True)
257
    sse42 = models.PositiveIntegerField(null=True, blank=True)
258
    avx = models.PositiveIntegerField(null=True, blank=True)
259
    physmemory = models.PositiveIntegerField(null=True, blank=True)
260
261
262
class Request(models.Model):
263
    os = models.ForeignKey(Os, null=True, blank=True)
264
    hw = models.ForeignKey(Hw, null=True, blank=True)
265
    version = VersionField(help_text='Format: 255.255.65535.65535', number_bits=(8, 8, 16, 16))
266
    ismachine = models.PositiveSmallIntegerField(null=True, blank=True)
267
    sessionid = models.CharField(max_length=40, null=True, blank=True)
268
    userid = models.CharField(max_length=40, null=True, blank=True)
269
    installsource = models.CharField(max_length=40, null=True, blank=True)
270
    originurl = models.URLField(null=True, blank=True)
271
    testsource = models.CharField(max_length=40, null=True, blank=True)
272
    updaterchannel = models.CharField(max_length=10, null=True, blank=True)
273
    created = models.DateTimeField(db_index=True, default=datetime_now, editable=False, blank=True)
274
    ip = models.GenericIPAddressField(blank=True, null=True)
275
276
277
class Event(models.Model):
278
    eventtype = models.PositiveSmallIntegerField(db_index=True)
279
    eventresult = models.PositiveSmallIntegerField()
280
    errorcode = models.IntegerField(null=True, blank=True)
281
    extracode1 = models.IntegerField(null=True, blank=True)
282
    download_time_ms = models.PositiveIntegerField(null=True, blank=True)
283
    downloaded = models.PositiveIntegerField(null=True, blank=True)
284
    total = models.PositiveIntegerField(null=True, blank=True)
285
    update_check_time_ms = models.PositiveIntegerField(null=True, blank=True)
286
    install_time_ms = models.PositiveIntegerField(null=True, blank=True)
287
    source_url_index = models.URLField(null=True, blank=True)
288
    state_cancelled = models.PositiveIntegerField(null=True, blank=True)
289
    time_since_update_available_ms = models.PositiveIntegerField(null=True, blank=True)
290
    time_since_download_start_ms = models.PositiveIntegerField(null=True, blank=True)
291
    nextversion = models.CharField(max_length=40, null=True, blank=True)
292
    previousversion = models.CharField(max_length=40, null=True, blank=True)
293
294
    @property
295
    def is_error(self):
296
        if self.eventtype in (100, 102, 103):
297
            return True
298
        elif self.eventresult not in (1, 2, 3):
299
            return True
300
        elif self.errorcode != 0:
301
            return True
302
        return False
303
304
305
class AppRequest(models.Model):
306
    request = models.ForeignKey(Request, db_index=True)
307
    appid = models.CharField(max_length=38, db_index=True)
308
    version = VersionField(help_text='Format: 255.255.65535.65535',
309
                           number_bits=(8, 8, 16, 16), default=0, null=True, blank=True)
310
    nextversion = VersionField(help_text='Format: 255.255.65535.65535',
311
                               number_bits=(8, 8, 16, 16), default=0, null=True, blank=True)
312
    lang = models.CharField(max_length=40, null=True, blank=True)
313
    tag = models.CharField(max_length=40, null=True, blank=True)
314
    installage = models.SmallIntegerField(null=True, blank=True)
315
    events = models.ManyToManyField(Event)
316
317
318
@receiver(pre_save, sender=Version)
319
def pre_version_save(sender, instance, *args, **kwargs):
320
    if instance.pk:
321
        old = sender.objects.get(pk=instance.pk)
322
        if old.file == instance.file:
323
            return
324
        else:
325
            old.file.delete(save=False)
326
            old.file_size = 0
327
    sha1 = hashlib.sha1()
328
    for chunk in instance.file.chunks():
329
        sha1.update(chunk)
330
    instance.file_hash = base64.b64encode(sha1.digest()).decode()
331
332
333
334
335
@receiver(pre_delete, sender=Version)
336
def pre_version_delete(sender, instance, **kwargs):
337
    storage, name = instance.file.storage, instance.file.name
338
    if name:
339
        storage.delete(name)
340