Skip to content

Commit f50d50d

Browse files
committed
support deleting media folder
1 parent e0e744d commit f50d50d

File tree

14 files changed

+455
-104
lines changed

14 files changed

+455
-104
lines changed

cli/docs/MediaMetadataAPI.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Media Metadata APIs
2+
3+
Media Metadata APIs provide operations for reading, writing, and deleting media metadata cache files.
4+
5+
## POST /api/readMediaMetadata
6+
7+
Read media metadata from cache file. If the cache file does not exist, creates a new metadata object with file listing.
8+
9+
Request body:
10+
```typescript
11+
interface ReadMediaMetadataRequestBody {
12+
/**
13+
* Absolute path of media folder in platform-specific format
14+
*/
15+
path: string;
16+
}
17+
```
18+
19+
Response body:
20+
```typescript
21+
interface ReadMediaMetadataResponseBody {
22+
data: MediaMetadata;
23+
/**
24+
* undefined - The request succeeded without error
25+
* Folder Not Found - The media folder not found
26+
* Media Metadata Not Found - The local cache file for media metadata not found
27+
*/
28+
error?: string;
29+
}
30+
```
31+
32+
Status codes:
33+
- `200`: The API called successfully, need to look at the ReadMediaMetadataResponseBody to see if request succeeded
34+
35+
## POST /api/writeMediaMetadata
36+
37+
Write media metadata to cache file.
38+
39+
Request body:
40+
```typescript
41+
interface WriteMediaMetadataRequestBody {
42+
data: MediaMetadata;
43+
}
44+
```
45+
46+
Response body:
47+
```typescript
48+
interface WriteMediaMetadataResponseBody {
49+
data: MediaMetadata;
50+
}
51+
```
52+
53+
Status codes:
54+
- `200`: Metadata successfully written
55+
- `400`: Invalid request (missing mediaFolderPath)
56+
- `500`: Internal server error (failed to create directory or write file)
57+
58+
## POST /api/deleteMediaMetadata
59+
60+
Delete media metadata cache file.
61+
62+
Request body:
63+
```typescript
64+
interface DeleteMediaMetadataRequestBody {
65+
/**
66+
* Absolute path of media folder in platform-specific format
67+
*/
68+
path: string;
69+
}
70+
```
71+
72+
Response body:
73+
```typescript
74+
interface DeleteMediaMetadataResponseBody {
75+
}
76+
```
77+
78+
Status codes:
79+
- `200`: Metadata file successfully deleted
80+
- `400`: Invalid request (missing path)
81+
- `404`: Metadata file not found
82+
- `500`: Internal server error (failed to delete file)
83+
84+
## Metadata Cache Location
85+
86+
All metadata cache files are stored in the `metadata` subdirectory of the user data directory. The cache file name is generated by replacing invalid filename characters in the folder path with underscores and appending `.json` extension.
87+

cli/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import { handleChatRequest } from './tasks/ChatTask';
99
import { handleReadFile } from './src/route/ReadFile';
1010
import { handleWriteFile } from './src/route/WriteFile';
1111
import { handleReadImage } from './src/route/ReadImage';
12-
import { handleReadMediaMetadata } from '@/route/ReadMediaMetadata';
12+
import { handleReadMediaMetadata } from '@/route/api/mediaMetadata/read';
13+
import { handleWriteMediaMetadata } from '@/route/api/mediaMetadata/write';
14+
import { handleDeleteMediaMetadata } from '@/route/api/mediaMetadata/delete';
1315

1416
export interface ServerConfig {
1517
port?: number;
@@ -158,6 +160,8 @@ export class Server {
158160
});
159161

160162
handleReadMediaMetadata(this.app);
163+
handleWriteMediaMetadata(this.app);
164+
handleDeleteMediaMetadata(this.app);
161165

162166
// Serve static files from the configured root directory
163167
// Files will be accessible at the root path (e.g., /index.html serves public/index.html)

cli/src/route/ReadMediaMetadata.ts

