Compare commits

...

10 Commits
55.2 ... 55.6

Author SHA1 Message Date
31888a85cb [release] Bump to version 55.6
All checks were successful
build / test (push) Successful in 1m56s
deploy / test (push) Successful in 1m54s
deploy / build-and-deploy (push) Successful in 30s
- Figure out why historical Lastfm imports don't work
2026-06-19 14:12:44 -04:00
22d8b0787e [importers] Allow starting full import for new user
Some checks failed
build / test (push) Has been cancelled
2026-06-19 14:12:09 -04:00
8cc559752b [release] Bump to version 55.5
All checks were successful
build / test (push) Successful in 2m5s
deploy / test (push) Successful in 3m12s
deploy / build-and-deploy (push) Successful in 31s
- Fix bug in lastfm import for new users
2026-06-19 14:06:10 -04:00
db3f9696fa [importers] Fix smol bug in lastfm importer
Some checks failed
build / test (push) Has been cancelled
2026-06-19 14:05:46 -04:00
407d570c82 [release] Bump to version 55.4
All checks were successful
build / test (push) Successful in 2m12s
deploy / test (push) Successful in 2m4s
deploy / build-and-deploy (push) Successful in 1m22s
- Tighten up the speed of startup and first request
2026-06-19 13:41:23 -04:00
033239260f [perf] Add caching and lock protections
All checks were successful
build / test (push) Successful in 1m57s
2026-06-19 13:37:30 -04:00
9f854dc735 [release] Bump to version 55.3
All checks were successful
build / test (push) Successful in 2m6s
deploy / test (push) Successful in 2m3s
deploy / build-and-deploy (push) Successful in 39s
- =alt_names= feature for artists (commented out / dead code)
- Put chart rebuilds in a lower priority task queue
- Check for existing book scrobble and update page count
2026-06-19 01:14:39 -04:00
f29272a853 [music] Clean up dead code
Some checks failed
build / test (push) Has been cancelled
2026-06-19 01:13:28 -04:00
4e56d9420a [settings] Put chart rebuilds in their own queue 2026-06-19 01:12:50 -04:00
852a257159 [scrobbles] Clean up a TODO already done
All checks were successful
build / test (push) Successful in 1m59s
2026-06-18 16:59:03 -04:00
17 changed files with 422 additions and 115 deletions

View File

