Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53c226eba8 | ||
|
|
3b74d93b74 | ||
|
|
ff54ab4ef6 | ||
|
|
a7c90b63df | ||
|
|
872962c435 | ||
|
|
b9c4627a77 | ||
|
|
eff7a288cf | ||
|
|
94d419cf01 | ||
|
|
4760d576f3 | ||
|
|
800c9f3f02 | ||
|
|
6c7def3184 | ||
|
|
962279db29 | ||
|
|
bffe286f40 | ||
|
|
abc96dd648 |
No files matched your search
@@ -1,5 +1,26 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [3.11.5](https://github.com/Masutayunikon/FanKarr/compare/v3.11.4...v3.11.5) (2026-04-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* le folder picker affiche tous les disques depuis le bouton homes sous windows ([7149022](https://github.com/Masutayunikon/FanKarr/commit/7149022c8701f79d194c675da87c6cb2d5793a35))
|
||||||
|
|
||||||
|
## [3.11.4](https://github.com/Masutayunikon/FanKarr/compare/v3.11.3...v3.11.4) (2026-04-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* ajout de la mise a jour des noms toutes les 1h ([0fe5c8c](https://github.com/Masutayunikon/FanKarr/commit/0fe5c8ccb7ecdc1643b5e69de3957c39dfd8085e))
|
||||||
|
|
||||||
|
## [3.11.3](https://github.com/Masutayunikon/FanKarr/compare/v3.11.2...v3.11.3) (2026-04-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* dockerfile node version to 22 ([5983494](https://github.com/Masutayunikon/FanKarr/commit/598349407750c729579bd639680ebc6a8ebcc12f))
|
||||||
|
|
||||||
## [3.11.2](https://github.com/Masutayunikon/FanKarr/compare/v3.11.1...v3.11.2) (2026-04-26)
|
## [3.11.2](https://github.com/Masutayunikon/FanKarr/compare/v3.11.1...v3.11.2) (2026-04-26)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
FROM node:20-slim AS base
|
FROM node:22-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
@@ -15,7 +15,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
|||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|
||||||
# ── Production ─────────────────────────────────────────────────
|
# ── Production ─────────────────────────────────────────────────
|
||||||
FROM node:20-slim
|
FROM node:22-slim
|
||||||
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
+14
-1
@@ -11,7 +11,7 @@ import transmissionDriver from './torrent-clients/transmission.js'
|
|||||||
import synologyDsDriver from './torrent-clients/synology-ds.js'
|
import synologyDsDriver from './torrent-clients/synology-ds.js'
|
||||||
import utorrentDriver from './torrent-clients/utorrent.js'
|
import utorrentDriver from './torrent-clients/utorrent.js'
|
||||||
import rtorrentDriver from './torrent-clients/rtorrent.js'
|
import rtorrentDriver from './torrent-clients/rtorrent.js'
|
||||||
import { autoOrganizeAll, scanMediaPath } from './organize.js'
|
import { autoOrganizeAll, scanMediaPath, syncFilenameChanges } from './organize.js'
|
||||||
import { logger } from './logger.js'
|
import { logger } from './logger.js'
|
||||||
import { DATA_DIR, BASE_DIR } from './config.js'
|
import { DATA_DIR, BASE_DIR } from './config.js'
|
||||||
import { readAvailable, readInfohashMap, loadEnrichedSeriesData } from './lib/github-cache.js'
|
import { readAvailable, readInfohashMap, loadEnrichedSeriesData } from './lib/github-cache.js'
|
||||||
@@ -139,4 +139,17 @@ server.listen(PORT, async () => {
|
|||||||
checkNfoUpdates().catch(err => logger.error('nfo-update', `Vérif horaire échouée : ${err instanceof Error ? err.message : err}`))
|
checkNfoUpdates().catch(err => logger.error('nfo-update', `Vérif horaire échouée : ${err instanceof Error ? err.message : err}`))
|
||||||
}, 60 * 60_000)
|
}, 60 * 60_000)
|
||||||
}, 30_000)
|
}, 30_000)
|
||||||
|
|
||||||
|
// Sync automatique des noms de fichiers toutes les heures.
|
||||||
|
// Le TTL du cache GitHub est de 1h — ce setInterval tire toujours sur des données fraîches.
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const seriesData = await loadEnrichedSeriesData()
|
||||||
|
const { renamed } = await syncFilenameChanges(seriesData, path.join(DATA_DIR, 'organized.json'))
|
||||||
|
if (renamed > 0)
|
||||||
|
logger.info('api', `Sync noms auto — ${renamed} fichier(s) renommé(s)`)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('api', `Sync noms auto échoué : ${err instanceof Error ? err.message : err}`)
|
||||||
|
}
|
||||||
|
}, 60 * 60_000)
|
||||||
})
|
})
|
||||||
+27
-2
@@ -47,11 +47,36 @@ router.get('/version', (_req, res) => {
|
|||||||
|
|
||||||
// ── Parcours fichiers ──────────────────────────────────────────
|
// ── Parcours fichiers ──────────────────────────────────────────
|
||||||
router.get('/browse', requireAuth, (req, res) => {
|
router.get('/browse', requireAuth, (req, res) => {
|
||||||
const dirPath = (req.query.path as string) || '/'
|
const isWindows = process.platform === 'win32'
|
||||||
|
const dirPath = (req.query.path as string) || '/'
|
||||||
|
|
||||||
|
// ── Racine virtuelle Windows : liste les lecteurs disponibles ──
|
||||||
|
if (isWindows && dirPath === '/') {
|
||||||
|
const drives: string[] = []
|
||||||
|
for (const letter of 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') {
|
||||||
|
const d = `${letter}:\\`
|
||||||
|
try { fs.readdirSync(d); drives.push(d) } catch {}
|
||||||
|
}
|
||||||
|
res.json({ path: '/', parent: null, dirs: drives, drivesRoot: true })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
||||||
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).filter(n => !n.startsWith('.')).sort((a, b) => a.localeCompare(b))
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).filter(n => !n.startsWith('.')).sort((a, b) => a.localeCompare(b))
|
||||||
res.json({ path: dirPath, parent: dirPath === '/' ? null : path.dirname(dirPath), dirs })
|
|
||||||
|
// Sur Windows, remonter à '/' quand on est à la racine d'un lecteur (ex: C:\)
|
||||||
|
let parent: string | null
|
||||||
|
if (dirPath === '/') {
|
||||||
|
parent = null
|
||||||
|
} else if (isWindows && /^[A-Za-z]:\\?$/.test(dirPath)) {
|
||||||
|
parent = '/'
|
||||||
|
} else {
|
||||||
|
const up = path.dirname(dirPath)
|
||||||
|
parent = up === dirPath ? null : up // dirname de C:\ retourne C:\ → null
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ path: dirPath, parent, dirs })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('api', `Lecture dossier "${dirPath}" impossible : ${err instanceof Error ? err.message : err}`)
|
logger.warn('api', `Lecture dossier "${dirPath}" impossible : ${err instanceof Error ? err.message : err}`)
|
||||||
res.status(400).json({ error: err instanceof Error ? err.message : 'Erreur lecture dossier' })
|
res.status(400).json({ error: err instanceof Error ? err.message : 'Erreur lecture dossier' })
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<!-- Chemin éditable -->
|
<!-- Chemin éditable -->
|
||||||
<div class="px-5 py-3 border-b border-border shrink-0 flex gap-2">
|
<div class="px-5 py-3 border-b border-border shrink-0 flex gap-2">
|
||||||
<button @click="navigateTo('/')" class="btn-secondary px-2 text-muted hover:text-primary" title="Retour à la racine">
|
<button @click="navigateTo('/')" class="btn-secondary px-2 text-muted hover:text-primary" :title="drivesRoot ? 'Lecteurs' : 'Retour à la racine'">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none">
|
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none">
|
||||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||||
<polyline points="9 22 9 12 15 12 15 22"/>
|
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||||
@@ -85,12 +85,13 @@ import { ref, watch } from 'vue'
|
|||||||
const props = defineProps<{ initialPath?: string }>()
|
const props = defineProps<{ initialPath?: string }>()
|
||||||
const emit = defineEmits<{ select: [path: string]; cancel: [] }>()
|
const emit = defineEmits<{ select: [path: string]; cancel: [] }>()
|
||||||
|
|
||||||
const current = ref(props.initialPath || '/')
|
const current = ref(props.initialPath || '/')
|
||||||
const inputPath = ref(current.value)
|
const inputPath = ref(current.value)
|
||||||
const dirs = ref<string[]>([])
|
const dirs = ref<string[]>([])
|
||||||
const parent = ref<string | null>(null)
|
const parent = ref<string | null>(null)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
|
const drivesRoot = ref(false) // true quand la liste affiche les lecteurs Windows
|
||||||
|
|
||||||
// FIX : la regex /\/+$/ dans un @click inline fait planter le parser Vue
|
// FIX : la regex /\/+$/ dans un @click inline fait planter le parser Vue
|
||||||
function selectCurrent() {
|
function selectCurrent() {
|
||||||
@@ -98,8 +99,10 @@ function selectCurrent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function joinPath(base: string, name: string): string {
|
function joinPath(base: string, name: string): string {
|
||||||
|
// Lecteur Windows complet (ex: "C:\") → chemin absolu, pas de jointure
|
||||||
|
if (/^[A-Za-z]:\\$/.test(name)) return name
|
||||||
if (base === '/') return `/${name}`
|
if (base === '/') return `/${name}`
|
||||||
const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base
|
const cleanBase = base.replace(/[\\/]+$/, '')
|
||||||
return `${cleanBase}/${name}`
|
return `${cleanBase}/${name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,10 +117,11 @@ async function navigateTo(p: string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
current.value = data.path
|
current.value = data.path
|
||||||
inputPath.value = data.path
|
inputPath.value = data.path
|
||||||
parent.value = data.parent
|
parent.value = data.parent
|
||||||
dirs.value = data.dirs
|
dirs.value = data.dirs
|
||||||
|
drivesRoot.value = data.drivesRoot ?? false
|
||||||
} catch {
|
} catch {
|
||||||
error.value = 'Impossible de lire ce dossier'
|
error.value = 'Impossible de lire ce dossier'
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
3.11.2
|
3.11.5
|
||||||
Reference in new issue
Block a user