mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #219 from igorkaldowski/master
Add: Force re-scan library
This commit is contained in:
		
						commit
						5751cac290
					
				| @ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <div class="w-full h-16 bg-primary relative"> | ||||
|     <div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-40"> | ||||
|     <div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-50"> | ||||
|       <div class="flex h-full items-center"> | ||||
|         <img v-if="!showBack" src="/Logo48.png" class="w-10 h-10 md:w-12 md:h-12 mr-4" /> | ||||
|         <a v-if="showBack" @click="back" class="rounded-full h-12 w-12 flex items-center justify-center hover:bg-white hover:bg-opacity-10 mr-4 cursor-pointer"> | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
|         <p>Series</p> | ||||
|       </nuxt-link> | ||||
|     </div> | ||||
|     <div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-20 flex items-center px-2 md:px-8"> | ||||
|     <div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-30 flex items-center px-2 md:px-8"> | ||||
|       <template v-if="page !== 'search' && !isHome"> | ||||
|         <p v-if="!selectedSeries" class="font-book hidden md:block">{{ numShowing }} {{ entityName }}</p> | ||||
|         <div v-else class="items-center hidden md:flex"> | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <div class="w-20 bg-bg h-full relative box-shadow-side z-30" style="min-width: 80px"> | ||||
|   <div class="w-20 bg-bg h-full relative box-shadow-side z-40" style="min-width: 80px"> | ||||
|     <div class="absolute top-0 -right-4 w-4 bg-bg h-10 pointer-events-none" /> | ||||
|     <nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> | ||||
|       <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|       </span> | ||||
|     </button> | ||||
| 
 | ||||
