[importers] Add error_log to surface import errors
All checks were successful
build / test (push) Successful in 2m2s
All checks were successful
build / test (push) Successful in 2m2s
This commit is contained in:
66
PROJECT.org
66
PROJECT.org
@ -88,7 +88,7 @@ fetching and simple saving.
|
|||||||
*** Metadata sources
|
*** Metadata sources
|
||||||
**** Scraper
|
**** Scraper
|
||||||
|
|
||||||
* Backlog [0/12] :vrobbler:project:personal:
|
* Backlog [1/16] :vrobbler:project:personal:
|
||||||
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles:
|
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
|
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
|
||||||
@ -500,6 +500,70 @@ needed import celery task. This is how the WebDAV celery task currently works.
|
|||||||
This would also be an opporunity to clean up the code around WebDAV imports
|
This would also be an opporunity to clean up the code around WebDAV imports
|
||||||
and make them more re-usable for other import services.
|
and make them more re-usable for other import services.
|
||||||
|
|
||||||
|
** TODO [#A] Generate a report of tracks with mistmatched metadata :music:tracks:metadata:
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 684b8cd2-a3c1-4995-ba9e-7abdb02c37f2
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Description
|
||||||
|
|
||||||
|
We should have a management command that outputs a CSV file of track IDs where the log["raw_data"] artist
|
||||||
|
does not match the actual track artist. Specific cases are
|
||||||
|
|
||||||
|
** TODO [#A] Add an exception list of artists as a constant that are exempted from splitting :music:artists:metadata:
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: fd86a11a-73ec-470d-b5e3-2d90ba9137c8
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Description
|
||||||
|
|
||||||
|
Certain artists like "Simon & Garfunkel" are actually one artist. While we don't want to mess with splitting up
|
||||||
|
tracks into featured artists, we should have a "LITERAL_ARTIST_TITLES" constant that can have exceptions like
|
||||||
|
this put into it and then we stop trying to pull the artist apart when we run into it.
|
||||||
|
|
||||||
|
** TODO [#A] Before enriching anything, trust the POST data :feature:scrobbles:metadata:
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: db6b05f8-09f4-49f5-9838-fbacc9fe9cd0
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Description
|
||||||
|
|
||||||
|
Both Jellyfin and Mopidy provide a decent amount of metadata when they POST to our webhooks.
|
||||||
|
|
||||||
|
In most cases, we should be able to trust this data to created music tracks or videos rather
|
||||||
|
than going to third-party services to enrich. Thus, for tracks and videos we should search in
|
||||||
|
the local database for imdb_id or musicbrainz_id for the specific content and, if found, not
|
||||||
|
enrich further.
|
||||||
|
|
||||||
|
If not found, tracks and videos from mopidy and jellyfin should be created as completely as
|
||||||
|
possible using only the POST data from the webhooks, tagged the scrobble with "webhook-metadata-only"
|
||||||
|
and start the scrobble. A separate celery task should be kicked off to enrich the track or video
|
||||||
|
async with the POST data stored in the log["raw_data"] and used by the celery enrichment task
|
||||||
|
to go try to enrich the media instance. Should this enrichment fail, tag the scrobble as "enrichment-failed"
|
||||||
|
log a warning and move on.
|
||||||
|
|
||||||
|
** DONE [#A] Date parsing failing in eBird imports :birds:ebird:importers:
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 72e0254f-39d8-4843-9857-623e0362d77e
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Description
|
||||||
|
|
||||||
|
On line 45 in the apps/birds/importer.py file, the import is thorowing this
|
||||||
|
error:
|
||||||
|
|
||||||
|
#+begin_src python
|
||||||
|
ValueError: time data 'Jun 7, 2026, 5:15 PM' does not match format '%B %d, %Y %I:%M %p'
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Historically other files starting on May 24 worked, so I suspect this is a
|
||||||
|
problem of the date formatter expecting Long month names and zero-padded days
|
||||||
|
and the only sample we had was a three-letter month (May) and days with two
|
||||||
|
digites(24 through the 31)
|
||||||
|
|
||||||
|
We should also add a "error_log" to the importers so that errors tha occur are
|
||||||
|
surfaced, even when 0 successful files were processed. And we should make sure
|
||||||
|
all importers do this as well.
|
||||||
|
|
||||||
* Version 48.0 [2/2]
|
* Version 48.0 [2/2]
|
||||||
** DONE [#B] Show team or player images on sport detail and scrobble detail :sports:templates:
|
** DONE [#B] Show team or player images on sport detail and scrobble detail :sports:templates:
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import tempfile
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -128,6 +129,23 @@ class TestBirdingCSVImportModel:
|
|||||||
assert imp.import_type == "Birding CSV"
|
assert imp.import_type == "Birding CSV"
|
||||||
assert "Birding" in str(imp)
|
assert "Birding" in str(imp)
|
||||||
|
|
||||||
|
def test_record_error(self, db, user):
|
||||||
|
imp = BirdingCSVImport.objects.create(user=user)
|
||||||
|
assert imp.error_log is None
|
||||||
|
imp.record_error("test error")
|
||||||
|
imp.refresh_from_db()
|
||||||
|
assert imp.error_log is not None
|
||||||
|
assert "test error" in imp.error_log
|
||||||
|
|
||||||
|
def test_record_error_appends(self, db, user):
|
||||||
|
imp = BirdingCSVImport.objects.create(user=user)
|
||||||
|
imp.record_error("first error")
|
||||||
|
imp.record_error("second error")
|
||||||
|
imp.refresh_from_db()
|
||||||
|
assert imp.error_log.count("\n") == 1
|
||||||
|
assert "first error" in imp.error_log
|
||||||
|
assert "second error" in imp.error_log
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.django_db(transaction=True)
|
||||||
def test_process_via_model(self, user, birding_csv_file):
|
def test_process_via_model(self, user, birding_csv_file):
|
||||||
imp = BirdingCSVImport.objects.create(user=user)
|
imp = BirdingCSVImport.objects.create(user=user)
|
||||||
@ -137,3 +155,35 @@ class TestBirdingCSVImportModel:
|
|||||||
imp.refresh_from_db()
|
imp.refresh_from_db()
|
||||||
assert imp.process_count == 1
|
assert imp.process_count == 1
|
||||||
assert imp.processed_finished is not None
|
assert imp.processed_finished is not None
|
||||||
|
|
||||||
|
def test_record_error_on_bad_csv(self, user, db):
|
||||||
|
content = """Species,Count,Location,Observation Type,Observation Date,Start Time,Duration,Distance,Area,Party Size,Complete Checklist,# of species,Details
|
||||||
|
Canada Goose,6,Test Park,Stationary,"Bad Date",4:15 PM,9 minute(s),,,4,true,4 species,
|
||||||
|
"""
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
mode="w", suffix=".csv", delete=False, encoding="utf-8"
|
||||||
|
) as f:
|
||||||
|
f.write(content)
|
||||||
|
file_path = f.name
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
scrobbles = import_birding_csv(file_path, user.id, record_error=errors.append)
|
||||||
|
assert len(scrobbles) == 0
|
||||||
|
assert len(errors) == 1
|
||||||
|
assert "Could not parse date/time" in errors[0]
|
||||||
|
|
||||||
|
def test_record_error_on_bad_location(self, user, db):
|
||||||
|
content = """Species,Count,Location,Observation Type,Observation Date,Start Time,Duration,Distance,Area,Party Size,Complete Checklist,# of species,Details
|
||||||
|
Canada Goose,6,,Stationary,"May 10, 2026",4:15 PM,9 minute(s),,,4,true,4 species,
|
||||||
|
"""
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
mode="w", suffix=".csv", delete=False, encoding="utf-8"
|
||||||
|
) as f:
|
||||||
|
f.write(content)
|
||||||
|
file_path = f.name
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
scrobbles = import_birding_csv(file_path, user.id, record_error=errors.append)
|
||||||
|
assert len(scrobbles) == 0
|
||||||
|
assert len(errors) == 1
|
||||||
|
assert "Skipping rows with no location" in errors[0]
|
||||||
|
|||||||
@ -26,5 +26,5 @@ class BirdingLocationAdmin(admin.ModelAdmin):
|
|||||||
@admin.register(BirdingCSVImport)
|
@admin.register(BirdingCSVImport)
|
||||||
class BirdingCSVImportAdmin(admin.ModelAdmin):
|
class BirdingCSVImportAdmin(admin.ModelAdmin):
|
||||||
date_hierarchy = "created"
|
date_hierarchy = "created"
|
||||||
list_display = ("uuid", "process_count", "processed_finished", "processing_started")
|
list_display = ("uuid", "process_count", "processed_finished", "processing_started", "error_log")
|
||||||
ordering = ("-created",)
|
ordering = ("-created",)
|
||||||
|
|||||||
@ -64,7 +64,7 @@ def parse_int(value):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def import_birding_csv(file_path, user_id):
|
def import_birding_csv(file_path, user_id, record_error=None):
|
||||||
user = User.objects.get(id=user_id)
|
user = User.objects.get(id=user_id)
|
||||||
new_scrobbles = []
|
new_scrobbles = []
|
||||||
|
|
||||||
@ -83,11 +83,17 @@ def import_birding_csv(file_path, user_id):
|
|||||||
|
|
||||||
for (location_str, date_str, time_str), sighting_rows in groups.items():
|
for (location_str, date_str, time_str), sighting_rows in groups.items():
|
||||||
if not location_str:
|
if not location_str:
|
||||||
logger.warning("Skipping rows with no location")
|
msg = "Skipping rows with no location"
|
||||||
|
logger.warning(msg)
|
||||||
|
if record_error:
|
||||||
|
record_error(msg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
timestamp = parse_timestamp(date_str, time_str)
|
timestamp = parse_timestamp(date_str, time_str)
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
|
msg = f"Could not parse date/time: {date_str} {time_str}"
|
||||||
|
if record_error:
|
||||||
|
record_error(msg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
timestamp = user.profile.get_timestamp_with_tz(timestamp)
|
timestamp = user.profile.get_timestamp_with_tz(timestamp)
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.29 on 2026-06-08 14:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("birds", "0002_birdingcsvimport"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="birdingcsvimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -216,6 +216,7 @@ class BirdingCSVImport(TimeStampedModel):
|
|||||||
processed_finished = models.DateTimeField(**BNULL)
|
processed_finished = models.DateTimeField(**BNULL)
|
||||||
process_log = models.TextField(**BNULL)
|
process_log = models.TextField(**BNULL)
|
||||||
process_count = models.IntegerField(**BNULL)
|
process_count = models.IntegerField(**BNULL)
|
||||||
|
error_log = models.TextField(**BNULL)
|
||||||
csv_file = models.FileField(upload_to="birding-csv-uploads/", **BNULL)
|
csv_file = models.FileField(upload_to="birding-csv-uploads/", **BNULL)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -269,6 +270,14 @@ class BirdingCSVImport(TimeStampedModel):
|
|||||||
self.process_count = len(scrobbles)
|
self.process_count = len(scrobbles)
|
||||||
self.save(update_fields=["process_log", "process_count"])
|
self.save(update_fields=["process_log", "process_count"])
|
||||||
|
|
||||||
|
def record_error(self, error_message):
|
||||||
|
log_line = f"{timezone.now().isoformat()}: {error_message}"
|
||||||
|
if self.error_log:
|
||||||
|
self.error_log += "\n" + log_line
|
||||||
|
else:
|
||||||
|
self.error_log = log_line
|
||||||
|
self.save(update_fields=["error_log"])
|
||||||
|
|
||||||
def scrobbles(self):
|
def scrobbles(self):
|
||||||
from scrobbles.models import Scrobble
|
from scrobbles.models import Scrobble
|
||||||
|
|
||||||
@ -287,6 +296,13 @@ class BirdingCSVImport(TimeStampedModel):
|
|||||||
from birds.importer import import_birding_csv
|
from birds.importer import import_birding_csv
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
scrobbles = import_birding_csv(self.upload_file_path, self.user_id)
|
try:
|
||||||
self.record_log(scrobbles)
|
scrobbles = import_birding_csv(
|
||||||
self.mark_finished()
|
self.upload_file_path, self.user_id, record_error=self.record_error
|
||||||
|
)
|
||||||
|
self.record_log(scrobbles)
|
||||||
|
except Exception as e:
|
||||||
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
|
finally:
|
||||||
|
self.mark_finished()
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class ImportBaseAdmin(admin.ModelAdmin):
|
|||||||
"process_count",
|
"process_count",
|
||||||
"processed_finished",
|
"processed_finished",
|
||||||
"processing_started",
|
"processing_started",
|
||||||
|
"error_log",
|
||||||
)
|
)
|
||||||
ordering = ("-created",)
|
ordering = ("-created",)
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
# Generated by Django 4.2.29 on 2026-06-08 14:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("scrobbles", "0089_favoritemedia"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="audioscrobblertsvimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="bgstatsimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="ebirdcsvimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="koreaderimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="lastfmimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="retroarchimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scalecsvimport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="trailgpximport",
|
||||||
|
name="error_log",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -74,6 +74,7 @@ class BaseFileImportMixin(TimeStampedModel):
|
|||||||
processed_finished = models.DateTimeField(**BNULL)
|
processed_finished = models.DateTimeField(**BNULL)
|
||||||
process_log = models.TextField(**BNULL)
|
process_log = models.TextField(**BNULL)
|
||||||
process_count = models.IntegerField(**BNULL)
|
process_count = models.IntegerField(**BNULL)
|
||||||
|
error_log = models.TextField(**BNULL)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@ -158,6 +159,14 @@ class BaseFileImportMixin(TimeStampedModel):
|
|||||||
self.process_count = len(scrobbles)
|
self.process_count = len(scrobbles)
|
||||||
self.save(update_fields=["process_log", "process_count"])
|
self.save(update_fields=["process_log", "process_count"])
|
||||||
|
|
||||||
|
def record_error(self, error_message):
|
||||||
|
log_line = f"{timezone.now().isoformat()}: {error_message}"
|
||||||
|
if self.error_log:
|
||||||
|
self.error_log += "\n" + log_line
|
||||||
|
else:
|
||||||
|
self.error_log = log_line
|
||||||
|
self.save(update_fields=["error_log"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def upload_file_path(self):
|
def upload_file_path(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -213,9 +222,14 @@ class KoReaderImport(BaseFileImportMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
scrobbles = process_koreader_sqlite_file(self.upload_file_path, self.user.id)
|
try:
|
||||||
self.record_log(scrobbles)
|
scrobbles = process_koreader_sqlite_file(self.upload_file_path, self.user.id)
|
||||||
self.mark_finished()
|
self.record_log(scrobbles)
|
||||||
|
except Exception as e:
|
||||||
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
|
finally:
|
||||||
|
self.mark_finished()
|
||||||
|
|
||||||
|
|
||||||
class AudioScrobblerTSVImport(BaseFileImportMixin):
|
class AudioScrobblerTSVImport(BaseFileImportMixin):
|
||||||
@ -255,10 +269,14 @@ class AudioScrobblerTSVImport(BaseFileImportMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
|
try:
|
||||||
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.record_log(scrobbles)
|
||||||
self.mark_finished()
|
except Exception as e:
|
||||||
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
|
finally:
|
||||||
|
self.mark_finished()
|
||||||
|
|
||||||
|
|
||||||
class ScaleCSVImport(BaseFileImportMixin):
|
class ScaleCSVImport(BaseFileImportMixin):
|
||||||
@ -297,9 +315,14 @@ class ScaleCSVImport(BaseFileImportMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
scrobbles = import_scale_csv(self.upload_file_path, self.user.id)
|
try:
|
||||||
self.record_log(scrobbles)
|
scrobbles = import_scale_csv(self.upload_file_path, self.user.id)
|
||||||
self.mark_finished()
|
self.record_log(scrobbles)
|
||||||
|
except Exception as e:
|
||||||
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
|
finally:
|
||||||
|
self.mark_finished()
|
||||||
|
|
||||||
|
|
||||||
class TrailGPXImport(BaseFileImportMixin):
|
class TrailGPXImport(BaseFileImportMixin):
|
||||||
@ -337,11 +360,16 @@ class TrailGPXImport(BaseFileImportMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
scrobbles = import_trail_gpx(
|
try:
|
||||||
self.upload_file_path, self.user.id, self.original_filename
|
scrobbles = import_trail_gpx(
|
||||||
)
|
self.upload_file_path, self.user.id, self.original_filename
|
||||||
self.record_log(scrobbles)
|
)
|
||||||
self.mark_finished()
|
self.record_log(scrobbles)
|
||||||
|
except Exception as e:
|
||||||
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
|
finally:
|
||||||
|
self.mark_finished()
|
||||||
|
|
||||||
|
|
||||||
class LastFmImport(BaseFileImportMixin):
|
class LastFmImport(BaseFileImportMixin):
|
||||||
@ -391,11 +419,14 @@ class LastFmImport(BaseFileImportMixin):
|
|||||||
last_processed = last_import.processed_finished
|
last_processed = last_import.processed_finished
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
|
try:
|
||||||
scrobbles = lastfm.import_from_lastfm(last_processed, time_to=time_to)
|
scrobbles = lastfm.import_from_lastfm(last_processed, time_to=time_to)
|
||||||
|
self.record_log(scrobbles)
|
||||||
self.record_log(scrobbles)
|
except Exception as e:
|
||||||
self.mark_finished()
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
|
finally:
|
||||||
|
self.mark_finished()
|
||||||
|
|
||||||
|
|
||||||
class RetroarchImport(BaseFileImportMixin):
|
class RetroarchImport(BaseFileImportMixin):
|
||||||
@ -426,43 +457,48 @@ class RetroarchImport(BaseFileImportMixin):
|
|||||||
logger.info(f"You told me to force import from Retroarch")
|
logger.info(f"You told me to force import from Retroarch")
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
|
scrobbles = None
|
||||||
|
try:
|
||||||
|
if self.lrtl_file:
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
|
||||||
if self.lrtl_file:
|
tmpdir = tempfile.mkdtemp()
|
||||||
import os
|
try:
|
||||||
import tempfile
|
zip_path = os.path.join(tmpdir, "archive.zip")
|
||||||
import zipfile
|
with open(zip_path, "wb") as f:
|
||||||
|
f.write(self.lrtl_file.read())
|
||||||
|
with zipfile.ZipFile(zip_path, "r") as zf:
|
||||||
|
zf.extractall(tmpdir)
|
||||||
|
os.unlink(zip_path)
|
||||||
|
scrobbles = retroarch.import_retroarch_lrtl_files(
|
||||||
|
tmpdir + "/",
|
||||||
|
self.user.id,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||||
|
else:
|
||||||
|
if not self.user.profile.retroarch_path:
|
||||||
|
logger.info(
|
||||||
|
"Trying to import Retroarch logs, but user has no retroarch_path configured"
|
||||||
|
)
|
||||||
|
self.mark_finished()
|
||||||
|
return
|
||||||
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
zip_path = os.path.join(tmpdir, "archive.zip")
|
|
||||||
with open(zip_path, "wb") as f:
|
|
||||||
f.write(self.lrtl_file.read())
|
|
||||||
with zipfile.ZipFile(zip_path, "r") as zf:
|
|
||||||
zf.extractall(tmpdir)
|
|
||||||
os.unlink(zip_path)
|
|
||||||
scrobbles = retroarch.import_retroarch_lrtl_files(
|
scrobbles = retroarch.import_retroarch_lrtl_files(
|
||||||
tmpdir + "/",
|
self.user.profile.retroarch_path,
|
||||||
self.user.id,
|
self.user.id,
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
self.record_log(scrobbles)
|
||||||
else:
|
except Exception as e:
|
||||||
if not self.user.profile.retroarch_path:
|
self.record_error(f"Import failed: {e}")
|
||||||
logger.info(
|
logger.exception(f"Import failed for {self}")
|
||||||
"Tying to import Retroarch logs, but user has no retroarch_path configured"
|
finally:
|
||||||
)
|
self.mark_finished()
|
||||||
self.mark_finished()
|
|
||||||
return
|
|
||||||
|
|
||||||
scrobbles = retroarch.import_retroarch_lrtl_files(
|
|
||||||
self.user.profile.retroarch_path,
|
|
||||||
self.user.id,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.record_log(scrobbles)
|
|
||||||
self.mark_finished()
|
|
||||||
|
|
||||||
|
|
||||||
class BGStatsImport(BaseFileImportMixin):
|
class BGStatsImport(BaseFileImportMixin):
|
||||||
@ -499,17 +535,21 @@ class BGStatsImport(BaseFileImportMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
|
||||||
import json
|
from scrobbles.scrobblers import email_scrobble_board_game
|
||||||
|
|
||||||
from scrobbles.scrobblers import email_scrobble_board_game
|
with open(self.upload_file_path, "r", encoding="utf-8") as f:
|
||||||
|
parsed_json = json.load(f)
|
||||||
|
scrobbles = email_scrobble_board_game(parsed_json, self.user_id)
|
||||||
|
|
||||||
with open(self.upload_file_path, "r", encoding="utf-8") as f:
|
self.record_log(scrobbles)
|
||||||
parsed_json = json.load(f)
|
except Exception as e:
|
||||||
scrobbles = email_scrobble_board_game(parsed_json, self.user_id)
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
self.record_log(scrobbles)
|
finally:
|
||||||
self.mark_finished()
|
self.mark_finished()
|
||||||
|
|
||||||
|
|
||||||
class EBirdCSVImport(BaseFileImportMixin):
|
class EBirdCSVImport(BaseFileImportMixin):
|
||||||
@ -554,9 +594,16 @@ class EBirdCSVImport(BaseFileImportMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.mark_started()
|
self.mark_started()
|
||||||
scrobbles = import_birding_csv(self.upload_file_path, self.user_id)
|
try:
|
||||||
self.record_log(scrobbles)
|
scrobbles = import_birding_csv(
|
||||||
self.mark_finished()
|
self.upload_file_path, self.user_id, record_error=self.record_error
|
||||||
|
)
|
||||||
|
self.record_log(scrobbles)
|
||||||
|
except Exception as e:
|
||||||
|
self.record_error(f"Import failed: {e}")
|
||||||
|
logger.exception(f"Import failed for {self}")
|
||||||
|
finally:
|
||||||
|
self.mark_finished()
|
||||||
|
|
||||||
|
|
||||||
class ScrobbleQuerySet(models.QuerySet):
|
class ScrobbleQuerySet(models.QuerySet):
|
||||||
|
|||||||
Reference in New Issue
Block a user