[locations] Try another tack for whether we've moved or not
This commit is contained in:
@ -51,21 +51,6 @@ class GeoLocation(ScrobblableMixin):
|
|||||||
logger.error("No lat or lon keys in data dict")
|
logger.error("No lat or lon keys in data dict")
|
||||||
return
|
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(".")
|
int_alt, r_alt = str(data_dict.get("alt", "")).split(".")
|
||||||
|
|
||||||
data_dict["altitude"] = float(int_alt)
|
data_dict["altitude"] = float(int_alt)
|
||||||
|
|||||||
@ -33,6 +33,7 @@ from scrobbles.stats import build_charts
|
|||||||
from scrobbles.utils import (
|
from scrobbles.utils import (
|
||||||
check_long_play_for_finish,
|
check_long_play_for_finish,
|
||||||
check_scrobble_for_finish,
|
check_scrobble_for_finish,
|
||||||
|
media_class_to_foreign_key,
|
||||||
)
|
)
|
||||||
from sports.models import SportEvent
|
from sports.models import SportEvent
|
||||||
from videogames import retroarch
|
from videogames import retroarch
|
||||||
@ -476,6 +477,15 @@ class Scrobble(TimeStampedModel):
|
|||||||
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
||||||
WEBPAGE = "WebPage", "Web Page"
|
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)
|
uuid = models.UUIDField(editable=False, **BNULL)
|
||||||
video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL)
|
video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
track = models.ForeignKey(Track, 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:
|
if not self.uuid:
|
||||||
self.uuid = uuid4()
|
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__)
|
self.media_type = self.MediaType(self.media_obj.__class__.__name__)
|
||||||
|
|
||||||
return super(Scrobble, self).save(*args, **kwargs)
|
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
|
@property
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
if self.is_paused:
|
if self.is_paused:
|
||||||
@ -592,25 +608,31 @@ class Scrobble(TimeStampedModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def previous_all(self) -> "Scrobble":
|
def previous_by_media(self) -> "Scrobble":
|
||||||
return (
|
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")
|
.order_by("-timestamp")
|
||||||
.filter(timestamp__lt=self.timestamp)
|
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def next_all(self) -> "Scrobble":
|
def next_by_media(self) -> "Scrobble":
|
||||||
return (
|
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")
|
.order_by("-timestamp")
|
||||||
.filter(timestamp__gt=self.timestamp)
|
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def previous_all_media(self) -> "Scrobble":
|
def previous_by_user(self) -> "Scrobble":
|
||||||
return (
|
return (
|
||||||
Scrobble.objects.order_by("-timestamp")
|
Scrobble.objects.order_by("-timestamp")
|
||||||
.filter(timestamp__lt=self.timestamp)
|
.filter(timestamp__lt=self.timestamp)
|
||||||
@ -618,7 +640,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def next_all_media(self) -> "Scrobble":
|
def next_by_user(self) -> "Scrobble":
|
||||||
return (
|
return (
|
||||||
Scrobble.objects.order_by("-timestamp")
|
Scrobble.objects.order_by("-timestamp")
|
||||||
.filter(timestamp__gt=self.timestamp)
|
.filter(timestamp__gt=self.timestamp)
|
||||||
@ -683,40 +705,51 @@ class Scrobble(TimeStampedModel):
|
|||||||
logger.info(f"No - stale - {self.id} - {self.source}")
|
logger.info(f"No - stale - {self.id} - {self.source}")
|
||||||
updatable = False
|
updatable = False
|
||||||
if self.media_obj.__class__.__name__ in ["GeoLocation"]:
|
if self.media_obj.__class__.__name__ in ["GeoLocation"]:
|
||||||
if self.previous_all:
|
updatable = not self.has_moved
|
||||||
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
|
|
||||||
return updatable
|
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
|
@property
|
||||||
def media_obj(self):
|
def media_obj(self):
|
||||||
media_obj = None
|
media_obj = None
|
||||||
@ -763,47 +796,9 @@ class Scrobble(TimeStampedModel):
|
|||||||
def create_or_update(
|
def create_or_update(
|
||||||
cls, media, user_id: int, scrobble_data: dict, **kwargs
|
cls, media, user_id: int, scrobble_data: dict, **kwargs
|
||||||
) -> "Scrobble":
|
) -> "Scrobble":
|
||||||
|
key = media_class_to_foreign_key(media.__class__.__name__)
|
||||||
media_class = media.__class__.__name__
|
media_query = models.Q(**{key: media})
|
||||||
dup = None
|
scrobble_data[key + "_id"] = media.id
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
scrobble = (
|
scrobble = (
|
||||||
cls.objects.filter(
|
cls.objects.filter(
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from datetime import datetime, timedelta, tzinfo
|
from datetime import datetime, timedelta, tzinfo
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ User = get_user_model()
|
|||||||
|
|
||||||
PODCAST_DATE_FORMAT = "YYYY-MM-DD"
|
PODCAST_DATE_FORMAT = "YYYY-MM-DD"
|
||||||
|
|
||||||
|
|
||||||
def timestamp_user_tz_to_utc(timestamp: int, user_tz: tzinfo) -> datetime:
|
def timestamp_user_tz_to_utc(timestamp: int, user_tz: tzinfo) -> datetime:
|
||||||
return user_tz.localize(datetime.utcfromtimestamp(timestamp)).astimezone(
|
return user_tz.localize(datetime.utcfromtimestamp(timestamp)).astimezone(
|
||||||
pytz.utc
|
pytz.utc
|
||||||
@ -42,6 +44,7 @@ def convert_to_seconds(run_time: str) -> int:
|
|||||||
run_time_int = int((((hours * 60) + minutes) * 60) + seconds)
|
run_time_int = int((((hours * 60) + minutes) * 60) + seconds)
|
||||||
return run_time_int
|
return run_time_int
|
||||||
|
|
||||||
|
|
||||||
def parse_mopidy_uri(uri: str) -> dict:
|
def parse_mopidy_uri(uri: str) -> dict:
|
||||||
logger.debug(f"Parsing URI: {uri}")
|
logger.debug(f"Parsing URI: {uri}")
|
||||||
parsed_uri = os.path.splitext(unquote(uri))[0].split("/")
|
parsed_uri = os.path.splitext(unquote(uri))[0].split("/")
|
||||||
@ -60,7 +63,12 @@ def parse_mopidy_uri(uri: str) -> dict:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Beacuse we have epsiode numbers on
|
# 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:
|
except ParserError:
|
||||||
pub_date = ""
|
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"
|
f"Found {zombies_found} zombie scrobbles to delete, use dry_run=False to proceed"
|
||||||
)
|
)
|
||||||
return zombies_found
|
return zombies_found
|
||||||
|
|
||||||
|
|
||||||
|
def media_class_to_foreign_key(media_class: str) -> str:
|
||||||
|
return re.sub(r"(?<!^)(?=[A-Z])", "_", media_class).lower()
|
||||||
|
|||||||
Reference in New Issue
Block a user