mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-03 19:07:00 -05:00 
			
		
		
		
	Change Library object use mediaCategory, allow adding new manual folder path, validate folder paths, fix Watcher re-init after folder path updates
This commit is contained in:
		
							parent
							
								
									7348432594
								
							
						
					
					
						commit
						deea6702f0
					
				@ -11,7 +11,7 @@
 | 
			
		||||
          <ui-text-input-with-label v-model="name" label="Library Name" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-1/2 md:w-72 px-1 py-1 md:py-0">
 | 
			
		||||
          <ui-media-type-picker v-model="mediaType" />
 | 
			
		||||
          <ui-media-category-picker v-model="mediaCategory" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-1/2 md:w-72 px-1 py-1 md:py-0">
 | 
			
		||||
          <ui-dropdown v-model="provider" :items="providers" label="Metadata Provider" small />
 | 
			
		||||
@ -22,10 +22,16 @@
 | 
			
		||||
        <p class="px-1 text-sm font-semibold">Folders</p>
 | 
			
		||||
        <div v-for="(folder, index) in folders" :key="index" class="w-full flex items-center py-1 px-2">
 | 
			
		||||
          <span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
 | 
			
		||||
          <ui-editable-text v-model="folder.fullPath" type="text" class="w-full" />
 | 
			
		||||
          <ui-editable-text v-model="folder.fullPath" readonly type="text" class="w-full" />
 | 
			
		||||
          <span v-show="folders.length > 1" class="material-icons ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <p v-if="!folders.length" class="text-sm text-gray-300 px-1 py-2">No folders</p>
 | 
			
		||||
        <!-- <p v-if="!folders.length" class="text-sm text-gray-300 px-1 py-2">No folders</p> -->
 | 
			
		||||
 | 
			
		||||
        <div class="flex py-1 px-2 items-center w-full">
 | 
			
		||||
          <span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
 | 
			
		||||
          <ui-editable-text v-model="newFolderPath" placeholder="New folder path" type="text" class="w-full" />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <ui-btn class="w-full mt-2" color="primary" @click="showDirectoryPicker = true">Browse for Folder</ui-btn>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="absolute bottom-0 left-0 w-full py-4 px-4">
 | 
			
		||||
