[scrobbles] Blacken files and add local dev static
This commit is contained in:
@ -58,9 +58,7 @@ logger = logging.getLogger(__name__)
|
||||
User = get_user_model()
|
||||
BNULL = {"blank": True, "null": True}
|
||||
|
||||
POINTS_FOR_MOVEMENT_HISTORY = int(
|
||||
getattr(settings, "POINTS_FOR_MOVEMENT_HISTORY", 3)
|
||||
)
|
||||
POINTS_FOR_MOVEMENT_HISTORY = int(getattr(settings, "POINTS_FOR_MOVEMENT_HISTORY", 3))
|
||||
|
||||
|
||||
class BaseFileImportMixin(TimeStampedModel):
|
||||
@ -96,7 +94,6 @@ class BaseFileImportMixin(TimeStampedModel):
|
||||
from scrobbles.models import Scrobble
|
||||
|
||||
if not self.process_log:
|
||||
|
||||
logger.warning("No lines in process log found to undo")
|
||||
return
|
||||
|
||||
@ -104,9 +101,7 @@ class BaseFileImportMixin(TimeStampedModel):
|
||||
scrobble_id = line.split("\t")[0]
|
||||
scrobble = Scrobble.objects.filter(id=scrobble_id).first()
|
||||
if not scrobble:
|
||||
logger.warning(
|
||||
f"Could not find scrobble {scrobble_id} to undo"
|
||||
)
|
||||
logger.warning(f"Could not find scrobble {scrobble_id} to undo")
|
||||
continue
|
||||
logger.info(f"Removing scrobble {scrobble_id}")
|
||||
if not dryrun:
|
||||
@ -149,9 +144,7 @@ class BaseFileImportMixin(TimeStampedModel):
|
||||
return
|
||||
|
||||
for count, scrobble in enumerate(scrobbles):
|
||||
scrobble_str = (
|
||||
f"{scrobble.id}\t{scrobble.timestamp}\t{scrobble.media_obj}"
|
||||
)
|
||||
scrobble_str = f"{scrobble.id}\t{scrobble.timestamp}\t{scrobble.media_obj}"
|
||||
log_line = f"{scrobble_str}"
|
||||
if count > 0:
|
||||
log_line = "\n" + log_line
|
||||
@ -173,9 +166,7 @@ class KoReaderImport(BaseFileImportMixin):
|
||||
return "KOReader"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"scrobbles:koreader-import-detail", kwargs={"slug": self.uuid}
|
||||
)
|
||||
return reverse("scrobbles:koreader-import-detail", kwargs={"slug": self.uuid})
|
||||
|
||||
def get_path(instance, filename):
|
||||
extension = filename.split(".")[-1]
|
||||
@ -211,15 +202,11 @@ class KoReaderImport(BaseFileImportMixin):
|
||||
fix_profile_historic_timezones(self.user.profile)
|
||||
|
||||
if self.processed_finished and not force:
|
||||
logger.info(
|
||||
f"{self} already processed on {self.processed_finished}"
|
||||
)
|
||||
logger.info(f"{self} already processed on {self.processed_finished}")
|
||||
return
|
||||
|
||||
self.mark_started()
|
||||
scrobbles = process_koreader_sqlite_file(
|
||||
self.upload_file_path, self.user.id
|
||||
)
|
||||
scrobbles = process_koreader_sqlite_file(self.upload_file_path, self.user.id)
|
||||
self.record_log(scrobbles)
|
||||
self.mark_finished()
|
||||
|
||||
@ -233,9 +220,7 @@ class AudioScrobblerTSVImport(BaseFileImportMixin):
|
||||
return "AudiosScrobbler"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"scrobbles:tsv-import-detail", kwargs={"slug": self.uuid}
|
||||
)
|
||||
return reverse("scrobbles:tsv-import-detail", kwargs={"slug": self.uuid})
|
||||
|
||||
def get_path(instance, filename):
|
||||
extension = filename.split(".")[-1]
|
||||
@ -259,16 +244,12 @@ class AudioScrobblerTSVImport(BaseFileImportMixin):
|
||||
fix_profile_historic_timezones(self.user.profile)
|
||||
|
||||
if self.processed_finished and not force:
|
||||
logger.info(
|
||||
f"{self} already processed on {self.processed_finished}"
|
||||
)
|
||||
logger.info(f"{self} already processed on {self.processed_finished}")
|
||||
return
|
||||
|
||||
self.mark_started()
|
||||
|
||||
scrobbles = import_audioscrobbler_tsv_file(
|
||||
self.upload_file_path, self.user.id
|
||||
)
|
||||
scrobbles = import_audioscrobbler_tsv_file(self.upload_file_path, self.user.id)
|
||||
self.record_log(scrobbles)
|
||||
self.mark_finished()
|
||||
|
||||
@ -282,9 +263,7 @@ class LastFmImport(BaseFileImportMixin):
|
||||
return "LastFM"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"scrobbles:lastfm-import-detail", kwargs={"slug": self.uuid}
|
||||
)
|
||||
return reverse("scrobbles:lastfm-import-detail", kwargs={"slug": self.uuid})
|
||||
|
||||
def process(self, import_all=False):
|
||||
"""Import scrobbles found on LastFM"""
|
||||
@ -293,9 +272,7 @@ class LastFmImport(BaseFileImportMixin):
|
||||
fix_profile_historic_timezones(self.user.profile)
|
||||
|
||||
if self.processed_finished:
|
||||
logger.info(
|
||||
f"{self} already processed on {self.processed_finished}"
|
||||
)
|
||||
logger.info(f"{self} already processed on {self.processed_finished}")
|
||||
return
|
||||
|
||||
last_import = None
|
||||
@ -333,9 +310,7 @@ class RetroarchImport(BaseFileImportMixin):
|
||||
return "Retroarch"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"scrobbles:retroarch-import-detail", kwargs={"slug": self.uuid}
|
||||
)
|
||||
return reverse("scrobbles:retroarch-import-detail", kwargs={"slug": self.uuid})
|
||||
|
||||
def process(self, import_all=False, force=False):
|
||||
"""Import scrobbles found on Retroarch"""
|
||||
@ -343,9 +318,7 @@ class RetroarchImport(BaseFileImportMixin):
|
||||
fix_profile_historic_timezones(self.user.profile)
|
||||
|
||||
if self.processed_finished and not force:
|
||||
logger.info(
|
||||
f"{self} already processed on {self.processed_finished}"
|
||||
)
|
||||
logger.info(f"{self} already processed on {self.processed_finished}")
|
||||
return
|
||||
|
||||
if force:
|
||||
@ -536,39 +509,25 @@ class Scrobble(TimeStampedModel):
|
||||
podcast_episode = models.ForeignKey(
|
||||
PodcastEpisode, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
sport_event = models.ForeignKey(
|
||||
SportEvent, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
sport_event = models.ForeignKey(SportEvent, on_delete=models.DO_NOTHING, **BNULL)
|
||||
book = models.ForeignKey(Book, on_delete=models.DO_NOTHING, **BNULL)
|
||||
paper = models.ForeignKey(Paper, on_delete=models.DO_NOTHING, **BNULL)
|
||||
video_game = models.ForeignKey(
|
||||
VideoGame, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
board_game = models.ForeignKey(
|
||||
BoardGame, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
geo_location = models.ForeignKey(
|
||||
GeoLocation, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
video_game = models.ForeignKey(VideoGame, on_delete=models.DO_NOTHING, **BNULL)
|
||||
board_game = models.ForeignKey(BoardGame, on_delete=models.DO_NOTHING, **BNULL)
|
||||
geo_location = models.ForeignKey(GeoLocation, on_delete=models.DO_NOTHING, **BNULL)
|
||||
beer = models.ForeignKey(Beer, on_delete=models.DO_NOTHING, **BNULL)
|
||||
puzzle = models.ForeignKey(Puzzle, on_delete=models.DO_NOTHING, **BNULL)
|
||||
food = models.ForeignKey(Food, on_delete=models.DO_NOTHING, **BNULL)
|
||||
trail = models.ForeignKey(Trail, on_delete=models.DO_NOTHING, **BNULL)
|
||||
task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, **BNULL)
|
||||
web_page = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL)
|
||||
life_event = models.ForeignKey(
|
||||
LifeEvent, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
life_event = models.ForeignKey(LifeEvent, on_delete=models.DO_NOTHING, **BNULL)
|
||||
mood = models.ForeignKey(Mood, on_delete=models.DO_NOTHING, **BNULL)
|
||||
brick_set = models.ForeignKey(
|
||||
BrickSet, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
brick_set = models.ForeignKey(BrickSet, on_delete=models.DO_NOTHING, **BNULL)
|
||||
media_type = models.CharField(
|
||||
max_length=14, choices=MediaType.choices, default=MediaType.VIDEO
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
User, blank=True, null=True, on_delete=models.DO_NOTHING
|
||||
)
|
||||
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.DO_NOTHING)
|
||||
|
||||
# Time keeping
|
||||
timestamp = models.DateTimeField(**BNULL)
|
||||
@ -596,9 +555,7 @@ class Scrobble(TimeStampedModel):
|
||||
upload_to="scrobbles/videogame_save_data/", **BNULL
|
||||
)
|
||||
gpx_file = models.FileField(upload_to="scrobbles/gpx_file/", **BNULL)
|
||||
screenshot = models.ImageField(
|
||||
upload_to="scrobbles/videogame_screenshot/", **BNULL
|
||||
)
|
||||
screenshot = models.ImageField(upload_to="scrobbles/videogame_screenshot/", **BNULL)
|
||||
screenshot_small = ImageSpecField(
|
||||
source="screenshot",
|
||||
processors=[ResizeToFit(100, 100)],
|
||||
@ -614,6 +571,16 @@ class Scrobble(TimeStampedModel):
|
||||
long_play_seconds = models.BigIntegerField(**BNULL)
|
||||
long_play_complete = models.BooleanField(**BNULL)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=["timestamp"]),
|
||||
models.Index(fields=["media_type"]),
|
||||
models.Index(fields=["source_id"]),
|
||||
models.Index(
|
||||
fields=["user", "in_progress", "played_to_completion", "is_paused"]
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def for_year(cls, user, year):
|
||||
return cls.objects.filter(timestamp__year=year, user=user).order_by(
|
||||
@ -679,9 +646,7 @@ class Scrobble(TimeStampedModel):
|
||||
from scrobbles.models import Scrobble
|
||||
|
||||
if self.logdata and self.logdata.serial_scrobble_id:
|
||||
return Scrobble.objects.filter(
|
||||
id=self.logdata.serial_scrobble_id
|
||||
).first()
|
||||
return Scrobble.objects.filter(id=self.logdata.serial_scrobble_id).first()
|
||||
|
||||
@property
|
||||
def finish_url(self) -> str:
|
||||
@ -703,8 +668,12 @@ class Scrobble(TimeStampedModel):
|
||||
if self.media_obj:
|
||||
self.media_type = self.MediaType(self.media_obj.__class__.__name__)
|
||||
|
||||
if (self.timestamp and self.stop_timestamp) and (not self.playback_position_seconds or self.playback_position_seconds <= 0):
|
||||
self.playback_position_seconds = (self.stop_timestamp - self.timestamp).seconds
|
||||
if (self.timestamp and self.stop_timestamp) and (
|
||||
not self.playback_position_seconds or self.playback_position_seconds <= 0
|
||||
):
|
||||
self.playback_position_seconds = (
|
||||
self.stop_timestamp - self.timestamp
|
||||
).seconds
|
||||
|
||||
return super(Scrobble, self).save(*args, **kwargs)
|
||||
|
||||
@ -715,9 +684,9 @@ class Scrobble(TimeStampedModel):
|
||||
return reverse("scrobbles:detail", kwargs={"uuid": self.uuid})
|
||||
|
||||
def push_to_archivebox(self):
|
||||
pushable_media = hasattr(
|
||||
self.media_obj, "push_to_archivebox"
|
||||
) and callable(self.media_obj.push_to_archivebox)
|
||||
pushable_media = hasattr(self.media_obj, "push_to_archivebox") and callable(
|
||||
self.media_obj.push_to_archivebox
|
||||
)
|
||||
|
||||
if pushable_media and self.user.profile.archivebox_url:
|
||||
try:
|
||||
@ -781,10 +750,7 @@ class Scrobble(TimeStampedModel):
|
||||
logger.info(f"Redirecting to {self.media_obj.url}")
|
||||
redirect_url = self.media_obj.url
|
||||
|
||||
if (
|
||||
self.media_type == self.MediaType.VIDEO
|
||||
and self.media_obj.youtube_id
|
||||
):
|
||||
if self.media_type == self.MediaType.VIDEO and self.media_obj.youtube_id:
|
||||
redirect_url = self.media_obj.youtube_link
|
||||
|
||||
return redirect_url
|
||||
@ -800,9 +766,7 @@ class Scrobble(TimeStampedModel):
|
||||
@property
|
||||
def local_stop_timestamp(self):
|
||||
if self.stop_timestamp:
|
||||
return timezone.localtime(
|
||||
self.stop_timestamp, timezone=self.tzinfo
|
||||
)
|
||||
return timezone.localtime(self.stop_timestamp, timezone=self.tzinfo)
|
||||
|
||||
@property
|
||||
def scrobble_media_key(self) -> str:
|
||||
@ -930,9 +894,7 @@ class Scrobble(TimeStampedModel):
|
||||
long_play_secs = 0
|
||||
if self.previous and not self.previous.long_play_complete:
|
||||
long_play_secs = self.previous.long_play_seconds or 0
|
||||
percent = int(
|
||||
((playback_seconds + long_play_secs) / run_time_secs) * 100
|
||||
)
|
||||
percent = int(((playback_seconds + long_play_secs) / run_time_secs) * 100)
|
||||
|
||||
return percent
|
||||
|
||||
@ -977,9 +939,7 @@ class Scrobble(TimeStampedModel):
|
||||
expected_end = self.timestamp + datetime.timedelta(
|
||||
seconds=self.media_obj.run_time_seconds
|
||||
)
|
||||
expected_end_padded = expected_end + datetime.timedelta(
|
||||
seconds=padding_seconds
|
||||
)
|
||||
expected_end_padded = expected_end + datetime.timedelta(seconds=padding_seconds)
|
||||
# Take our start time, add our media length and an extra 30 min (1800s) is it still in the future? keep going
|
||||
is_in_progress = expected_end_padded > pendulum.now()
|
||||
logger.info(
|
||||
@ -995,7 +955,10 @@ class Scrobble(TimeStampedModel):
|
||||
|
||||
@property
|
||||
def can_be_updated(self) -> bool:
|
||||
if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values() and self.source != "readcomicsonline.ru":
|
||||
if (
|
||||
self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values()
|
||||
and self.source != "readcomicsonline.ru"
|
||||
):
|
||||
logger.info(
|
||||
"[scrobbling] cannot be updated, long play media",
|
||||
extra={
|
||||
@ -1039,11 +1002,9 @@ class Scrobble(TimeStampedModel):
|
||||
|
||||
@classmethod
|
||||
def by_date(cls, media_type: str = "Track"):
|
||||
cls.objects.filter(media_type=media_type).values(
|
||||
"timestamp__date"
|
||||
).annotate(count=models.Count("id")).values(
|
||||
"timestamp__date", "count"
|
||||
).order_by(
|
||||
cls.objects.filter(media_type=media_type).values("timestamp__date").annotate(
|
||||
count=models.Count("id")
|
||||
).values("timestamp__date", "count").order_by(
|
||||
"-count",
|
||||
)
|
||||
|
||||
@ -1150,7 +1111,10 @@ class Scrobble(TimeStampedModel):
|
||||
if existing_by_source_id:
|
||||
logger.info(
|
||||
"[create_or_update] found existing scrobble by source_id, updating",
|
||||
extra={"scrobble_id": existing_by_source_id.id, "source_id": source_id},
|
||||
extra={
|
||||
"scrobble_id": existing_by_source_id.id,
|
||||
"source_id": source_id,
|
||||
},
|
||||
)
|
||||
scrobble_data["playback_status"] = scrobble_data.pop("status", None)
|
||||
return existing_by_source_id.update(scrobble_data)
|
||||
@ -1172,9 +1136,7 @@ class Scrobble(TimeStampedModel):
|
||||
logger.warning(
|
||||
f"[create_or_update] geoloc requires create_or_update_location"
|
||||
)
|
||||
scrobble = cls.create_or_update_location(
|
||||
media, scrobble_data, user_id
|
||||
)
|
||||
scrobble = cls.create_or_update_location(media, scrobble_data, user_id)
|
||||
return scrobble
|
||||
|
||||
if not skip_in_progress_check or read_log_page:
|
||||
@ -1187,9 +1149,7 @@ class Scrobble(TimeStampedModel):
|
||||
"scrobble_data": scrobble_data,
|
||||
},
|
||||
)
|
||||
scrobble_data["playback_status"] = scrobble_data.pop(
|
||||
"status", None
|
||||
)
|
||||
scrobble_data["playback_status"] = scrobble_data.pop("status", None)
|
||||
# If it's marked as stopped, send it through our update mechanism, which will complete it
|
||||
if scrobble and (
|
||||
scrobble.can_be_updated
|
||||
@ -1207,7 +1167,7 @@ class Scrobble(TimeStampedModel):
|
||||
page_list.append(
|
||||
BookPageLogData(
|
||||
page_number=read_log_page,
|
||||
start_ts=int(timezone.now().timestamp())
|
||||
start_ts=int(timezone.now().timestamp()),
|
||||
)
|
||||
)
|
||||
scrobble.log["page_data"] = page_list
|
||||
@ -1220,7 +1180,14 @@ class Scrobble(TimeStampedModel):
|
||||
scrobble_data.pop("playback_status")
|
||||
|
||||
if read_log_page:
|
||||
scrobble_data["log"] = BookLogData(page_data=[BookPageLogData(page_number=read_log_page, start_ts=int(timezone.now().timestamp()))])
|
||||
scrobble_data["log"] = BookLogData(
|
||||
page_data=[
|
||||
BookPageLogData(
|
||||
page_number=read_log_page,
|
||||
start_ts=int(timezone.now().timestamp()),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[scrobbling] creating new scrobble",
|
||||
@ -1231,7 +1198,9 @@ class Scrobble(TimeStampedModel):
|
||||
"source": source,
|
||||
},
|
||||
)
|
||||
if mtype == cls.MediaType.FOOD and not scrobble_data.get("log", {}).get("calories", None):
|
||||
if mtype == cls.MediaType.FOOD and not scrobble_data.get("log", {}).get(
|
||||
"calories", None
|
||||
):
|
||||
if media.calories:
|
||||
scrobble_data["log"] = FoodLogData(calories=media.calories)
|
||||
|
||||
@ -1318,9 +1287,7 @@ class Scrobble(TimeStampedModel):
|
||||
if existing_locations := location.in_proximity(named=True):
|
||||
existing_location = existing_locations.first()
|
||||
ts = int(pendulum.now().timestamp())
|
||||
scrobble.log[
|
||||
ts
|
||||
] = f"Location {location.id} too close to this scrobble"
|
||||
scrobble.log[ts] = f"Location {location.id} too close to this scrobble"
|
||||
scrobble.save(update_fields=["log"])
|
||||
logger.info(
|
||||
f"[scrobbling] finished - found existing named location",
|
||||
@ -1499,9 +1466,7 @@ class Scrobble(TimeStampedModel):
|
||||
"""Returns true if our media is beyond our completion percent, unless
|
||||
our type is geolocation in which case we always return false
|
||||
"""
|
||||
beyond_completion = (
|
||||
self.percent_played >= self.media_obj.COMPLETION_PERCENT
|
||||
)
|
||||
beyond_completion = self.percent_played >= self.media_obj.COMPLETION_PERCENT
|
||||
|
||||
if self.media_type == "GeoLocation":
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user