Lines changed: 0 additions & 72 deletions
This file was deleted.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { ProblemDetails, DeleteMediaMetadataRequestBody, DeleteMediaMetadataResponseBody } from "@core/types";
2+
import type { Hono } from "hono";
3+
import { metadataCacheFilePath } from "./utils";
4+
5+
export async function handleDeleteMediaMetadata(app: Hono) {
6+
app.post('/api/deleteMediaMetadata', async (c) => {
7+
const raw = await c.req.json() as DeleteMediaMetadataRequestBody;
8+
console.log(`[HTTP_IN] ${c.req.method} ${c.req.url} ${JSON.stringify(raw)}`)
9+
const folderPath = raw.path;
10+
11+
if (!folderPath) {
12+
const problemDetails: ProblemDetails = {
13+
type: 'https://www.example.com/problemdetails/types/unexpected-error',
14+
title: 'Invalid Request',
15+
status: 400,
16+
detail: 'path is required',
17+
instance: c.req.url,
18+
};
19+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(problemDetails)}`)
20+
return c.json(problemDetails, 400);
21+
}
22+
23+
const metadataFilePath = metadataCacheFilePath(folderPath);
24+
const fileExists = await Bun.file(metadataFilePath).exists();
25+
26+
if (!fileExists) {
27+
const problemDetails: ProblemDetails = {
28+
type: 'https://www.example.com/problemdetails/types/unexpected-error',
29+
title: 'Not Found',
30+
status: 404,
31+
detail: `Metadata file not found for path: ${folderPath}`,
32+
instance: c.req.url,
33+
};
34+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(problemDetails)}`)
35+
return c.json(problemDetails, 404);
36+
}
37+
38+
// Delete metadata file
39+
try {
40+
await Bun.file(metadataFilePath).unlink();
41+
console.log(`[DeleteMediaMetadata] Deleted metadata file: ${metadataFilePath}`)
42+
const resp: DeleteMediaMetadataResponseBody = {};
43+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(resp)}`)
44+
return c.json(resp, 200);
45+
} catch (error) {
46+
const problemDetails: ProblemDetails = {
47+
type: 'https://www.example.com/problemdetails/types/unexpected-error',
48+
title: 'Internal Server Error',
49+
status: 500,
50+
detail: `Failed to delete metadata file: ${error instanceof Error ? error.message : 'Unknown error'}`,
51+
instance: c.req.url,
52+
};
53+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(problemDetails)}`)
54+
return c.json(problemDetails, 500);
55+
}
56+
});
57+
}
58+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type { MediaMetadata, ReadMediaMetadataRequestBody, ReadMediaMetadataResponseBody } from "@core/types";
2+
import type { Hono } from "hono";
3+
import { stat } from "node:fs/promises";
4+
import { Path } from "@core/path";
5+
import { listFiles } from "@/utils/files";
6+
import { metadataCacheFilePath } from "./utils";
7+
8+
export async function newMediaMetadata(folderPath: Path) {
9+
const metadata: MediaMetadata = {
10+
mediaFolderPath: folderPath.abs(),
11+
mediaName: '',
12+
officalMediaName: '',
13+
files: [],
14+
mediaFiles: [],
15+
poster: undefined,
16+
tmdbTVShowId: undefined,
17+
seasons: undefined,
18+
}
19+
20+
const files = await listFiles(folderPath, true)
21+
metadata.files = files
22+
23+
return metadata
24+
}
25+
26+
export async function handleReadMediaMetadata(app: Hono) {
27+
app.post('/api/readMediaMetadata', async (c) => {
28+
const raw = await c.req.json() as ReadMediaMetadataRequestBody;
29+
console.log(`[HTTP_IN] ${c.req.method} ${c.req.url} ${JSON.stringify(raw)}`)
30+
const folderPath = raw.path;
31+
32+
// Check if folder exists
33+
try {
34+
const stats = await stat(folderPath);
35+
if (!stats.isDirectory()) {
36+
const resp: ReadMediaMetadataResponseBody = {
37+
data: {} as MediaMetadata,
38+
error: 'Folder Not Found'
39+
};
40+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(resp)}`)
41+
return c.json(resp, 200);
42+
}
43+
} catch (error) {
44+
// Folder doesn't exist or can't be accessed
45+
const resp: ReadMediaMetadataResponseBody = {
46+
data: {} as MediaMetadata,
47+
error: 'Folder Not Found'
48+
};
49+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(resp)}`)
50+
return c.json(resp, 200);
51+
}
52+
53+
const metadataFilePath = metadataCacheFilePath(folderPath);
54+
const isExist = await Bun.file(metadataFilePath).exists();
55+
56+
if(!isExist) {
57+
// Cache doesn't exist, create new metadata
58+
try {
59+
const metadata = await newMediaMetadata(new Path(folderPath));
60+
const resp: ReadMediaMetadataResponseBody = {
61+
data: metadata
62+
};
63+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(resp)}`)
64+
return c.json(resp, 200);
65+
} catch (error) {
66+
// Error creating metadata (e.g., can't list files)
67+
const resp: ReadMediaMetadataResponseBody = {
68+
data: {} as MediaMetadata,
69+
error: 'Media Metadata Not Found'
70+
};
71+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(resp)}`)
72+
return c.json(resp, 200);
73+
}
74+
} else {
75+
// Cache exists, read it
76+
try {
77+
const data = await Bun.file(metadataFilePath).json();
78+
const resp: ReadMediaMetadataResponseBody = {
79+
data
80+
};
81+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(resp)}`)
82+
return c.json(resp, 200);
83+
} catch (error) {
84+
// Error reading cache file
85+
const resp: ReadMediaMetadataResponseBody = {
86+
data: {} as MediaMetadata,
87+
error: 'Media Metadata Not Found'
88+
};
89+
console.log(`[HTTP_OUT] ${c.req.method} ${c.req.url} ${JSON.stringify(resp)}`)
90+
return c.json(resp, 200);
91+
}
92+
}
93+
});
94+
}
95+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { getUserDataDir } from "tasks/HelloTask";
2+
import path, { join } from "path";
3+
4+
const userDataDir = getUserDataDir();
5+
export const mediaMetadataDir = path.join(userDataDir, 'metadata');
6+
7+
export function metadataCacheFilePath(folderPath: string) {
8+
const filename = folderPath.replace(/[\/\\:?*|<>"]/g, '_')
9+
return join(mediaMetadataDir, `${filename}.json`)
10+
}
11+

0 commit comments

Comments
 (0)