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