|     <ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-56 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" role="listbox" aria-labelledby="listbox-label"> | ||||
|     <ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" role="listbox" aria-labelledby="listbox-label"> | ||||
|       <template v-for="item in items"> | ||||
|         <li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400" :class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedOption(item.value)"> | ||||
|           <div class="flex items-center"> | ||||
|  | ||||
| @ -10,6 +10,7 @@ | ||||
|     <p class="text-xl font-book pl-4 hover:underline cursor-pointer" @click.stop="$emit('click', library)">{{ library.name }}</p> | ||||
|     <div class="flex-grow" /> | ||||
|     <ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" @click.stop="scan">Scan</ui-btn> | ||||
|     <ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" class="ml-2" @click.stop="forceScan">Force Re-Scan</ui-btn> | ||||
|     <span v-show="isHovering && !libraryScan && showEdit && canEdit" class="material-icons text-xl text-gray-300 hover:text-gray-50 ml-4 cursor-pointer" @click.stop="editClick">edit</span> | ||||
|     <span v-show="!libraryScan && isHovering && showEdit && canDelete && !isDeleting" class="material-icons text-xl text-gray-300 ml-3" :class="isMain ? 'text-opacity-5 cursor-not-allowed' : 'hover:text-gray-50 cursor-pointer'" @click.stop="deleteClick">delete</span> | ||||
|     <div v-show="isDeleting" class="text-xl text-gray-300 ml-3 animate-spin"> | ||||
| @ -58,15 +59,15 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     itemClicked() { | ||||
|       // this.$emit('click', this.library) | ||||
|     }, | ||||
|     editClick() { | ||||
|       this.$emit('edit', this.library) | ||||
|     }, | ||||
|     scan() { | ||||
|       this.$root.socket.emit('scan', this.library.id) | ||||
|     }, | ||||
|     forceScan() { | ||||
|       this.$root.socket.emit('scan', this.library.id, { forceRescan: true }) | ||||
|     }, | ||||
|     deleteClick() { | ||||
|       if (this.isMain) return | ||||
|       if (confirm(`Are you sure you want to permanently delete library "${this.library.name}"?`)) { | ||||
|  | ||||
| @ -6,14 +6,16 @@ | ||||
|         <span class="material-icons" style="font-size: 1.4rem">add</span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <draggable v-model="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag"> | ||||
|       <!-- <transition-group type="transition" :name="!drag ? 'flip-list' : null"> --> | ||||
|     <draggable :list="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag"> | ||||
|       <template v-for="library in libraryCopies"> | ||||
|         <modals-libraries-library-item :key="library.id" :library="library" :selected="currentLibraryId === library.id" :show-edit="true" :dragging="drag" @edit="editLibrary" @click="setLibrary" class="item" /> | ||||
|         <div :key="library.id" class="item"> | ||||
|           <modals-libraries-library-item :library="library" :selected="currentLibraryId === library.id" :show-edit="true" :dragging="drag" @edit="editLibrary" @sort="draggableSort" @click="setLibrary" /> | ||||
|         </div> | ||||
|       </template> | ||||
|       <!-- </transition-group> --> | ||||
|     </draggable> | ||||
|     <modals-edit-library-modal v-model="showLibraryModal" :library="selectedLibrary" /> | ||||
| 
 | ||||
|     <p class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| @ -48,6 +50,9 @@ export default { | ||||
|     }, | ||||
|     libraries() { | ||||
|       return this.$store.getters['libraries/getSortedLibraries']() | ||||
|     }, | ||||
|     libraryScans() { | ||||
|       return this.$store.state.scanners.libraryScans | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
| @ -55,10 +60,9 @@ export default { | ||||
|       this.drag = true | ||||
|       clearTimeout(this.orderTimeout) | ||||
|     }, | ||||
|     endDrag() { | ||||
|     endDrag(e) { | ||||
|       this.drag = false | ||||
|       this.checkOrder() | ||||
|       console.log('DRAG END') | ||||
|     }, | ||||
|     checkOrder() { | ||||
|       clearTimeout(this.orderTimeout) | ||||
| @ -78,7 +82,7 @@ export default { | ||||
|       }) | ||||
|       var newOrder = libraryOrderData.map((lib) => lib.id).join(',') | ||||
|       if (currOrder !== newOrder) { | ||||
|         this.$axios.$patch('/api/libraries/order', libraryOrderData).then((libraries) => { | ||||
|         this.$axios.$post('/api/libraries/order', libraryOrderData).then((libraries) => { | ||||
|           if (libraries && libraries.length) { | ||||
|             this.$toast.success('Library order saved', { timeout: 1500 }) | ||||
|             this.$store.commit('libraries/set', libraries) | ||||
|  | ||||
| @ -210,7 +210,7 @@ export const mutations = { | ||||
|     } | ||||
|     if (audiobook.book.genres && audiobook.book.genres.length) { | ||||
|       audiobook.book.genres.forEach((genre) => { | ||||
|         if (tag && !state.filterData.genres.includes(genre)) state.filterData.genres.push(genre) | ||||
|         if (genre && !state.filterData.genres.includes(genre)) state.filterData.genres.push(genre) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -63,7 +63,8 @@ class ApiController { | ||||
|     this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), LibraryController.search.bind(this)) | ||||
|     this.router.get('/libraries/:id/stats', LibraryController.middleware.bind(this), LibraryController.stats.bind(this)) | ||||
|     this.router.get('/libraries/:id/authors', LibraryController.middleware.bind(this), LibraryController.getAuthors.bind(this)) | ||||
|     this.router.patch('/libraries/order', LibraryController.reorder.bind(this)) | ||||
|     this.router.post('/libraries/order', LibraryController.reorder.bind(this)) | ||||
| 
 | ||||
| 
 | ||||
|     // TEMP: Support old syntax for mobile app
 | ||||
|     this.router.get('/library/:id/audiobooks', LibraryController.middleware.bind(this), LibraryController.getBooksForLibrary.bind(this)) | ||||
|  | ||||
| @ -316,9 +316,9 @@ class Server { | ||||
|     await this.scanner2.scanFilesChanged(fileUpdates) | ||||
|   } | ||||
| 
 | ||||
|   async scan(libraryId) { | ||||
|   async scan(libraryId, options = {}) { | ||||
|     Logger.info('[Server] Starting Scan') | ||||
|     await this.scanner2.scan(libraryId) | ||||
|     await this.scanner2.scan(libraryId, options) | ||||
|     // await this.scanner.scan(libraryId)
 | ||||
|     Logger.info('[Server] Scan complete') | ||||
|   } | ||||
|  | ||||
| @ -96,9 +96,9 @@ class AudioFile { | ||||
|     this.exclude = !!data.exclude | ||||
|     this.error = data.error || null | ||||
| 
 | ||||
|     this.trackNumFromMeta = data.trackNumFromMeta || null | ||||
|     this.trackNumFromFilename = data.trackNumFromFilename || null | ||||
|     this.cdNumFromFilename = data.cdNumFromFilename || null | ||||
|     this.trackNumFromMeta = data.trackNumFromMeta | ||||
|     this.trackNumFromFilename = data.trackNumFromFilename | ||||
|     this.cdNumFromFilename = data.cdNumFromFilename | ||||
| 
 | ||||
|     this.format = data.format | ||||
|     this.duration = data.duration | ||||
| @ -130,9 +130,9 @@ class AudioFile { | ||||
|     this.fullPath = data.fullPath | ||||
|     this.addedAt = Date.now() | ||||
| 
 | ||||
|     this.trackNumFromMeta = data.trackNumFromMeta || null | ||||
|     this.trackNumFromFilename = data.trackNumFromFilename || null | ||||
|     this.cdNumFromFilename = data.cdNumFromFilename || null | ||||
|     this.trackNumFromMeta = data.trackNumFromMeta | ||||
|     this.trackNumFromFilename = data.trackNumFromFilename | ||||
|     this.cdNumFromFilename = data.cdNumFromFilename | ||||
| 
 | ||||
|     this.manuallyVerified = !!data.manuallyVerified | ||||
|     this.invalid = !!data.invalid | ||||
| @ -302,9 +302,9 @@ class AudioFile { | ||||
|           hasUpdated = true | ||||
|         } | ||||
|       } else if (this[key] !== newjson[key]) { | ||||
|         // console.log(this.filename, 'key', key, 'updated', this[key], newjson[key])
 | ||||
|         this[key] = newjson[key] | ||||
|         hasUpdated = true | ||||
|         // console.log('key', key, 'updated', this[key], newjson[key])
 | ||||
|       } | ||||
|     } | ||||
|     return hasUpdated | ||||
|  | ||||
| @ -542,10 +542,9 @@ class Audiobook { | ||||
|     var alreadyHasDescTxt = otherFilenamesAlreadyInBook.includes('desc.txt') | ||||
|     var alreadyHasReaderTxt = otherFilenamesAlreadyInBook.includes('reader.txt') | ||||
| 
 | ||||
|     // Filter out other files no longer in directory
 | ||||
|     var newOtherFilePaths = newOtherFiles.map(f => f.path) | ||||
|     this.otherFiles = this.otherFiles.filter(f => newOtherFilePaths.includes(f.path)) | ||||
| 
 | ||||
|     // Some files are not there anymore and filtered out
 | ||||
|     if (currOtherFileNum !== this.otherFiles.length) { | ||||
|       Logger.debug(`[Audiobook] ${currOtherFileNum - this.otherFiles.length} other files were removed for "${this.title}"`) | ||||
|       hasUpdates = true | ||||
| @ -937,6 +936,8 @@ class Audiobook { | ||||
| 
 | ||||
|     var newAudioFileData = [] | ||||
|     var newOtherFileData = [] | ||||
|     var existingAudioFileData = [] | ||||
|     var existingOtherFileData = [] | ||||
| 
 | ||||
|     dataFound.audioFiles.forEach((af) => { | ||||
|       var audioFileFoundCheck = this.checkFileFound(af, true) | ||||
| @ -944,6 +945,9 @@ class Audiobook { | ||||
|         newAudioFileData.push(af) | ||||
|       } else if (audioFileFoundCheck) { | ||||
|         hasUpdated = true | ||||
|         existingAudioFileData.push(af) | ||||
|       } else { | ||||
|         existingAudioFileData.push(af) | ||||
|       } | ||||
|     }) | ||||
| 
 | ||||
| @ -953,6 +957,9 @@ class Audiobook { | ||||
|         newOtherFileData.push(otherFileData) | ||||
|       } else if (fileFoundCheck) { | ||||
|         hasUpdated = true | ||||
|         existingOtherFileData.push(otherFileData) | ||||
|       } else { | ||||
|         existingOtherFileData.push(otherFileData) | ||||
|       } | ||||
|     }) | ||||
| 
 | ||||
| @ -1010,7 +1017,9 @@ class Audiobook { | ||||
|       newAudioFileData, | ||||
|       newOtherFileData, | ||||
|       audioFilesRemoved, | ||||
|       otherFilesRemoved | ||||
|       otherFilesRemoved, | ||||
|       existingAudioFileData, // Existing file data may get re-scanned if forceRescan is set
 | ||||
|       existingOtherFileData | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -139,12 +139,10 @@ class AudioFileScanner { | ||||
|         } | ||||
|         if (existingAF) { | ||||
|           if (audiobook.updateAudioFile(newAF)) { | ||||
|             // console.log('update dauido file')
 | ||||
|             hasUpdated = true | ||||
|           } | ||||
|         } else { | ||||
|           audiobook.addAudioFile(newAF) | ||||
|           // console.log('added auido file')
 | ||||
|           hasUpdated = true | ||||
|         } | ||||
|       } | ||||
|  | ||||
| @ -35,7 +35,7 @@ class AudioProbeData { | ||||
| 
 | ||||
|   setData(data) { | ||||
|     var audioStream = this.getDefaultAudioStream(data.audio_streams) | ||||
|     this.embeddedCoverArt = data.video_stream ? this.getEmbeddedCoverArt(data.video_stream) : false | ||||
|     this.embeddedCoverArt = data.video_stream ? this.getEmbeddedCoverArt(data.video_stream) : null | ||||
|     this.format = data.format | ||||
|     this.duration = data.duration | ||||
|     this.size = data.size | ||||
|  | ||||
| @ -221,7 +221,7 @@ class Scanner { | ||||
|         } | ||||
|       } else { | ||||
|         var checkRes = audiobook.checkScanData(dataFound, version) | ||||
|         if (checkRes.newAudioFileData.length || checkRes.newOtherFileData.length) { // Audiobook has new files
 | ||||
|         if (checkRes.newAudioFileData.length || checkRes.newOtherFileData.length || libraryScan.scanOptions.forceRescan) { // Audiobook has new files
 | ||||
|           checkRes.audiobook = audiobook | ||||
|           checkRes.bookScanData = dataFound | ||||
|           audiobookDataToRescan.push(checkRes) | ||||
| @ -305,9 +305,11 @@ class Scanner { | ||||
|       return this.rescanAudiobook(abd, libraryScan) | ||||
|     })) | ||||
|     audiobooksUpdated = audiobooksUpdated.filter(ab => ab) // Filter out nulls
 | ||||
|     libraryScan.resultsUpdated += audiobooksUpdated.length | ||||
|     await this.db.updateEntities('audiobook', audiobooksUpdated) | ||||
|     this.emitter('audiobooks_updated', audiobooksUpdated.map(ab => ab.toJSONExpanded())) | ||||
|     if (audiobooksUpdated.length) { | ||||
|       libraryScan.resultsUpdated += audiobooksUpdated.length | ||||
|       await this.db.updateEntities('audiobook', audiobooksUpdated) | ||||
|       this.emitter('audiobooks_updated', audiobooksUpdated.map(ab => ab.toJSONExpanded())) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async scanNewAudiobookDataChunk(newAudiobookDataToScan, libraryScan) { | ||||
| @ -321,24 +323,38 @@ class Scanner { | ||||
|   } | ||||
| 
 | ||||
|   async rescanAudiobook(audiobookCheckData, libraryScan) { | ||||
|     const { newAudioFileData, newOtherFileData, audiobook, bookScanData } = audiobookCheckData | ||||
|     const { newAudioFileData, newOtherFileData, audiobook, bookScanData, updated, existingAudioFileData, existingOtherFileData } = audiobookCheckData | ||||
|     libraryScan.addLog(LogLevel.DEBUG, `Library "${libraryScan.libraryName}" Re-scanning "${audiobook.path}"`) | ||||
|     var hasUpdated = updated | ||||
| 
 | ||||
|     // Sync other files first to use local images as cover before extracting audio file cover
 | ||||
|     if (newOtherFileData.length) { | ||||
|     if (newOtherFileData.length || libraryScan.scanOptions.forceRescan) { | ||||
|       // TODO: Cleanup other file sync
 | ||||
|       var allOtherFiles = newOtherFileData.concat(audiobook._otherFiles) | ||||
|       await audiobook.syncOtherFiles(allOtherFiles, this.MetadataPath, libraryScan.preferOpfMetadata) | ||||
|       var allOtherFiles = newOtherFileData.concat(existingOtherFileData) | ||||
|       if (await audiobook.syncOtherFiles(allOtherFiles, this.MetadataPath, libraryScan.preferOpfMetadata)) { | ||||
|         hasUpdated = true | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // forceRescan all existing audio files - will probe and update ID3 tag metadata
 | ||||
|     if (libraryScan.scanOptions.forceRescan && existingAudioFileData.length) { | ||||
|       if (await AudioFileScanner.scanAudioFiles(existingAudioFileData, bookScanData, audiobook, libraryScan.preferAudioMetadata, libraryScan)) { | ||||
|         hasUpdated = true | ||||
|       } | ||||
|     } | ||||
|     // Scan new audio files
 | ||||
|     if (newAudioFileData.length) { | ||||
|       await AudioFileScanner.scanAudioFiles(newAudioFileData, bookScanData, audiobook, libraryScan.preferAudioMetadata, libraryScan) | ||||
| 
 | ||||
|       // Extract embedded cover art if cover is not already in directory
 | ||||
|       if (await AudioFileScanner.scanAudioFiles(newAudioFileData, bookScanData, audiobook, libraryScan.preferAudioMetadata, libraryScan)) { | ||||
|         hasUpdated = true | ||||
|       } | ||||
|     } | ||||
|     // If an audio file has embedded cover art and no cover is set yet, extract & use it
 | ||||
|     if (newAudioFileData.length || libraryScan.scanOptions.forceRescan) { | ||||
|       if (audiobook.hasEmbeddedCoverArt && !audiobook.cover) { | ||||
|         var outputCoverDirs = this.getCoverDirectory(audiobook) | ||||
|         var relativeDir = await audiobook.saveEmbeddedCoverArt(outputCoverDirs.fullPath, outputCoverDirs.relPath) | ||||
|         if (relativeDir) { | ||||
|           hasUpdated = true | ||||
|           libraryScan.addLog(LogLevel.DEBUG, `Saved embedded cover art "${relativeDir}"`) | ||||
|         } | ||||
|       } | ||||
| @ -346,17 +362,20 @@ class Scanner { | ||||
| 
 | ||||
|     if (!audiobook.audioFilesToInclude.length && !audiobook.ebooks.length) { // Audiobook is invalid
 | ||||
|       audiobook.setInvalid() | ||||
|       hasUpdated = true | ||||
|     } else if (audiobook.isInvalid) { | ||||
|       audiobook.isInvalid = false | ||||
|       hasUpdated = true | ||||
|     } | ||||
| 
 | ||||
|     // Scan for cover if enabled and has no cover
 | ||||
|     // Scan for cover if enabled and has no cover (and author or title has changed OR has been 7 days since last lookup)
 | ||||
|     if (audiobook && libraryScan.findCovers && !audiobook.cover && audiobook.book.shouldSearchForCover) { | ||||
|       var updatedCover = await this.searchForCover(audiobook, libraryScan) | ||||
|       audiobook.book.updateLastCoverSearch(updatedCover) | ||||
|       hasUpdated = true | ||||
|     } | ||||
| 
 | ||||
|     return audiobook | ||||
|     return hasUpdated ? audiobook : null | ||||
|   } | ||||
| 
 | ||||
|   async scanNewAudiobook(audiobookData, preferAudioMetadata, preferOpfMetadata, findCovers, libraryScan = null) { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user