Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fe/src/__tests__/libraryImport.store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('library import store', () => {
expect(startManualImport).toHaveBeenCalledWith({
path: 'C:\\incoming',
mode: 'interactive',
inputMode: 'move',
action: 'move',
includeCompanionFiles: true,
cleanupEmptySourceFolders: true,
items: [
Expand Down
40 changes: 28 additions & 12 deletions fe/src/components/domain/audiobook/LibraryImportFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@
<div class="import-footer">
<div class="footer-left">
<label class="footer-label">
<select v-model="store.inputMode" class="mode-select" :disabled="isImporting">
Monitor:
<select v-model="store.monitor" class="mode-select" :disabled="isImporting">
<option value="all">All</option>
<option value="none">None</option>
</select>
</label>
<label class="footer-label">
On import:
<select v-model="store.action" class="mode-select" :disabled="isImporting">
<option value="none">Do nothing</option>
<option value="move">Move</option>
<option value="hardlink/copy">Hardlink / Copy</option>
</select>
<span class="footer-to">to:</span>
<select
v-model="destinationFolderId"
class="mode-select destination-select"
:disabled="isImporting"
>
<option v-for="f in props.folders" :key="f.id" :value="f.id">
{{ f.path }}
</option>
</select>
<div v-if="store.action != 'none'">
<label class="footer-label">to
<select
v-model="destinationFolderId"
class="mode-select destination-select"
:disabled="isImporting"
>
<option v-for="f in props.folders" :key="f.id" :value="f.id">
{{ f.path }}
</option>
</select>
</label>
</div>
<span v-else>Imported files will be left where they are</span>
</label>

<div v-if="store.metadataFetchCount > 100" class="rate-limit-warning">
Expand Down Expand Up @@ -81,7 +94,10 @@ const isImporting = ref(false)
const importingCount = ref(0)

const destinationPath = computed(
() => props.folders.find((f) => f.id === destinationFolderId.value)?.path ?? '',
() => {
if (store.action == 'none') return '';
return props.folders.find((f) => f.id === destinationFolderId.value)?.path ?? ''
}
)

const displayImportCount = computed(() =>
Expand Down
11 changes: 7 additions & 4 deletions fe/src/stores/libraryImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export const useLibraryImportStore = defineStore('libraryImport', () => {
const scanStatus = ref<'idle' | 'scanning' | 'done' | 'error'>('idle')
const scanError = ref<string | null>(null)
const lastScannedAt = ref<string | null>(null)
const inputMode = ref<'move' | 'hardlink/copy'>('move')
const action = ref<'none' | 'move' | 'hardlink/copy'>('none')
const monitor = ref<'none' | 'all'>('all')
const metadataFetchCount = ref(0)
const importErrors = ref<string[]>([])

Expand Down Expand Up @@ -432,6 +433,7 @@ export const useLibraryImportStore = defineStore('libraryImport', () => {
: match.series,
}
const { audiobook } = await apiService.addToLibrary(metadata, {
monitored: monitor.value != 'none',
destinationPath: rootFolderPath,
searchResult: sanitizedMatch,
})
Expand Down Expand Up @@ -461,9 +463,9 @@ export const useLibraryImportStore = defineStore('libraryImport', () => {
await apiService.startManualImport({
path: item.folderPath,
mode: 'interactive',
inputMode: inputMode.value,
action: action.value,
includeCompanionFiles: true,
Comment thread
therobbiedavis marked this conversation as resolved.
Outdated
cleanupEmptySourceFolders: inputMode.value === 'move',
cleanupEmptySourceFolders: action.value === 'move',
items: item.sourceFiles.map((fullPath) => ({
fullPath,
matchedAudiobookId: audiobookId,
Expand Down Expand Up @@ -494,7 +496,8 @@ export const useLibraryImportStore = defineStore('libraryImport', () => {
scanStatus,
scanError,
lastScannedAt,
inputMode,
action,
monitor,
metadataFetchCount,
importErrors,
// Computed
Expand Down
6 changes: 5 additions & 1 deletion fe/src/stores/rootFolders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,9 @@ export const useRootFoldersStore = defineStore('rootFolders', () => {
return r
}

return { folders, loading, defaultFolder, load, create, update, remove }
function getLast() {
return folders.value.at(-1);
Comment thread
therobbiedavis marked this conversation as resolved.
Outdated
}

return { folders, loading, defaultFolder, load, create, update, remove, getLast }
})
4 changes: 2 additions & 2 deletions fe/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export interface ApplicationSettings {
allowedFileExtensions: string[]
importBlacklistExtensions?: string[]
// Action to perform for completed downloads.
completedFileAction?: 'Move' | 'Copy' | 'Hardlink/Copy'
completedFileAction?: 'None' | 'Move' | 'Copy' | 'Hardlink/Copy'
// Show completed external downloads (torrents/NZBs) in the Activity view
showCompletedExternalDownloads?: boolean
// Failed download handling
Expand Down Expand Up @@ -893,7 +893,7 @@ export interface ManualImportRequestItem {
export interface ManualImportRequest {
path: string
mode?: 'automatic' | 'interactive'
inputMode?: 'move' | 'copy' | 'hardlink/copy'
action?: 'none' | 'move' | 'copy' | 'hardlink/copy'
includeCompanionFiles?: boolean
cleanupEmptySourceFolders?: boolean
items?: ManualImportRequestItem[]
Expand Down
38 changes: 33 additions & 5 deletions fe/src/views/library/LibraryImportView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
<option v-for="f in rootFoldersStore.folders" :key="f.id" :value="f.id">
{{ f.name || f.path }}
</option>
<option :value="null">
Choose another folder...
</option>
</select>
</div>

Expand Down Expand Up @@ -175,6 +178,12 @@
:folders="rootFoldersStore.folders"
/>
</div>

<RootFolderFormModal
v-if="addRootFolder"
@saved="refreshRootFolders"
@close="addRootFolder = false"
Comment thread
therobbiedavis marked this conversation as resolved.
Outdated
/>
</template>

<script setup lang="ts">
Expand Down Expand Up @@ -202,6 +211,7 @@ import {
type LibraryImportSortDirection,
type LibraryImportSortKey,
} from '@/utils/libraryImportTable'
import RootFolderFormModal from '@/components/settings/RootFolderFormModal.vue'

const COLUMN_WIDTH_STORAGE_KEY = 'listenarr.libraryImport.columnWidths.v1'
const MAX_COLUMN_WIDTH = 960
Expand All @@ -216,6 +226,7 @@ const sortKey = ref<LibraryImportSortKey>('folder')
const sortDirection = ref<LibraryImportSortDirection>('asc')
const columnWidths = ref<LibraryImportColumnWidths>({ ...DEFAULT_LIBRARY_IMPORT_COLUMN_WIDTHS })
const resizingColumn = ref<LibraryImportResizableColumnKey | null>(null)
const addRootFolder = ref<boolean>(false)

const sortOptions: Array<{ value: LibraryImportSortKey; label: string }> = [
{ value: 'folder', label: 'Book' },
Expand Down Expand Up @@ -271,18 +282,23 @@ onMounted(async () => {
await store.initFromRootFolder(defaultFolder.id)
}

const action = configStore.applicationSettings?.completedFileAction
store.inputMode = action === 'Move' || !action ? 'move' : 'hardlink/copy'
store.action = 'none'
})

onBeforeUnmount(() => {
stopResize()
})

async function onFolderChange() {
if (!selectedFolderId.value) return
store.stopProcessing()
await store.initFromRootFolder(selectedFolderId.value)
// Selected a valid root folder
if (selectedFolderId.value) {
store.stopProcessing()
await store.initFromRootFolder(selectedFolderId.value)
}
// Tries to create a new root folder
else {
addRootFolder.value = true;
}
}

async function startScan() {
Expand Down Expand Up @@ -404,6 +420,18 @@ function persistColumnWidths() {
// Non-fatal: resizing still works for the current session.
}
}

async function refreshRootFolders() {
addRootFolder.value = false

await rootFoldersStore.load()

const newFolder = rootFoldersStore.getLast();
if (newFolder) {
selectedFolderId.value = newFolder.id
await store.initFromRootFolder(newFolder.id)
}
}
</script>

<style scoped>
Expand Down
5 changes: 3 additions & 2 deletions listenarr.api/Controllers/LibraryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Text;
using Listenarr.Domain.Utils;

namespace Listenarr.Api.Controllers
{
Expand Down Expand Up @@ -2327,7 +2328,7 @@ private static bool PathsEqual(string? left, string? right)

private static bool IsSamePathOrWithin(string path, string rootPath)
{
return PathsEqual(path, rootPath) || FileUtils.IsPathWithinRoot(path, rootPath);
return PathsEqual(path, rootPath) || FileUtils.IsPathInsideOf(path, rootPath);
}

private static bool IsFilesystemRoot(string? path)
Expand Down Expand Up @@ -2776,7 +2777,7 @@ public async Task<IActionResult> ScanAudiobookFiles(int id, [FromBody] ScanReque
{
try
{
var jobId = await _scanQueueService.EnqueueScanAsync(id, request?.Path);
var jobId = await _scanQueueService.EnqueueScanAsync(audiobook, request?.Path);
_logger.LogInformation("Enqueued scan job {JobId} for audiobook {AudiobookId}", jobId, id);

// Broadcast initial job status via SignalR so clients can show queued state
Expand Down
Loading
Loading