PartialUpdate   A
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 10
Duplicated Lines 0 %

Importance

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