Completed
Push — master ( 37bb4c...2160b9 )
by Egor
01:33
created

Action.get_attributes()   B

Complexity

Conditions 5

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 5
dl 0
loc 11
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
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
246
class Hw(models.Model):
247
    sse = models.PositiveIntegerField(null=True, blank=True)
248
    sse2 = models.PositiveIntegerField(null=True, blank=True)
249
    sse3 = models.PositiveIntegerField(null=True, blank=True)
250
    ssse3 = models.PositiveIntegerField(null=True, blank=True)
251
    sse41 = models.PositiveIntegerField(null=True, blank=True)
252
    sse42 = models.PositiveIntegerField(null=True, blank=True)
253
    avx = models.PositiveIntegerField(null=True, blank=True)
254
    physmemory = models.PositiveIntegerField(null=True, blank=True)
255
256
257
class Request(models.Model):
258
    os = models.ForeignKey(Os, null=True, blank=True)
259
    hw = models.ForeignKey(Hw, null=True, blank=True)
260
    version = VersionField(help_text='Format: 255.255.65535.65535', number_bits=(8, 8, 16, 16))
261
    ismachine = models.PositiveSmallIntegerField(null=True, blank=True)
262
    sessionid = models.CharField(max_length=40, null=True, blank=True)
263
    userid = models.CharField(max_length=40, null=True, blank=True)
264
    installsource = models.CharField(max_length=40, null=True, blank=True)
265
    originurl = models.URLField(null=True, blank=True)
266
    testsource = models.CharField(max_length=40, null=True, blank=True)
267
    updaterchannel = models.CharField(max_length=10, null=True, blank=True)
268
    created = models.DateTimeField(db_index=True, default=datetime_now, editable=False, blank=True)
269
    ip = models.GenericIPAddressField(blank=True, null=True)
270
271
272
class Event(models.Model):
273
    eventtype = models.PositiveSmallIntegerField(db_index=True)
274
    eventresult = models.PositiveSmallIntegerField()
275
    errorcode = models.IntegerField(null=True, blank=True)
276
    extracode1 = models.IntegerField(null=True, blank=True)
277
    download_time_ms = models.PositiveIntegerField(null=True, blank=True)
278
    downloaded = models.PositiveIntegerField(null=True, blank=True)
279
    total = models.PositiveIntegerField(null=True, blank=True)
280
    update_check_time_ms = models.PositiveIntegerField(null=True, blank=True)
281
    install_time_ms = models.PositiveIntegerField(null=True, blank=True)
282
    source_url_index = models.URLField(null=True, blank=True)
283
    state_cancelled = models.PositiveIntegerField(null=True, blank=True)
284
    time_since_update_available_ms = models.PositiveIntegerField(null=True, blank=True)
285
    time_since_download_start_ms = models.PositiveIntegerField(null=True, blank=True)
286
    nextversion = models.CharField(max_length=40, null=True, blank=True)
287
    previousversion = models.CharField(max_length=40, null=True, blank=True)
288
289
    @property
290
    def is_error(self):
291
        if self.eventtype in (100, 102, 103):
292
            return True
293
        elif self.eventresult not in (1, 2, 3):
294
            return True
295
        elif self.errorcode != 0:
296
            return True
297
        return False
298
299
300
class AppRequest(models.Model):
301
    request = models.ForeignKey(Request, db_index=True)
302
    appid = models.CharField(max_length=38, db_index=True)
303
    version = VersionField(help_text='Format: 255.255.65535.65535',
304
                           number_bits=(8, 8, 16, 16), default=0, null=True, blank=True)
305
    nextversion = VersionField(help_text='Format: 255.255.65535.65535',
306
                               number_bits=(8, 8, 16, 16), default=0, null=True, blank=True)
307
    lang = models.CharField(max_length=40, null=True, blank=True)
308
    tag = models.CharField(max_length=40, null=True, blank=True)
309
    installage = models.SmallIntegerField(null=True, blank=True)
310
    events = models.ManyToManyField(Event)
311
312
313
@receiver(pre_save, sender=Version)
314
def pre_version_save(sender, instance, *args, **kwargs):
315
    if instance.pk:
316
        old = sender.objects.get(pk=instance.pk)
317
        if old.file == instance.file:
318
            return
319
    sha1 = hashlib.sha1()
320
    for chunk in instance.file.chunks():
321
        sha1.update(chunk)
322
    instance.file_hash = base64.b64encode(sha1.digest()).decode()
323
324
325
@receiver(pre_delete, sender=Version)
326
def pre_version_delete(sender, instance, **kwargs):
327
    storage, name = instance.file.storage, instance.file.name
328
    if name:
329
        storage.delete(name)
330