Updated CLI to handle DB sync, UUID as record ids, more details after a library scan.
This commit is contained in:
parent
d6edd3f11d
commit
bc21abc24b
@ -36,6 +36,8 @@ Usage:
|
|||||||
wdiwtlt [options]
|
wdiwtlt [options]
|
||||||
wdiwtlt --version
|
wdiwtlt --version
|
||||||
wdiwtlt --help
|
wdiwtlt --help
|
||||||
|
wdiwtlt --sync
|
||||||
|
wdiwtlt --sync-only
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-c --config <config-file>
|
-c --config <config-file>
|
||||||
@ -55,6 +57,22 @@ Options:
|
|||||||
HikariConfig constructor.
|
HikariConfig constructor.
|
||||||
(see https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby)
|
(see https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby)
|
||||||
|
|
||||||
|
-R --remote-database-config <config-file>
|
||||||
|
|
||||||
|
For use when performing a sync: the path to a data source configuration
|
||||||
|
file for the remote database configuration data. This is processed in
|
||||||
|
the same manner as the local database configuration file.
|
||||||
|
|
||||||
|
--sync-pull
|
||||||
|
|
||||||
|
When performing a sync, copy data present on the remote database and
|
||||||
|
missing locally into the local database.
|
||||||
|
|
||||||
|
--sync-push
|
||||||
|
|
||||||
|
When performing a sync, copy data missing on the remote database and
|
||||||
|
present in the local database into the remote database.
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -185,12 +203,72 @@ Configuration:
|
|||||||
// Create our database instance
|
// Create our database instance
|
||||||
def dbapi
|
def dbapi
|
||||||
try {
|
try {
|
||||||
|
logger.debug('Using datasource config:\n\t{}', hcfg.dataSourceProperties)
|
||||||
def hds = new HikariDataSource(hcfg)
|
def hds = new HikariDataSource(hcfg)
|
||||||
dbapi = new DbApi(hds) }
|
dbapi = new DbApi(hds) }
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
logger.error(
|
||||||
|
"Could not establish a connection to the database:\n\t{}\n",
|
||||||
|
e.localizedMessage, e)
|
||||||
exitErr("Could not establish a connection to the database:\n\t" +
|
exitErr("Could not establish a connection to the database:\n\t" +
|
||||||
e.localizedMessage) }
|
e.localizedMessage) }
|
||||||
|
|
||||||
|
// Look to see if we've been asked to sync with a remote DB
|
||||||
|
if (opts['--sync'] || opts['--sync-only'] ||
|
||||||
|
givenCfg['sync']?.toLowerCase() == 'true') {
|
||||||
|
|
||||||
|
// Get the remote database
|
||||||
|
HikariConfig remoteHcfg
|
||||||
|
File remoteDbCfgFile = new File(
|
||||||
|
opts["--remote-database-config"] ?:
|
||||||
|
givenCfg['sync.database.config.file'] ?:
|
||||||
|
"database.remote.properties")
|
||||||
|
|
||||||
|
// Try to load from the given DB config file
|
||||||
|
if (remoteDbCfgFile && remoteDbCfgFile.exists() &&
|
||||||
|
remoteDbCfgFile.isFile()) {
|
||||||
|
Properties props = new Properties()
|
||||||
|
try { remoteDbCfgFile.withInputStream { props.load(it) } }
|
||||||
|
catch (Exception e) { props.clear() }
|
||||||
|
|
||||||
|
if (props) remoteHcfg = new HikariConfig(props) }
|
||||||
|
|
||||||
|
// Look to see if database connection properties are given in the CLI
|
||||||
|
// config file.
|
||||||
|
if (givenCfg && givenCfg["sync.database.config.dataSourceClassName"]) {
|
||||||
|
Properties props = new Properties()
|
||||||
|
props.putAll(givenCfg
|
||||||
|
.findAll { it.key.startsWith("sync.database.config.") }
|
||||||
|
.collectEntries { [it.key[21..-1], it.value] } )
|
||||||
|
if (props) remoteHcfg = new HikariConfig(props) }
|
||||||
|
|
||||||
|
def remoteDbApi
|
||||||
|
try {
|
||||||
|
logger.debug('Using remote datasource config:\n\t{}',
|
||||||
|
remoteHcfg.dataSourceProperties)
|
||||||
|
def hds = new HikariDataSource(remoteHcfg)
|
||||||
|
remoteDbApi = new DbApi(hds) }
|
||||||
|
catch (Exception e) {
|
||||||
|
logger.error(
|
||||||
|
"Could not establish a connection to the remote database:\n\t{}\n",
|
||||||
|
e.localizedMessage, e)
|
||||||
|
exitErr("Could not establish a connection to the remote database:\n\t" +
|
||||||
|
e.localizedMessage) }
|
||||||
|
|
||||||
|
// Sync the databases
|
||||||
|
try {
|
||||||
|
println 'Syncing database...'
|
||||||
|
dbapi.syncWith(remoteDbApi,
|
||||||
|
opts['--sync-pull'] || givenCfg['sync.pull']?.toLowerCase() == 'true',
|
||||||
|
opts['--sync-push'] || givenCfg['sync.push']?.toLowerCase() == 'true')
|
||||||
|
println 'Sync completed!'
|
||||||
|
if (opts['--sync-only']) System.exit(0) }
|
||||||
|
catch (Exception e) {
|
||||||
|
logger.error( "Unable to sync with remote database:\n\t{}\n",
|
||||||
|
e.localizedMessage, e)
|
||||||
|
exitErr("Unable to sync with remote database:\n\t" +
|
||||||
|
e.localizedMessage) } }
|
||||||
|
|
||||||
// Try to discover the VLC native libraries
|
// Try to discover the VLC native libraries
|
||||||
def vlcj
|
def vlcj
|
||||||
try {
|
try {
|
||||||
@ -204,6 +282,8 @@ Configuration:
|
|||||||
try {
|
try {
|
||||||
def cliInst = new CommandLineInterface(new MediaLibrary(dbapi, libRoot),
|
def cliInst = new CommandLineInterface(new MediaLibrary(dbapi, libRoot),
|
||||||
vlcj, System.in, System.out, givenCfg)
|
vlcj, System.in, System.out, givenCfg)
|
||||||
|
if (opts['--sync'] || givenCfg['sync']?.toLowerCase() == 'true')
|
||||||
|
cliInst.consoleReadBuffer.add('scan')
|
||||||
cliInst.repl() }
|
cliInst.repl() }
|
||||||
catch (Exception e) { e.printStackTrace(); exitErr(e.localizedMessage) }
|
catch (Exception e) { e.printStackTrace(); exitErr(e.localizedMessage) }
|
||||||
finally { if (vlcj) vlcj.release() }
|
finally { if (vlcj) vlcj.release() }
|
||||||
@ -234,7 +314,7 @@ Configuration:
|
|||||||
|
|
||||||
playBookmark.playlistId = playQueue.id
|
playBookmark.playlistId = playQueue.id
|
||||||
playBookmark.playIndex = 0
|
playBookmark.playIndex = 0
|
||||||
playBookmark.mediaFileId = 0
|
playBookmark.mediaFileId = null
|
||||||
|
|
||||||
this.vlcj = vlcj
|
this.vlcj = vlcj
|
||||||
|
|
||||||
@ -367,10 +447,18 @@ Configuration:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MediaLibrary scanMediaLibrary() {
|
public MediaLibrary scanMediaLibrary() {
|
||||||
status.text = "Scanning media library..."
|
msg "Scanning media library..."
|
||||||
|
drawLeader()
|
||||||
def counts = library.rescanLibrary()
|
def counts = library.rescanLibrary()
|
||||||
msg("Scanned ${counts.total} files. Added ${counts.new} and " +
|
String scanResults = "\n\n--------------------\nScan complete:\n\t${counts.total} files total."
|
||||||
"ignored ${counts.ignored} files.")
|
|
||||||
|
if (counts.new) scanResults += "\n\t${counts.new} new files added."
|
||||||
|
if (counts.ignored) scanResults += "\n\t${counts.ignored} files ignored."
|
||||||
|
if (counts.absent)
|
||||||
|
scanResults += "\n\t${counts.absent} files in the database but not stored locally."
|
||||||
|
|
||||||
|
printLongMessage(scanResults)
|
||||||
|
resetStatus()
|
||||||
return library }
|
return library }
|
||||||
|
|
||||||
private String processList(String options, def selection) {
|
private String processList(String options, def selection) {
|
||||||
@ -380,7 +468,8 @@ Configuration:
|
|||||||
else if (options != 'selection') selection = select(options, selection)
|
else if (options != 'selection') selection = select(options, selection)
|
||||||
|
|
||||||
if (!selection) err "Nothing selected."
|
if (!selection) err "Nothing selected."
|
||||||
else return printLongMessage(makeList(selection, { "${it.id}: ${it} " })) }
|
else return printLongMessage(makeList(selection,
|
||||||
|
{ "${it.id.toString()[0..<6]}: ${it} " })) }
|
||||||
|
|
||||||
private List<Model> processSelect(String options, List<Model> selection) {
|
private List<Model> processSelect(String options, List<Model> selection) {
|
||||||
currentSelection = select(options, selection)
|
currentSelection = select(options, selection)
|
||||||
@ -423,6 +512,9 @@ Configuration:
|
|||||||
return selection.collectMany { library.getWhere(modelClass,
|
return selection.collectMany { library.getWhere(modelClass,
|
||||||
[(idKeyFor(selectionClass)): it.id]) }.findAll()
|
[(idKeyFor(selectionClass)): it.id]) }.findAll()
|
||||||
|
|
||||||
|
case ~/absent files/:
|
||||||
|
return library.getMediaFilesWhere(presentLocally: false)
|
||||||
|
|
||||||
case ~/files tagged( as){0,1}((\s[^\s]+)+)/:
|
case ~/files tagged( as){0,1}((\s[^\s]+)+)/:
|
||||||
selectedTags = Matcher.lastMatcher[0][2].split(/\s/)
|
selectedTags = Matcher.lastMatcher[0][2].split(/\s/)
|
||||||
.collect { it?.trim() }.findAll()
|
.collect { it?.trim() }.findAll()
|
||||||
@ -832,6 +924,11 @@ ${cmdStyle}select selected { album | artist | file | playlist | tag }${normalSty
|
|||||||
This example would select all files that are tagged as *either*
|
This example would select all files that are tagged as *either*
|
||||||
instrumental or orchestral
|
instrumental or orchestral
|
||||||
|
|
||||||
|
${cmdStyle}select absent files${normalStyle}
|
||||||
|
|
||||||
|
Select al media files which have entries in the database but are not
|
||||||
|
actually present locally on disk.
|
||||||
|
|
||||||
${cmdStyle}select files tagged as <tag-name>...${normalStyle}
|
${cmdStyle}select files tagged as <tag-name>...${normalStyle}
|
||||||
|
|
||||||
Select all media files tagged with the given tags. If multiple tags are
|
Select all media files tagged with the given tags. If multiple tags are
|
||||||
@ -1083,6 +1180,7 @@ Selecting files:
|
|||||||
select files tagged as <tag>... and not as <tag>...
|
select files tagged as <tag>... and not as <tag>...
|
||||||
select playing { albums | artists | files | playlists | tags }
|
select playing { albums | artists | files | playlists | tags }
|
||||||
select queued { albums | artists | files | playlists | tags }
|
select queued { albums | artists | files | playlists | tags }
|
||||||
|
select absent files
|
||||||
|
|
||||||
list selection
|
list selection
|
||||||
list <selection-criteria>
|
list <selection-criteria>
|
||||||
@ -1197,8 +1295,8 @@ Library Management:
|
|||||||
if (!matches) err "Nothing matches."
|
if (!matches) err "Nothing matches."
|
||||||
|
|
||||||
String englishName = toEnglish(matches[0].class)
|
String englishName = toEnglish(matches[0].class)
|
||||||
if (matches.size() > 1) err "Multiple ${englishName}s match: " +
|
if (matches.size() > 1) err "Multiple ${englishName}s match:\n\t" +
|
||||||
matches.collect { "${it.id}: ${it.name}" }.join(', ')
|
matches.collect { "${it.id}: ${it.name}" }.join('\n\t')
|
||||||
|
|
||||||
return matches[0] }
|
return matches[0] }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user