@ -88,7 +88,7 @@ fetching and simple saving.
*** Metadata sources *** Metadata sources
**** Scraper **** Scraper
* Backlog [0/21] :vrobbler:project:personal: * Backlog [0/22] :vrobbler:project:personal:
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles:
:PROPERTIES: :PROPERTIES:
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb :ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
@ -579,6 +579,18 @@ named constants for maintainability.
- ~vrobbler/apps/scrobbles/importers/tsv.py~ (line 55) -- ="S"= completion status - ~vrobbler/apps/scrobbles/importers/tsv.py~ (line 55) -- ="S"= completion status
** TODO [#A] Deduplicate BGG plays before posting :boardgames:bgg:duplication:
:PROPERTIES:
:ID: e9b842bf-0049-42e7-a060-f3ebd0067d2f
:END:
*** Description
No check for existing BGG plays before posting, which can create duplicates.
Should look up past plays by =bggeek_id= first.
File: ~vrobbler/apps/boardgames/bgg.py~ (line 117)
** TODO [#C] Clean up naming of =bgsplay= parsing :importers:refactoring: ** TODO [#C] Clean up naming of =bgsplay= parsing :importers:refactoring:
:PROPERTIES: :PROPERTIES:
:ID: c751dbbc-464a-4e63-9fe3-e034303f7b54 :ID: c751dbbc-464a-4e63-9fe3-e034303f7b54
@ -591,6 +603,57 @@ a helper method to create board game scrobbles given a json blob. It's
independent of the email flow it was originally creatdd for independent of the email flow it was originally creatdd for
** TODO [#B] Is there way to create unique slugs for media instances :media_types: ** TODO [#B] Is there way to create unique slugs for media instances :media_types:
* Version 55.6 [1/1]
** DONE [#A] Figure out why historical Lastfm imports don't work :importers:lastfm:music:
:PROPERTIES:
:ID: 71b18c1b-de96-6d93-20fa-de2ec0df1288
:END:
* Version 55.5 [1/1]
** DONE [#B] Fix bug in lastfm import for new users :importers:lastfm:music:
:PROPERTIES:
:ID: d034966d-0c7f-e512-4cf8-7329c9026b6f
:END:
* Version 55.4 [1/1]
** DONE [#A] Tighten up the speed of startup and first request :perf:
:PROPERTIES:
:ID: 9ee8834c-6be2-d04b-df6d-56375504083f
:END:
* Version 55.3 [3/3]
** DONE [#C] =alt_names= feature for artists (commented out / dead code) :music:dead-code:
:PROPERTIES:
:ID: e22060a2-5f7a-4f33-9056-309ecd27159c
:END:
*** Description
File: ~vrobbler/apps/music/models.py~ (line 236)
An entire block of code for tracking alternate artist names is commented
out. The TODO questions whether it even works. Review: either implement
properly or remove the dead code.
** DONE [#A] Put chart rebuilds in a lower priority task queue :charts:tasks:
:PROPERTIES:
:ID: 43c90de0-fc1c-1139-dac7-9b7c82006b2e
:END:
** DONE [#A] Check for existing book scrobble and update page count :books:scrobbling:
:PROPERTIES:
:ID: 1a0609bc-6b16-4da4-96c1-59588229e4b4
:END:
*** Description
File: ~vrobbler/apps/scrobbles/scrobblers.py~ (line 330)
When scrobbling a book (comic), the code doesn't check for prior scrobbles to
update reading progress. Needed for proper page-count tracking.
* Version 55.2 [2/2] * Version 55.2 [2/2]
** DONE [#A] Fix bug in scrobble id in calendar view :templates: ** DONE [#A] Fix bug in scrobble id in calendar view :templates:
:PROPERTIES: :PROPERTIES:

View File

@ -1,2 +1,2 @@
web: python manage.py runserver 0.0.0.0:8014 web: python manage.py runserver 0.0.0.0:8014
worker: celery -A vrobbler worker -l DEBUG worker: celery -A vrobbler worker -Q default,charts -l DEBUG

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "vrobbler" name = "vrobbler"
version = "55.2" version = "55.6"
description = "" description = ""
authors = ["Colin Powell <colin@unbl.ink>"] authors = ["Colin Powell <colin@unbl.ink>"]

View File

@ -4,9 +4,15 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
from vrobbler import context_processors
from vrobbler.context_processors import version_info from vrobbler.context_processors import version_info
@pytest.fixture(autouse=True)
def reset_git_cache():
context_processors._GIT_COMMIT = None
@pytest.fixture @pytest.fixture
def mock_request(): def mock_request():
return MagicMock() return MagicMock()

View File

@ -0,0 +1,109 @@
# Generated by Django 4.2.29 on 2026-06-19 16:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("charts", "0002_chartrecord_charts_char_user_id_1adcde_idx_and_more"),
]
operations = [
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("artist__isnull", False)),
fields=("user", "year", "month", "week", "day", "artist"),
name="unique_chart_artist_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("album__isnull", False)),
fields=("user", "year", "month", "week", "day", "album"),
name="unique_chart_album_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("track__isnull", False)),
fields=("user", "year", "month", "week", "day", "track"),
name="unique_chart_track_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("tv_series__isnull", False)),
fields=("user", "year", "month", "week", "day", "tv_series"),
name="unique_chart_tv_series_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("video__isnull", False)),
fields=("user", "year", "month", "week", "day", "video"),
name="unique_chart_video_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("podcast__isnull", False)),
fields=("user", "year", "month", "week", "day", "podcast"),
name="unique_chart_podcast_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("podcast_episode__isnull", False)),
fields=("user", "year", "month", "week", "day", "podcast_episode"),
name="unique_chart_podcast_episode_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("board_game__isnull", False)),
fields=("user", "year", "month", "week", "day", "board_game"),
name="unique_chart_board_game_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("trail__isnull", False)),
fields=("user", "year", "month", "week", "day", "trail"),
name="unique_chart_trail_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("geo_location__isnull", False)),
fields=("user", "year", "month", "week", "day", "geo_location"),
name="unique_chart_geo_location_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("food__isnull", False)),
fields=("user", "year", "month", "week", "day", "food"),
name="unique_chart_food_period",
),
),
migrations.AddConstraint(
model_name="chartrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("book__isnull", False)),
fields=("user", "year", "month", "week", "day", "book"),
name="unique_chart_book_period",
),
),
]

View File

@ -2,6 +2,7 @@ import calendar
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import models from django.db import models
from django.db.models import Q
from django.urls import reverse from django.urls import reverse
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
@ -84,6 +85,68 @@ class ChartRecord(TimeStampedModel):
models.Index(fields=["user", "year", "month", "day", "album", "rank"]), models.Index(fields=["user", "year", "month", "day", "album", "rank"]),
models.Index(fields=["user", "year", "month", "day", "tv_series", "rank"]), models.Index(fields=["user", "year", "month", "day", "tv_series", "rank"]),
] ]
constraints = [
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "artist"],
condition=Q(artist__isnull=False),
name="unique_chart_artist_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "album"],
condition=Q(album__isnull=False),
name="unique_chart_album_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "track"],
condition=Q(track__isnull=False),
name="unique_chart_track_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "tv_series"],
condition=Q(tv_series__isnull=False),
name="unique_chart_tv_series_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "video"],
condition=Q(video__isnull=False),
name="unique_chart_video_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "podcast"],
condition=Q(podcast__isnull=False),
name="unique_chart_podcast_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "podcast_episode"],
condition=Q(podcast_episode__isnull=False),
name="unique_chart_podcast_episode_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "board_game"],
condition=Q(board_game__isnull=False),
name="unique_chart_board_game_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "trail"],
condition=Q(trail__isnull=False),
name="unique_chart_trail_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "geo_location"],
condition=Q(geo_location__isnull=False),
name="unique_chart_geo_location_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "food"],
condition=Q(food__isnull=False),
name="unique_chart_food_period",
),
models.UniqueConstraint(
fields=["user", "year", "month", "week", "day", "book"],
condition=Q(book__isnull=False),
name="unique_chart_book_period",
),
]
@property @property
def media_obj(self): def media_obj(self):

View File

@ -6,6 +6,7 @@ from typing import Optional
import pytz import pytz
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.db import transaction
from django.db.models import Count, Q from django.db.models import Count, Q
from django.utils import timezone from django.utils import timezone
@ -186,60 +187,64 @@ def build_charts(
ranks = {count: rank for rank, count in enumerate(unique_counts, start=1)} ranks = {count: rank for rank, count in enumerate(unique_counts, start=1)}
media_field = f"{media_type}_id" media_field = f"{media_type}_id"
records_to_create = []
records_to_update = []
existing = ChartRecord.objects.filter( with transaction.atomic():
period_filter, user=user, **{media_field + "__isnull": False} records_to_create = []
) records_to_update = []
existing_by_media_id = {getattr(r, media_field): r for r in existing}
found_media_ids = set()
for result in results: existing = ChartRecord.objects.select_for_update().filter(
media_id = result[config["values"]] period_filter, user=user, **{media_field + "__isnull": False}
if media_id is None:
continue
found_media_ids.add(media_id)
chart_record_data = {
"user_id": user.id,
"year": year,
"month": month,
"week": week,
"day": day,
"rank": ranks[result["scrobble_count"]],
"count": result["scrobble_count"],
}
chart_record_data[media_field] = media_id
if media_id in existing_by_media_id:
existing_record = existing_by_media_id[media_id]
existing_record.rank = chart_record_data["rank"]
existing_record.count = chart_record_data["count"]
records_to_update.append(existing_record)
else:
records_to_create.append(ChartRecord(**chart_record_data))
ids_to_delete = [
r.id for r in existing if getattr(r, media_field) not in found_media_ids
]
if ids_to_delete:
ChartRecord.objects.filter(id__in=ids_to_delete).delete()
if records_to_update:
ChartRecord.objects.bulk_update(
records_to_update, ["rank", "count"], batch_size=500
) )
existing_by_media_id = {getattr(r, media_field): r for r in existing}
found_media_ids = set()
if records_to_create: for result in results:
ChartRecord.objects.bulk_create(records_to_create, batch_size=500) media_id = result[config["values"]]
if media_id is None:
continue
logger.info( found_media_ids.add(media_id)
f"Built {len(records_to_create)} new, {len(records_to_update)} updated "
f"chart records for {media_type}, period " chart_record_data = {
f"{year}-{month or ''}-{week or ''}-{day or ''} for {user}" "user_id": user.id,
) "year": year,
"month": month,
"week": week,
"day": day,
"rank": ranks[result["scrobble_count"]],
"count": result["scrobble_count"],
}
chart_record_data[media_field] = media_id
if media_id in existing_by_media_id:
existing_record = existing_by_media_id[media_id]
existing_record.rank = chart_record_data["rank"]
existing_record.count = chart_record_data["count"]
records_to_update.append(existing_record)
else:
records_to_create.append(ChartRecord(**chart_record_data))
ids_to_delete = [
r.id
for r in existing
if getattr(r, media_field) not in found_media_ids
]
if ids_to_delete:
ChartRecord.objects.filter(id__in=ids_to_delete).delete()
if records_to_update:
ChartRecord.objects.bulk_update(
records_to_update, ["rank", "count"], batch_size=500
)
if records_to_create:
ChartRecord.objects.bulk_create(records_to_create, batch_size=500)
logger.info(
f"Built {len(records_to_create)} new, {len(records_to_update)} updated "
f"chart records for {media_type}, period "
f"{year}-{month or ''}-{week or ''}-{day or ''} for {user}"
)
def build_yesterdays_charts(user, media_types: Optional[list] = None) -> None: def build_yesterdays_charts(user, media_types: Optional[list] = None) -> None:

View File

@ -1,8 +1,22 @@
from django.core.cache import cache
from music.models import Artist, Album from music.models import Artist, Album
CACHE_TTL = 300
def music_lists(request): def music_lists(request):
artist_list = cache.get("music_lists_artist_list")
if artist_list is None:
artist_list = list(Artist.objects.all().only("id", "name"))
cache.set("music_lists_artist_list", artist_list, CACHE_TTL)
album_list = cache.get("music_lists_album_list")
if album_list is None:
album_list = list(Album.objects.all().only("id", "name"))
cache.set("music_lists_album_list", album_list, CACHE_TTL)
return { return {
"artist_list": Artist.objects.all(), "artist_list": artist_list,
"album_list": Album.objects.all(), "album_list": album_list,
} }

View File

@ -236,19 +236,6 @@ class Artist(TimeStampedModel):
) )
artist.fix_metadata() artist.fix_metadata()
# TODO: See if this alt_names stuff actually works or causes hard to debug problems
# If we did find our artist, but the found name is slightly differnt, record that
# if artist and alt_name:
# if not artist.alt_names:
# artist.alt_names = alt_name
# else:
# artist.alt_names += f"\\{alt_name}"
# logger.info(
# f"Add alt_name {alt_name} to artist {artist}",
# extra={"alt_name": alt_name, "artist_id": artist.id},
# )
# artist.save(update_fields=["alt_names"])
return artist return artist

