From 033e3c3b356b619d17caad29c81ffc060fd75282 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sat, 10 Feb 2024 13:18:45 -0500 Subject: [PATCH] [locations] Try another tack for whether we've moved or not --- vrobbler/apps/locations/models.py | 15 --- vrobbler/apps/scrobbles/models.py | 157 +++++++++++++++--------------- vrobbler/apps/scrobbles/utils.py | 14 ++- 3 files changed, 89 insertions(+), 97 deletions(-) diff --git a/vrobbler/apps/locations/models.py b/vrobbler/apps/locations/models.py index 8b480e2..8416cca 100644 --- a/vrobbler/apps/locations/models.py +++ b/vrobbler/apps/locations/models.py @@ -51,21 +51,6 @@ class GeoLocation(ScrobblableMixin): logger.error("No lat or lon keys in data dict") return - int_lat, r_lat = str(data_dict.get("lat", "")).split(".") - int_lon, r_lon = str(data_dict.get("lon", "")).split(".") - - try: - trunc_lat = r_lat[0:GEOLOC_ACCURACY] - except IndexError: - trunc_lat = r_lat - try: - trunc_lon = r_lon[0:GEOLOC_ACCURACY] - except IndexError: - trunc_lon = r_lon - - data_dict["lat"] = float(f"{int_lat}.{trunc_lat}") - data_dict["lon"] = float(f"{int_lon}.{trunc_lon}") - int_alt, r_alt = str(data_dict.get("alt", "")).split(".") data_dict["altitude"] = float(int_alt) diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 44f350a..8befc7e 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -33,6 +33,7 @@ from scrobbles.stats import build_charts from scrobbles.utils import ( check_long_play_for_finish, check_scrobble_for_finish, + media_class_to_foreign_key, ) from sports.models import SportEvent from videogames import retroarch @@ -476,6 +477,15 @@ class Scrobble(TimeStampedModel): GEO_LOCATION = "GeoLocation", "GeoLocation" WEBPAGE = "WebPage", "Web Page" + media_ids = { + "Track": "track_id", + "Video": "video_id", + "Episode": "episode_id", + "SportEvent": "spoort_event_id", + "Track": "track_id", + "Track": "track_id", + } + uuid = models.UUIDField(editable=False, **BNULL) video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL) track = models.ForeignKey(Track, on_delete=models.DO_NOTHING, **BNULL) @@ -551,10 +561,16 @@ class Scrobble(TimeStampedModel): if not self.uuid: self.uuid = uuid4() + # Microseconds mess up Django's filtering, and we don't need be that specific + self.timestamp = self.timestamp.replace(microsecond=0) self.media_type = self.MediaType(self.media_obj.__class__.__name__) return super(Scrobble, self).save(*args, **kwargs) + @property + def scrobble_media_key(self) -> str: + return media_class_to_foreign_key(self.media_type) + "_id" + @property def status(self) -> str: if self.is_paused: @@ -592,25 +608,31 @@ class Scrobble(TimeStampedModel): ) @property - def previous_all(self) -> "Scrobble": + def previous_by_media(self) -> "Scrobble": return ( - Scrobble.objects.filter(media_type=self.media_type) + Scrobble.objects.filter( + media_type=self.media_type, + user=self.user, + timestamp__lt=self.timestamp, + ) .order_by("-timestamp") - .filter(timestamp__lt=self.timestamp) .first() ) @property - def next_all(self) -> "Scrobble": + def next_by_media(self) -> "Scrobble": return ( - Scrobble.objects.filter(media_type=self.media_type) + Scrobble.objects.filter( + media_type=self.media_type, + user=self.user, + timestamp__gt=self.timestamp, + ) .order_by("-timestamp") - .filter(timestamp__gt=self.timestamp) .first() ) @property - def previous_all_media(self) -> "Scrobble": + def previous_by_user(self) -> "Scrobble": return ( Scrobble.objects.order_by("-timestamp") .filter(timestamp__lt=self.timestamp) @@ -618,7 +640,7 @@ class Scrobble(TimeStampedModel): ) @property - def next_all_media(self) -> "Scrobble": + def next_by_user(self) -> "Scrobble": return ( Scrobble.objects.order_by("-timestamp") .filter(timestamp__gt=self.timestamp) @@ -683,40 +705,51 @@ class Scrobble(TimeStampedModel): logger.info(f"No - stale - {self.id} - {self.source}") updatable = False if self.media_obj.__class__.__name__ in ["GeoLocation"]: - if self.previous_all: - last_location = self.previous_all.media_obj - logger.info( - f"Calculate proximity to last scrobble: {last_location}" - ) - - same_lat = last_location.lat == self.media_obj.lat - same_lon = last_location.lon == self.media_obj.lon - same_title = False - if self.media_obj.title: - same_title = last_location.title == self.media_obj.title - - logger.info(f"{self.timestamp}") - logger.info( - f"Last lat: {last_location.lat}, last long {last_location.lon}, last title {last_location.title}" - ) - logger.info( - f"Our lat {self.media_obj.lat}, Our lon {self.media_obj.lon} or our title {self.media_obj.title}" - ) - - if (same_lat and same_lon) or same_title: - logger.info( - f"Yes - We're in the same place: {self.media_obj}" - ) - updatable = True - else: - logger.info( - f"No - We've moved, start a new scrobble: {self.media_obj}" - ) - # Stop the previous location scrobble - self.previous_all.stop() - updatable = False + updatable = not self.has_moved return updatable + @property + def loc_diff(self) -> tuple: + if self.media_type != self.MediaType.GEO_LOCATION: + logger.warn("Non-location scrobble, no diff") + return tuple() + + if not self.previous_by_media: + logger.warn(f"No previous location scrobble for {self}, no diff") + return tuple() + + if self.media_obj == self.previous_by_media.media_obj: + logger.warn("Previous scrobble is same location, no diff") + return tuple() + + return ( + abs(self.media_obj.lat - self.previous_by_media.media_obj.lat), + abs(self.media_obj.lon - self.previous_by_media.media_obj.lon), + ) + + @property + def has_moved(self) -> bool: + has_moved = False + + if self.media_type != self.MediaType.GEO_LOCATION: + logger.warn("Non-location scrobble means nothing") + return has_moved + + scrobble = self + all_moves = [] + for i in range(3): + loc_diff = self.loc_diff + if loc_diff and loc_diff[0] < 0.001 and loc_diff[1] > 0.001: + all_moves.append(True) + else: + all_moves.append(False) + scrobble = self.previous_by_media + + if not False in all_moves: + has_moved = True + + return has_moved + @property def media_obj(self): media_obj = None @@ -763,47 +796,9 @@ class Scrobble(TimeStampedModel): def create_or_update( cls, media, user_id: int, scrobble_data: dict, **kwargs ) -> "Scrobble": - - media_class = media.__class__.__name__ - dup = None - - media_query = models.Q(track=media) - if media_class == "Track": - scrobble_data["track_id"] = media.id - if media_class == "Video": - media_query = models.Q(video=media) - scrobble_data["video_id"] = media.id - if media_class == "Episode": - media_query = models.Q(podcast_episode=media) - scrobble_data["podcast_episode_id"] = media.id - if media_class == "SportEvent": - media_query = models.Q(sport_event=media) - scrobble_data["sport_event_id"] = media.id - if media_class == "Book": - media_query = models.Q(book=media) - scrobble_data["book_id"] = media.id - if media_class == "VideoGame": - media_query = models.Q(video_game=media) - scrobble_data["video_game_id"] = media.id - if media_class == "BoardGame": - media_query = models.Q(board_game=media) - scrobble_data["board_game_id"] = media.id - if media_class == "WebPage": - media_query = models.Q(webpage=media) - scrobble_data["webpage_id"] = media.id - if media_class == "GeoLocation": - media_query = models.Q(media_type=Scrobble.MediaType.GEO_LOCATION) - scrobble_data["geo_location_id"] = media.id - dup = cls.objects.filter( - media_type=cls.MediaType.GEO_LOCATION, - timestamp=scrobble_data.get("timestamp"), - ).first() - - if dup: - logger.info( - "[scrobbling] scrobble for geo location with identical timestamp found" - ) - return dup + key = media_class_to_foreign_key(media.__class__.__name__) + media_query = models.Q(**{key: media}) + scrobble_data[key + "_id"] = media.id scrobble = ( cls.objects.filter( diff --git a/vrobbler/apps/scrobbles/utils.py b/vrobbler/apps/scrobbles/utils.py index 5f99b0a..e7c419f 100644 --- a/vrobbler/apps/scrobbles/utils.py +++ b/vrobbler/apps/scrobbles/utils.py @@ -1,5 +1,6 @@ import logging import os +import re from datetime import datetime, timedelta, tzinfo from urllib.parse import unquote @@ -20,6 +21,7 @@ User = get_user_model() PODCAST_DATE_FORMAT = "YYYY-MM-DD" + def timestamp_user_tz_to_utc(timestamp: int, user_tz: tzinfo) -> datetime: return user_tz.localize(datetime.utcfromtimestamp(timestamp)).astimezone( pytz.utc @@ -42,6 +44,7 @@ def convert_to_seconds(run_time: str) -> int: run_time_int = int((((hours * 60) + minutes) * 60) + seconds) return run_time_int + def parse_mopidy_uri(uri: str) -> dict: logger.debug(f"Parsing URI: {uri}") parsed_uri = os.path.splitext(unquote(uri))[0].split("/") @@ -60,7 +63,12 @@ def parse_mopidy_uri(uri: str) -> dict: try: # Beacuse we have epsiode numbers on - pub_date = parse(episode_str[episode_num_pad:len(PODCAST_DATE_FORMAT) + episode_num_pad]) + pub_date = parse( + episode_str[ + episode_num_pad : len(PODCAST_DATE_FORMAT) + + episode_num_pad + ] + ) except ParserError: pub_date = "" @@ -264,3 +272,7 @@ def delete_zombie_scrobbles(dry_run=True): f"Found {zombies_found} zombie scrobbles to delete, use dry_run=False to proceed" ) return zombies_found + + +def media_class_to_foreign_key(media_class: str) -> str: + return re.sub(r"(?