Show node scan status and fast-path shelf updates during library sync.
This commit is contained in:
parent
9168a159f7
commit
76c989ba3c
7 changed files with 65 additions and 6 deletions
|
|
@ -27,6 +27,7 @@ interface Node {
|
|||
libraryFileCount: number;
|
||||
fullScanInterval: string;
|
||||
fullScanAt: string;
|
||||
scanStatus?: string;
|
||||
moviesPaths: string[];
|
||||
seriesPaths: string[];
|
||||
scanRoots: ScanRootRow[];
|
||||
|
|
@ -399,6 +400,7 @@ export default function NodesPage() {
|
|||
<th>Naam</th>
|
||||
<th>Locatie</th>
|
||||
<th>Status</th>
|
||||
<th>Scan</th>
|
||||
<th>Paden</th>
|
||||
<th>Bestanden</th>
|
||||
<th>Streams</th>
|
||||
|
|
@ -417,6 +419,15 @@ export default function NodesPage() {
|
|||
<td>
|
||||
<span className={`status ${node.status.toLowerCase()}`}>{node.status}</span>
|
||||
</td>
|
||||
<td style={{ fontSize: "0.8rem" }}>
|
||||
{node.scanStatus === "scanning" ? (
|
||||
<span style={{ color: "#f59e0b" }}>bezig…</span>
|
||||
) : node.scanStatus === "idle" ? (
|
||||
<span style={{ color: "#71717a" }}>idle</span>
|
||||
) : (
|
||||
<span style={{ color: "#71717a" }}>{node.scanStatus ?? "-"}</span>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ fontSize: "0.75rem", color: "#a1a1aa" }}>
|
||||
{node.scanRoots?.length
|
||||
? `${node.scanRoots.length} roots`
|
||||
|
|
|
|||
|
|
@ -277,6 +277,32 @@ export class MetadataService {
|
|||
nodeId: string,
|
||||
file: MediaFileInfo
|
||||
): Promise<string | null> {
|
||||
// Fast path: already matched → only refresh shelf / availability (no TMDB).
|
||||
const existing = await prisma.mediaFile.findUnique({
|
||||
where: {
|
||||
nodeId_localFileId: { nodeId, localFileId: file.localFileId },
|
||||
},
|
||||
select: { id: true, movieId: true, episodeId: true, shelfId: true },
|
||||
});
|
||||
if (existing && (existing.movieId || existing.episodeId)) {
|
||||
const nextShelf = file.shelfId || null;
|
||||
await prisma.mediaFile.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
available: true,
|
||||
shelfId: nextShelf,
|
||||
sizeBytes: BigInt(file.sizeBytes),
|
||||
container: file.container,
|
||||
resolution: file.resolution,
|
||||
videoCodec: file.videoCodec,
|
||||
audioCodec: file.audioCodec,
|
||||
releaseName: file.releaseName,
|
||||
lastSeen: new Date(file.modifiedAt),
|
||||
},
|
||||
});
|
||||
return existing.movieId ?? existing.episodeId;
|
||||
}
|
||||
|
||||
if (file.mediaType === "movie" && file.parsedMovie) {
|
||||
return this.matchMovie(nodeId, file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ export async function registerNodeRoutes(app: FastifyInstance) {
|
|||
app.get("/api/v1/admin/nodes", { preHandler: requireAdmin }, async () => {
|
||||
const { ensureDefaultShelves } = await import("../library/shelves");
|
||||
await ensureDefaultShelves();
|
||||
const { nodeConnectionManager } = await import("../websocket/manager");
|
||||
const scanStatuses = nodeConnectionManager.getAllScanStatuses();
|
||||
const nodes = await prisma.node.findMany({
|
||||
include: {
|
||||
location: true,
|
||||
|
|
@ -130,7 +132,10 @@ export async function registerNodeRoutes(app: FastifyInstance) {
|
|||
});
|
||||
|
||||
return {
|
||||
nodes: nodes.map((n) => serializeNode(n)),
|
||||
nodes: nodes.map((n) => ({
|
||||
...serializeNode(n),
|
||||
scanStatus: scanStatuses[n.id] ?? (n.status === "ONLINE" ? "idle" : "offline"),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ interface NodeConnection {
|
|||
nodeId: string;
|
||||
socket: WebSocket;
|
||||
lastHeartbeat: Date;
|
||||
scanStatus: string;
|
||||
}
|
||||
|
||||
interface PendingAck {
|
||||
|
|
@ -162,6 +163,7 @@ class NodeConnectionManager {
|
|||
nodeId: payload.nodeId,
|
||||
socket,
|
||||
lastHeartbeat: new Date(),
|
||||
scanStatus: "idle",
|
||||
});
|
||||
onAuth(payload.nodeId);
|
||||
|
||||
|
|
@ -276,7 +278,10 @@ class NodeConnectionManager {
|
|||
|
||||
private async handleHeartbeat(payload: HeartbeatPayload): Promise<void> {
|
||||
const conn = this.connections.get(payload.nodeId);
|
||||
if (conn) conn.lastHeartbeat = new Date();
|
||||
if (conn) {
|
||||
conn.lastHeartbeat = new Date();
|
||||
conn.scanStatus = payload.scanStatus || "idle";
|
||||
}
|
||||
|
||||
await prisma.node.update({
|
||||
where: { id: payload.nodeId },
|
||||
|
|
@ -422,6 +427,18 @@ class NodeConnectionManager {
|
|||
return this.sendToNode(nodeId, "RESCAN", { full });
|
||||
}
|
||||
|
||||
getScanStatus(nodeId: string): string {
|
||||
return this.connections.get(nodeId)?.scanStatus ?? "offline";
|
||||
}
|
||||
|
||||
getAllScanStatuses(): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
for (const [id, conn] of this.connections) {
|
||||
out[id] = conn.scanStatus || "idle";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
sendConfigUpdate(
|
||||
nodeId: string,
|
||||
payload: {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ WORKDIR /src
|
|||
RUN apk add --no-cache git ca-certificates
|
||||
COPY node/media-node/ ./
|
||||
RUN go mod tidy \
|
||||
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=1.2.4" -o /media-node-linux-amd64 ./cmd/media-node \
|
||||
&& CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.version=1.2.4" -o /media-node-linux-arm64 ./cmd/media-node
|
||||
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=1.2.5" -o /media-node-linux-amd64 ./cmd/media-node \
|
||||
&& CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.version=1.2.5" -o /media-node-linux-arm64 ./cmd/media-node
|
||||
|
||||
FROM base AS builder
|
||||
COPY package.json pnpm-workspace.yaml ./
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
"github.com/sthmedia/media-node/internal/streaming"
|
||||
)
|
||||
|
||||
var version = "1.2.4"
|
||||
var version = "1.2.5"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ func (s *Store) GetFileByPath(path string) (*LocalFile, error) {
|
|||
func (s *Store) AllFiles() ([]LocalFile, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT local_file_id, path, size_bytes, modified_at, media_type, release_name, revision
|
||||
FROM local_files ORDER BY release_name
|
||||
FROM local_files ORDER BY path
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
Loading…
Reference in a new issue