View File

@ -1,4 +1,5 @@
import pytz import pytz
from django.core.cache import cache
from django.utils import timezone from django.utils import timezone
from scrobbles.constants import EXCLUDE_FROM_NOW_PLAYING from scrobbles.constants import EXCLUDE_FROM_NOW_PLAYING
@ -19,6 +20,8 @@ MONTH_COLORS = [
"#db7a7a", # Dec "#db7a7a", # Dec
] ]
CACHE_TTL = 60
def month_color(request): def month_color(request):
from datetime import date from datetime import date
@ -27,15 +30,25 @@ def month_color(request):
def now_playing(request): def now_playing(request):
user = request.user user = request.user
now = timezone.now()
if not user.is_authenticated: if not user.is_authenticated:
return {} return {}
return {
"now_playing_list": Scrobble.objects.filter( cache_key = f"now_playing_list_{user.id}"
in_progress=True, now_playing_list = cache.get(cache_key)
is_paused=False, if now_playing_list is None:
user=user, now_playing_list = list(
).exclude( Scrobble.objects.filter(
media_type__in=EXCLUDE_FROM_NOW_PLAYING, in_progress=True,
is_paused=False,
user=user,
)
.exclude(
media_type__in=EXCLUDE_FROM_NOW_PLAYING,
)
.select_related("track", "video", "podcast_episode")
) )
cache.set(cache_key, now_playing_list, CACHE_TTL)
return {
"now_playing_list": now_playing_list,
} }