@ -61,11 +67,12 @@ export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      name: '',
 | 
			
		||||
      provider: '',
 | 
			
		||||
      mediaType: '',
 | 
			
		||||
      provider: 'google',
 | 
			
		||||
      mediaCategory: '',
 | 
			
		||||
      folders: [],
 | 
			
		||||
      showDirectoryPicker: false,
 | 
			
		||||
      disableWatcher: false
 | 
			
		||||
      disableWatcher: false,
 | 
			
		||||
      newFolderPath: ''
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@ -83,7 +90,7 @@ export default {
 | 
			
		||||
      var newfolderpaths = this.folderPaths.join(',')
 | 
			
		||||
      var origfolderpaths = this.library.folders.map((f) => f.fullPath).join(',')
 | 
			
		||||
 | 
			
		||||
      return newfolderpaths === origfolderpaths && this.name === this.library.name && this.provider === this.library.provider && this.disableWatcher === this.library.disableWatcher && this.mediaType === this.library.mediaType
 | 
			
		||||
      return newfolderpaths === origfolderpaths && this.name === this.library.name && this.provider === this.library.provider && this.disableWatcher === this.library.disableWatcher && this.mediaCategory === this.library.mediaCategory && !this.newFolderPath
 | 
			
		||||
    },
 | 
			
		||||
    providers() {
 | 
			
		||||
      return this.$store.state.scanners.providers
 | 
			
		||||
@ -103,10 +110,10 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    init() {
 | 
			
		||||
      this.name = this.library ? this.library.name : ''
 | 
			
		||||
      this.provider = this.library ? this.library.provider : ''
 | 
			
		||||
      this.provider = this.library ? this.library.provider : 'google'
 | 
			
		||||
      this.folders = this.library ? this.library.folders.map((p) => ({ ...p })) : []
 | 
			
		||||
      this.disableWatcher = this.library ? !!this.library.disableWatcher : false
 | 
			
		||||
      this.mediaType = this.library ? this.library.mediaType : 'default'
 | 
			
		||||
      this.mediaCategory = this.library ? this.library.mediaCategory : 'default'
 | 
			
		||||
      this.showDirectoryPicker = false
 | 
			
		||||
    },
 | 
			
		||||
    selectFolder(fullPath) {
 | 
			
		||||
@ -114,6 +121,10 @@ export default {
 | 
			
		||||
      this.showDirectoryPicker = false
 | 
			
		||||
    },
 | 
			
		||||
    submit() {
 | 
			
		||||
      if (this.newFolderPath) {
 | 
			
		||||
        this.folders.push({ fullPath: this.newFolderPath })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.library) {
 | 
			
		||||
        this.updateLibrary()
 | 
			
		||||
      } else {
 | 
			
		||||
@ -133,8 +144,8 @@ export default {
 | 
			
		||||
        name: this.name,
 | 
			
		||||
        provider: this.provider,
 | 
			
		||||
        folders: this.folders,
 | 
			
		||||
        mediaType: this.mediaType,
 | 
			
		||||
        icon: this.mediaType,
 | 
			
		||||
        mediaCategory: this.mediaCategory,
 | 
			
		||||
        icon: this.mediaCategory,
 | 
			
		||||
        disableWatcher: this.disableWatcher
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -169,8 +180,8 @@ export default {
 | 
			
		||||
        name: this.name,
 | 
			
		||||
        provider: this.provider,
 | 
			
		||||
        folders: this.folders,
 | 
			
		||||
        mediaType: this.mediaType,
 | 
			
		||||
        icon: this.mediaType,
 | 
			
		||||
        mediaCategory: this.mediaCategory,
 | 
			
		||||
        icon: this.mediaCategory,
 | 
			
		||||
        disableWatcher: this.disableWatcher
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ export default {
 | 
			
		||||
    disabled: Boolean,
 | 
			
		||||
    label: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: 'Media Type'
 | 
			
		||||
      default: 'Media Category'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
@ -51,19 +51,19 @@ export default {
 | 
			
		||||
          name: 'Default'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: 'audiobooks',
 | 
			
		||||
          id: 'audiobook',
 | 
			
		||||
          name: 'Audiobooks'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: 'books',
 | 
			
		||||
          id: 'book',
 | 
			
		||||
          name: 'Books'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: 'podcasts',
 | 
			
		||||
          id: 'podcast',
 | 
			
		||||
          name: 'Podcasts'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: 'comics',
 | 
			
		||||
          id: 'comic',
 | 
			
		||||
          name: 'Comics'
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
@ -85,12 +85,15 @@ class FolderWatcher extends EventEmitter {
 | 
			
		||||
    if (libwatcher) {
 | 
			
		||||
      libwatcher.name = library.name
 | 
			
		||||
 | 
			
		||||
      // If any folder paths were added or removed then re-init watcher
 | 
			
		||||
      var pathsToAdd = library.folderPaths.filter(path => !libwatcher.paths.includes(path))
 | 
			
		||||
      if (pathsToAdd.length) {
 | 
			
		||||
        Logger.info(`[Watcher] Adding paths to library watcher "${library.name}"`)
 | 
			
		||||
        libwatcher.paths = library.folderPaths
 | 
			
		||||
        libwatcher.folders = library.folders
 | 
			
		||||
        libwatcher.watcher.watchPaths(pathsToAdd)
 | 
			
		||||
      var pathsRemoved = libwatcher.paths.filter(path => !library.folderPaths.includes(path))
 | 
			
		||||
      if (pathsToAdd.length || pathsRemoved.length) {
 | 
			
		||||
        Logger.info(`[Watcher] Re-Initializing watcher for "${library.name}".`)
 | 
			
		||||
 | 
			
		||||
        libwatcher.watcher.close()
 | 
			
		||||
        this.libraryWatchers = this.libraryWatchers.filter(lw => lw.id !== libwatcher.id)
 | 
			
		||||
        this.buildLibraryWatcher(library)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,6 @@
 | 
			
		||||
const Path = require('path')
 | 
			
		||||
const fs = require('fs-extra')
 | 
			
		||||
const filePerms = require('../utils/filePerms')
 | 
			
		||||
const Logger = require('../Logger')
 | 
			
		||||
const Library = require('../objects/Library')
 | 
			
		||||
const { sort, createNewSortInstance } = require('fast-sort')
 | 
			
		||||
@ -51,6 +54,30 @@ class LibraryController {
 | 
			
		||||
  async update(req, res) {
 | 
			
		||||
    var library = req.library
 | 
			
		||||
 | 
			
		||||
    // Validate new folder paths exist or can be created & resolve rel paths
 | 
			
		||||
    //   returns 400 if a new folder fails to access
 | 
			
		||||
    if (req.body.folders) {
 | 
			
		||||
      var newFolderPaths = []
 | 
			
		||||
      req.body.folders = req.body.folders.map(f => {
 | 
			
		||||
        if (!f.id) {
 | 
			
		||||
          f.fullPath = Path.resolve(f.fullPath)
 | 
			
		||||
          newFolderPaths.push(f.fullPath)
 | 
			
		||||
        }
 | 
			
		||||
        return f
 | 
			
		||||
      })
 | 
			
		||||
      for (var path of newFolderPaths) {
 | 
			
		||||
        var success = await fs.ensureDir(path).then(() => true).catch((error) => {
 | 
			
		||||
          Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
 | 
			
		||||
          return false
 | 
			
		||||
        })
 | 
			
		||||
        if (!success) {
 | 
			
		||||
          return res.status(400).send(`Invalid folder directory "${path}"`)
 | 
			
		||||
        } else {
 | 
			
		||||
          await filePerms.setDefault(path)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var hasUpdates = library.update(req.body)
 | 
			
		||||
    // TODO: Should check if this is an update to folder paths or name only
 | 
			
		||||
    if (hasUpdates) {
 | 
			
		||||
@ -400,7 +427,7 @@ class LibraryController {
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    var itemKey = req.library.itemMediaType
 | 
			
		||||
    var itemKey = req.library.mediaType
 | 
			
		||||
    var results = {
 | 
			
		||||
      [itemKey]: itemMatches.slice(0, maxResults),
 | 
			
		||||
      tags: Object.values(tagMatches).slice(0, maxResults),
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,8 @@ class Library {
 | 
			
		||||
    this.folders = []
 | 
			
		||||
    this.displayOrder = 1
 | 
			
		||||
    this.icon = 'database'
 | 
			
		||||
    this.mediaType = 'default'
 | 
			
		||||
    this.mediaCategory = 'default' // default, podcast, book, audiobook, comic
 | 
			
		||||
    this.mediaType = 'book' // book, podcast
 | 
			
		||||
    this.provider = 'google'
 | 
			
		||||
    this.disableWatcher = false
 | 
			
		||||
 | 
			
		||||
@ -25,9 +26,6 @@ class Library {
 | 
			
		||||
  get folderPaths() {
 | 
			
		||||
    return this.folders.map(f => f.fullPath)
 | 
			
		||||
  }
 | 
			
		||||
  get itemMediaType() {
 | 
			
		||||
    return this.mediaType === 'podcast' ? 'podcast' : 'book'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  construct(library) {
 | 
			
		||||
    this.id = library.id
 | 
			
		||||
@ -35,12 +33,31 @@ class Library {
 | 
			
		||||
    this.folders = (library.folders || []).map(f => new Folder(f))
 | 
			
		||||
    this.displayOrder = library.displayOrder || 1
 | 
			
		||||
    this.icon = library.icon || 'database'
 | 
			
		||||
    this.mediaType = library.mediaType || 'default'
 | 
			
		||||
    this.mediaCategory = library.mediaCategory || library.mediaType || 'default' // mediaCategory used to be mediaType
 | 
			
		||||
    this.mediaType = library.mediaType
 | 
			
		||||
    this.provider = library.provider || 'google'
 | 
			
		||||
    this.disableWatcher = !!library.disableWatcher
 | 
			
		||||
 | 
			
		||||
    this.createdAt = library.createdAt
 | 
			
		||||
    this.lastUpdate = library.lastUpdate
 | 
			
		||||
    this.cleanOldValues() // mediaCategory and mediaType were changed for v2
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cleanOldValues() {
 | 
			
		||||
    if (this.mediaCategory.endsWith('s')) {
 | 
			
		||||
      this.mediaCategory = this.mediaCategory.slice(0, -1)
 | 
			
		||||
    }
 | 
			
		||||
    var availableCategories = ['default', 'audiobook', 'book', 'comic', 'podcast']
 | 
			
		||||
    if (!availableCategories.includes(this.mediaCategory)) {
 | 
			
		||||
      this.mediaCategory = 'default'
 | 
			
		||||
    }
 | 
			
		||||
    if (!availableCategories.includes(this.icon)) {
 | 
			
		||||
      this.icon = this.mediaCategory
 | 
			
		||||
    }
 | 
			
		||||
    if (!this.mediaType || (this.mediaType !== 'podcast' && this.mediaType !== 'book')) {
 | 
			
		||||
      if (this.mediaCategory === 'podcast') this.mediaType = 'podcast'
 | 
			
		||||
      else this.mediaType = 'book'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toJSON() {
 | 
			
		||||
@ -50,6 +67,7 @@ class Library {
 | 
			
		||||
      folders: (this.folders || []).map(f => f.toJSON()),
 | 
			
		||||
      displayOrder: this.displayOrder,
 | 
			
		||||
      icon: this.icon,
 | 
			
		||||
      mediaCategory: this.mediaCategory,
 | 
			
		||||
      mediaType: this.mediaType,
 | 
			
		||||
      provider: this.provider,
 | 
			
		||||
      disableWatcher: this.disableWatcher,
 | 
			
		||||
@ -77,7 +95,8 @@ class Library {
 | 
			
		||||
    }
 | 
			
		||||
    this.displayOrder = data.displayOrder || 1
 | 
			
		||||
    this.icon = data.icon || 'database'
 | 
			
		||||
    this.mediaType = data.mediaType || 'default'
 | 
			
		||||
    this.mediaCategory = data.mediaCategory || 'default'
 | 
			
		||||
    this.mediaType = data.mediaType || 'book'
 | 
			
		||||
    this.disableWatcher = !!data.disableWatcher
 | 
			
		||||
    this.createdAt = Date.now()
 | 
			
		||||
    this.lastUpdate = Date.now()
 | 
			
		||||
@ -85,22 +104,14 @@ class Library {
 | 
			
		||||
 | 
			
		||||
  update(payload) {
 | 
			
		||||
    var hasUpdates = false
 | 
			
		||||
    if (payload.name && payload.name !== this.name) {
 | 
			
		||||
      this.name = payload.name
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.provider && payload.provider !== this.provider) {
 | 
			
		||||
      this.provider = payload.provider
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.mediaType && payload.mediaType !== this.mediaType) {
 | 
			
		||||
      this.mediaType = payload.mediaType
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.icon && payload.icon !== this.icon) {
 | 
			
		||||
      this.icon = payload.icon
 | 
			
		||||
      hasUpdates = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var keysToCheck = ['name', 'provider', 'mediaCategory', 'mediaType', 'icon']
 | 
			
		||||
    keysToCheck.forEach((key) => {
 | 
			
		||||
      if (payload[key] && payload[key] !== this[key]) {
 | 
			
		||||
        this[key] = payload[key]
 | 
			
		||||
        hasUpdates = true
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (payload.disableWatcher !== this.disableWatcher) {
 | 
			
		||||
      this.disableWatcher = !!payload.disableWatcher
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,6 @@ class LibraryFile {
 | 
			
		||||
    this.metadata = fileMetadata
 | 
			
		||||
    this.addedAt = Date.now()
 | 
			
		||||
    this.updatedAt = Date.now()
 | 
			
		||||
    console.log('Library file set from path', path, 'rel path', relPath)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
module.exports = LibraryFile
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user