mirror of
https://github.com/Chevron7Locked/kima-hub.git
synced 2026-06-19 07:37:17 +00:00
fix: surface Subsonic write failures, guard podcast sort, de-spam analyzer log
- Subsonic star.view swallowed every error and returned success, so a third-party app could star a track that never saved. Now only a P2003 FK violation (track legitimately missing) is absorbed; any other error is logged and returns a Subsonic error. Scrobble play-log failures are logged instead of silently discarded. - The podcasts page sorted by author/title with a raw localeCompare on an optional field, so one feed with no author crashed the whole page via the error boundary. Comparators are now null-guarded. - The audio analyzer re-logged the same 'N tracks permanently failed' warning every idle cycle (~50s) forever; it now logs only when the count changes.
This commit is contained in:
@@ -3,6 +3,7 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import { ListenSource } from "@prisma/client";
|
||||
import { prisma } from "../../utils/db";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { subsonicOk, subsonicError, SubsonicError } from "../../utils/subsonicResponse";
|
||||
import { getAudioStreamingService } from "../../services/audioStreaming";
|
||||
import { config } from "../../config";
|
||||
@@ -326,7 +327,7 @@ playbackRouter.all("/scrobble.view", wrap(async (req, res) => {
|
||||
const playedAt = isNaN(timeMs) ? new Date() : new Date(timeMs);
|
||||
await prisma.play
|
||||
.create({ data: { userId, trackId: id, playedAt, source: ListenSource.SUBSONIC } })
|
||||
.catch(() => {});
|
||||
.catch((err) => logger.warn("[Subsonic] scrobble play-log failed:", err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Router } from "express";
|
||||
import { prisma } from "../../utils/db";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { subsonicOk, subsonicError, SubsonicError } from "../../utils/subsonicResponse";
|
||||
import { mapSong, firstArtistGenre, wrap, parseRepeatedQueryParam } from "./mappers";
|
||||
|
||||
@@ -81,13 +82,18 @@ starredRouter.all("/star.view", wrap(async (req, res) => {
|
||||
const ids = parseRepeatedQueryParam(req.query.id);
|
||||
|
||||
for (const trackId of ids) {
|
||||
await prisma.likedTrack
|
||||
.upsert({
|
||||
try {
|
||||
await prisma.likedTrack.upsert({
|
||||
where: { userId_trackId: { userId, trackId } },
|
||||
create: { userId, trackId },
|
||||
update: {},
|
||||
})
|
||||
.catch(() => {}); // Absorbs FK violation if trackId doesn't exist
|
||||
});
|
||||
} catch (err) {
|
||||
// P2003 = FK violation: trackId doesn't exist. Absorb silently.
|
||||
if ((err as { code?: string }).code === "P2003") continue;
|
||||
logger.warn("[Subsonic] star failed:", err);
|
||||
return subsonicError(req, res, SubsonicError.GENERIC, "Failed to star track");
|
||||
}
|
||||
}
|
||||
return subsonicOk(req, res);
|
||||
}));
|
||||
|
||||
@@ -189,10 +189,10 @@ export default function PodcastsPage() {
|
||||
const sorted = [...podcasts];
|
||||
switch (sortBy) {
|
||||
case "title":
|
||||
sorted.sort((a, b) => a.title.localeCompare(b.title));
|
||||
sorted.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
|
||||
break;
|
||||
case "author":
|
||||
sorted.sort((a, b) => a.author.localeCompare(b.author));
|
||||
sorted.sort((a, b) => (a.author || "").localeCompare(b.author || ""));
|
||||
break;
|
||||
case "recent":
|
||||
sorted.sort((a, b) => (b.episodeCount || 0) - (a.episodeCount || 0));
|
||||
|
||||
@@ -1160,6 +1160,7 @@ class AnalysisWorker:
|
||||
self._last_work_time = time.time()
|
||||
self._pending_resize: int | None = None
|
||||
self._pending_resize_time: float = 0.0
|
||||
self._last_perm_failed_count: int | None = None
|
||||
self._setup_control_channel()
|
||||
|
||||
def _setup_control_channel(self):
|
||||
@@ -1503,8 +1504,10 @@ class AnalysisWorker:
|
||||
""", (MAX_RETRIES,))
|
||||
|
||||
perm_failed = cursor.fetchone()
|
||||
if perm_failed and perm_failed['count'] > 0:
|
||||
logger.warning(f"{perm_failed['count']} tracks have permanently failed (exceeded {MAX_RETRIES} retries)")
|
||||
perm_failed_count = perm_failed['count'] if perm_failed else 0
|
||||
if perm_failed_count > 0 and perm_failed_count != self._last_perm_failed_count:
|
||||
logger.warning(f"{perm_failed_count} tracks have permanently failed (exceeded {MAX_RETRIES} retries)")
|
||||
self._last_perm_failed_count = perm_failed_count
|
||||
|
||||
self.db.commit()
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user