View File

@ -330,8 +330,6 @@ def manual_scrobble_book(
source = READCOMICSONLINE_URL.replace("https://", "") source = READCOMICSONLINE_URL.replace("https://", "")
# TODO: Check for scrobble of this book already and if so, update the page count
book = Book.find_or_create(title, url=url, enrich=True) book = Book.find_or_create(title, url=url, enrich=True)
scrobble_dict = { scrobble_dict = {

View File

@ -1,5 +1,6 @@
import logging import logging
from django.core.cache import cache
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
@ -52,6 +53,11 @@ def _update_charts_for_timestamp(user, ts):
if ts is None: if ts is None:
return return
lock_key = f"chart_update_{user.id}"
if not cache.add(lock_key, "locked", timeout=30):
logger.info(f"Chart update already queued for user {user.id}, skipping")
return
if timezone.is_naive(ts): if timezone.is_naive(ts):
ts = timezone.make_aware(ts) ts = timezone.make_aware(ts)

View File

@ -12,6 +12,7 @@ from charts.utils import (
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@ -241,6 +242,11 @@ def update_charts_for_timestamp(user_id, year, month, day, week):
logger.error(f"User with id {user_id} not found") logger.error(f"User with id {user_id} not found")
return return
lock_key = f"chart_update_running_{user_id}"
if not cache.add(lock_key, "locked", timeout=300):
logger.info(f"Chart update already running for user {user_id}, skipping")
return
try: try:
build_daily_charts(user, year, month, day, CHARTABLE_MEDIA_TYPES) build_daily_charts(user, year, month, day, CHARTABLE_MEDIA_TYPES)
build_weekly_charts(user, year, week, CHARTABLE_MEDIA_TYPES) build_weekly_charts(user, year, week, CHARTABLE_MEDIA_TYPES)
@ -250,6 +256,8 @@ def update_charts_for_timestamp(user_id, year, month, day, week):
logger.info(f"[charts] Updated charts for {user} on {date_str}") logger.info(f"[charts] Updated charts for {user} on {date_str}")
except Exception as e: except Exception as e:
logger.error(f"[charts] Failed to update charts: {e}") logger.error(f"[charts] Failed to update charts: {e}")
finally:
cache.delete(lock_key)
@shared_task @shared_task

View File

@ -154,10 +154,11 @@ def import_lastfm_for_all_users(restart=False):
last_processed = lfm_import.processed_finished last_processed = lfm_import.processed_finished
else: else:
logger.info( logger.info(
f"Not resuming failed LastFM import {lfm_import.id} for user {user_id}, use restart=True to restart" "No existing LastFM import for user %s, "
"No existing LastFM import, we should start a monthly parsing of lastFm for this user going back to 2002" "starting a full parse",
user_id,
) )
continue last_processed = None
lfm_client = LastFM(user=get_user_model().objects.filter(id=user_id).first()) lfm_client = LastFM(user=get_user_model().objects.filter(id=user_id).first())

View File

@ -1,8 +1,24 @@
from django.core.cache import cache
from videos.models import Video, Series from videos.models import Video, Series
CACHE_TTL = 300
def video_lists(request): def video_lists(request):
movie_list = cache.get("video_lists_movie_list")
if movie_list is None:
movie_list = list(
Video.objects.filter(video_type=Video.VideoType.MOVIE).only("id", "title")
)
cache.set("video_lists_movie_list", movie_list, CACHE_TTL)
series_list = cache.get("video_lists_series_list")
if series_list is None:
series_list = list(Series.objects.all().only("id", "name"))
cache.set("video_lists_series_list", series_list, CACHE_TTL)
return { return {
"movie_list": Video.objects.filter(video_type=Video.VideoType.MOVIE), "movie_list": movie_list,
"series_list": Series.objects.all(), "series_list": series_list,
} }

View File

@ -4,46 +4,55 @@ from importlib.metadata import version as get_version
from pathlib import Path from pathlib import Path
_GIT_COMMIT = None
def version_info(request): def version_info(request):
global _GIT_COMMIT
try: try:
app_version = get_version("vrobbler") app_version = get_version("vrobbler")
except Exception: except Exception:
app_version = "unknown" app_version = "unknown"
commit = os.environ.get("VROBBLER_COMMIT") commit = os.environ.get("VROBBLER_COMMIT")
if not commit: if commit:
# Try to import from _commit.py module first return {"app_version": app_version, "git_commit": commit}
if _GIT_COMMIT is not None:
return {"app_version": app_version, "git_commit": _GIT_COMMIT}
try:
from vrobbler._commit import commit as _commit
except ImportError:
pass
else:
if _commit and _commit != "unknown":
_GIT_COMMIT = _commit
return {"app_version": app_version, "git_commit": _GIT_COMMIT}
commit_file = Path("/var/lib/vrobbler/commit.txt")
if commit_file.exists():
try: try:
from vrobbler._commit import commit as _commit commit = commit_file.read_text().strip()
except ImportError: if commit:
_GIT_COMMIT = commit
return {"app_version": app_version, "git_commit": _GIT_COMMIT}
except OSError:
pass pass
else:
if _commit and _commit != "unknown":
return {"app_version": app_version, "git_commit": _commit}
# Try to read from commit file (written during deploy) PROJECT_ROOT = Path(__file__).resolve().parent.parent
commit_file = Path("/var/lib/vrobbler/commit.txt") try:
if commit_file.exists(): _GIT_COMMIT = (
try: subprocess.check_output(
commit = commit_file.read_text().strip() ["git", "rev-parse", "--short", "HEAD"],
if commit: cwd=PROJECT_ROOT,
return {"app_version": app_version, "git_commit": commit} stderr=subprocess.DEVNULL,
except OSError:
pass
# Fall back to git command
PROJECT_ROOT = Path(__file__).resolve().parent.parent
try:
commit = (
subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"],
cwd=PROJECT_ROOT,
stderr=subprocess.DEVNULL,
)
.decode("utf-8")
.strip()
) )
except (subprocess.SubprocessError, FileNotFoundError): .decode("utf-8")
commit = "unknown" .strip()
)
except (subprocess.SubprocessError, FileNotFoundError):
_GIT_COMMIT = "unknown"
return {"app_version": app_version, "git_commit": commit} return {"app_version": app_version, "git_commit": _GIT_COMMIT}

View File

@ -122,6 +122,15 @@ CELERY_ACCEPT_CONTENT = ["json"]
CELERY_RESULT_EXTENDED = True CELERY_RESULT_EXTENDED = True
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
CELERY_TASK_CREATE_MISSING_QUEUES = True
CELERY_TASK_ROUTES = {
"scrobbles.tasks.update_charts_for_timestamp": {"queue": "charts"},
"scrobbles.tasks.create_yesterdays_charts": {"queue": "charts"},
"scrobbles.tasks.rebuild_weekly_charts": {"queue": "charts"},
"scrobbles.tasks.rebuild_monthly_charts": {"queue": "charts"},
"scrobbles.tasks.rebuild_yearly_charts": {"queue": "charts"},
}
CELERY_BEAT_SCHEDULE = { CELERY_BEAT_SCHEDULE = {
"build-yesterdays-charts": { "build-yesterdays-charts": {
"task": "scrobbles.tasks.create_yesterdays_charts", "task": "scrobbles.tasks.create_yesterdays_charts",
@ -352,11 +361,11 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/ # https://docs.djangoproject.com/en/3.1/howto/static-files/
# #
from storages.backends import s3boto3
USE_S3_STORAGE = os.getenv("VROBBLER_USE_S3", "False").lower() in TRUTHY USE_S3_STORAGE = os.getenv("VROBBLER_USE_S3", "False").lower() in TRUTHY
if USE_S3_STORAGE: if USE_S3_STORAGE:
from storages.backends import s3boto3
AWS_S3_ENDPOINT_URL = os.getenv("AWS_S3_ENDPOINT_URL", "") AWS_S3_ENDPOINT_URL = os.getenv("AWS_S3_ENDPOINT_URL", "")
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME", "") AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME", "")
AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID") AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID")