blob: 25aa452d1c1eb7d00d46837ceffbe0003058eb89 [file] [log] [blame]
Dirk Vogtc9e10ab2016-10-12 13:58:15 +02001# -*- coding: utf-8 -*-
Mitja Nikolaus6a679132018-08-30 14:35:29 +02002"""Models for devices, heartbeats, crashreports and log files."""
3
Mitja Nikolausb6cf6972018-10-04 15:03:31 +02004import os
Mitja Nikolausbcaf5022018-08-30 16:40:38 +02005import uuid
Dirk Vogtc9e10ab2016-10-12 13:58:15 +02006
Mitja Nikolausbcaf5022018-08-30 16:40:38 +02007from django.db import models, transaction
Dirk Vogtf2a33422016-10-11 17:17:26 +02008from django.contrib.auth.models import User
Mitja Nikolauscc90d572018-11-22 16:40:15 +01009from django.dispatch import receiver
Dirk Vogtf2a33422016-10-11 17:17:26 +020010from taggit.managers import TaggableManager
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020011
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020012
Dirk Vogtf2a33422016-10-11 17:17:26 +020013class Device(models.Model):
Mitja Nikolaus6a679132018-08-30 14:35:29 +020014 """A device representing a phone that has been registered on Hiccup."""
15
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020016 def __str__(self):
Mitja Nikolaus6a679132018-08-30 14:35:29 +020017 """Return the UUID as string representation of a device."""
Dirk Vogt83107df2017-05-02 12:04:19 +020018 return self.uuid
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020019
Dirk Vogtf2a33422016-10-11 17:17:26 +020020 # for every device there is a django user
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020021 uuid = models.CharField(
22 db_index=True,
23 max_length=64,
24 unique=True,
25 default=uuid.uuid4,
26 editable=False,
27 )
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020028 user = models.OneToOneField(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020029 User,
30 related_name="Hiccup_Device",
31 on_delete=models.CASCADE,
32 unique=True,
33 )
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020034 imei = models.CharField(max_length=32, null=True, blank=True)
35 board_date = models.DateTimeField(null=True, blank=True)
36 chipset = models.CharField(max_length=200, null=True, blank=True)
37 tags = TaggableManager(blank=True)
Dirk Vogtf2a33422016-10-11 17:17:26 +020038 last_heartbeat = models.DateTimeField(null=True, blank=True)
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020039 token = models.CharField(max_length=200, null=True, blank=True)
Dirk Vogt67eb1482016-10-13 12:42:56 +020040 next_per_crashreport_key = models.PositiveIntegerField(default=1)
41 next_per_heartbeat_key = models.PositiveIntegerField(default=1)
42
43 @transaction.atomic
44 def get_crashreport_key(self):
Mitja Nikolaus6a679132018-08-30 14:35:29 +020045 """Get the next key for a crashreport and update the ID-counter."""
Dirk Vogt67eb1482016-10-13 12:42:56 +020046 ret = self.next_per_crashreport_key
47 self.next_per_crashreport_key = self.next_per_crashreport_key + 1
48 self.save()
49 return ret
50
51 @transaction.atomic
52 def get_heartbeat_key(self):
Mitja Nikolaus6a679132018-08-30 14:35:29 +020053 """Get the next key for a heartbeat and update the ID-counter."""
Dirk Vogt67eb1482016-10-13 12:42:56 +020054 ret = self.next_per_heartbeat_key
Dirk Vogt0d9d5d22016-10-13 16:17:57 +020055 self.next_per_heartbeat_key = self.next_per_heartbeat_key + 1
Dirk Vogt67eb1482016-10-13 12:42:56 +020056 self.save()
57 return ret
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020058
Dirk Vogtf130c752016-08-23 14:45:01 +020059
60def crashreport_file_name(instance, filename):
Mitja Nikolaus6a679132018-08-30 14:35:29 +020061 """Generate the full path for new uploaded log files.
62
Mitja Nikolausb6cf6972018-10-04 15:03:31 +020063 The path is created by splitting up the device UUID into 3 parts: The
64 first 2 characters, the second 2 characters and the rest. This way the
65 number of directories in each subdirectory does not get too big.
66
Mitja Nikolaus6a679132018-08-30 14:35:29 +020067 Args:
68 instance: The log file instance.
69 filename: The name of the actual log file.
70
71 Returns: The generated path including the file name.
72
73 """
Mitja Nikolausb6cf6972018-10-04 15:03:31 +020074 return os.path.join(
Mitja Nikolausb6cf6972018-10-04 15:03:31 +020075 str(instance.crashreport.device.uuid)[0:2],
76 str(instance.crashreport.device.uuid)[2:4],
77 str(instance.crashreport.device.uuid)[4:],
78 str(instance.crashreport.id),
79 filename,
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020080 )
Dirk Vogtf130c752016-08-23 14:45:01 +020081
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020082
Dirk Vogtf130c752016-08-23 14:45:01 +020083class Crashreport(models.Model):
Mitja Nikolaus6a679132018-08-30 14:35:29 +020084 """A crashreport that was sent by a device."""
85
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020086 BOOT_REASON_UNKOWN = "UNKNOWN"
87 BOOT_REASON_KEYBOARD_POWER_ON = "keyboard power on"
88 BOOT_REASON_RTC_ALARM = "RTC alarm"
89 CRASH_BOOT_REASONS = [BOOT_REASON_UNKOWN, BOOT_REASON_KEYBOARD_POWER_ON]
90 SMPL_BOOT_REASONS = [BOOT_REASON_RTC_ALARM]
Franz-Xaver Geiger0b3a48e2018-04-16 15:00:14 +020091
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020092 device = models.ForeignKey(
93 Device,
94 db_index=True,
95 related_name="crashreports",
96 on_delete=models.CASCADE,
97 )
Dirk Vogtc9e10ab2016-10-12 13:58:15 +020098 is_fake_report = models.BooleanField(default=False)
99 app_version = models.IntegerField()
100 uptime = models.CharField(max_length=200)
Dirk Vogt83107df2017-05-02 12:04:19 +0200101 build_fingerprint = models.CharField(db_index=True, max_length=200)
Borjan Tchakaloff6f239a62018-02-19 09:05:50 +0100102 radio_version = models.CharField(db_index=True, max_length=200, null=True)
Dirk Vogt83107df2017-05-02 12:04:19 +0200103 boot_reason = models.CharField(db_index=True, max_length=200)
104 power_on_reason = models.CharField(db_index=True, max_length=200)
105 power_off_reason = models.CharField(db_index=True, max_length=200)
106 date = models.DateTimeField(db_index=True)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200107 tags = TaggableManager(blank=True)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200108 device_local_id = models.PositiveIntegerField(blank=True)
Dirk Vogt36635692016-10-17 12:19:10 +0200109 next_logfile_key = models.PositiveIntegerField(default=1)
Dirk Vogteda80d32016-11-21 11:45:50 +0100110 created_at = models.DateTimeField(auto_now_add=True)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200111
112 @transaction.atomic
113 def get_logfile_key(self):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200114 """Get the next key for a log file and update the ID-counter."""
Dirk Vogt67eb1482016-10-13 12:42:56 +0200115 ret = self.next_logfile_key
116 self.next_logfile_key = self.next_logfile_key + 1
117 self.save()
118 return ret
119
Mitja Nikolaus773d0cd2018-08-31 10:55:43 +0200120 def save(
121 self,
122 force_insert=False,
123 force_update=False,
124 using=None,
125 update_fields=None,
126 ):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200127 """Save the crashreport and set its local ID if it was not set."""
Dirk Vogt67eb1482016-10-13 12:42:56 +0200128 if not self.device_local_id:
129 self.device_local_id = self.device.get_crashreport_key()
Mitja Nikolaus773d0cd2018-08-31 10:55:43 +0200130 super(Crashreport, self).save(
131 force_insert, force_update, using, update_fields
132 )
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200133
Dirk Vogtf2a33422016-10-11 17:17:26 +0200134 def _get_uuid(self):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200135 """Return the device UUID."""
Dirk Vogtf2a33422016-10-11 17:17:26 +0200136 return self.device.uuid
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200137
Dirk Vogtf2a33422016-10-11 17:17:26 +0200138 uuid = property(_get_uuid)
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200139
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200140
Dirk Vogtf2a33422016-10-11 17:17:26 +0200141class LogFile(models.Model):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200142 """A log file that was sent along with a crashreport."""
143
Dirk Vogt7160b5e2016-10-12 17:04:40 +0200144 logfile_type = models.TextField(max_length=36, default="last_kmsg")
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200145 crashreport = models.ForeignKey(
146 Crashreport, related_name="logfiles", on_delete=models.CASCADE
147 )
Dirk Vogteda80d32016-11-21 11:45:50 +0100148 logfile = models.FileField(upload_to=crashreport_file_name, max_length=500)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200149 crashreport_local_id = models.PositiveIntegerField(blank=True)
Dirk Vogteda80d32016-11-21 11:45:50 +0100150 created_at = models.DateTimeField(auto_now_add=True)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200151
Mitja Nikolaus773d0cd2018-08-31 10:55:43 +0200152 def save(
153 self,
154 force_insert=False,
155 force_update=False,
156 using=None,
157 update_fields=None,
158 ):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200159 """Save the log file and set its local ID if it was not set."""
Dirk Vogt36635692016-10-17 12:19:10 +0200160 if not self.crashreport_local_id:
161 self.crashreport_local_id = self.crashreport.get_logfile_key()
Mitja Nikolaus773d0cd2018-08-31 10:55:43 +0200162 super(LogFile, self).save(
163 force_insert, force_update, using, update_fields
164 )
Dirk Vogtf2a33422016-10-11 17:17:26 +0200165
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200166
Mitja Nikolauscc90d572018-11-22 16:40:15 +0100167@receiver(models.signals.post_delete, sender=LogFile)
168def auto_delete_file_on_delete(sender, instance, **kwargs):
169 """Delete the file from the filesystem on deletion of the db instance."""
170 # pylint: disable=unused-argument
171
172 if instance.logfile:
173 if os.path.isfile(instance.logfile.path):
174 instance.logfile.delete(save=False)
175
176
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200177class HeartBeat(models.Model):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200178 """A heartbeat that was sent by a device."""
179
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200180 device = models.ForeignKey(
181 Device,
Dirk Vogt83107df2017-05-02 12:04:19 +0200182 db_index=True,
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200183 related_name="heartbeats",
184 on_delete=models.CASCADE,
185 )
Dirk Vogt1433f7c2016-09-20 15:30:56 +0200186 app_version = models.IntegerField()
Dirk Vogtf130c752016-08-23 14:45:01 +0200187 uptime = models.CharField(max_length=200)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200188 build_fingerprint = models.CharField(db_index=True, max_length=200)
Borjan Tchakaloff6f239a62018-02-19 09:05:50 +0100189 radio_version = models.CharField(db_index=True, max_length=200, null=True)
Dirk Vogt83107df2017-05-02 12:04:19 +0200190 date = models.DateTimeField(db_index=True)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200191 device_local_id = models.PositiveIntegerField(blank=True)
Dirk Vogteda80d32016-11-21 11:45:50 +0100192 created_at = models.DateTimeField(auto_now_add=True)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200193
Mitja Nikolaus773d0cd2018-08-31 10:55:43 +0200194 def save(
195 self,
196 force_insert=False,
197 force_update=False,
198 using=None,
199 update_fields=None,
200 ):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200201 """Save the heartbeat and set its local ID if it was not set."""
Dirk Vogt67eb1482016-10-13 12:42:56 +0200202 if not self.device_local_id:
203 self.device_local_id = self.device.get_heartbeat_key()
Mitja Nikolaus773d0cd2018-08-31 10:55:43 +0200204 super(HeartBeat, self).save(
205 force_insert, force_update, using, update_fields
206 )
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200207
208 def _get_uuid(self):
Mitja Nikolaus6a679132018-08-30 14:35:29 +0200209 """Return the device UUID."""
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200210 return self.device.uuid
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200211
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200212 uuid = property(_get_uuid)