From bc21abc24b622b7408c7a9998e887ac5ec677382 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Wed, 30 Mar 2016 01:13:53 -0500 Subject: [PATCH] Updated CLI to handle DB sync, UUID as record ids, more details after a library scan. --- .../wdiwtlt/cli/CommandLineInterface.groovy | 112 ++++++++++++++++-- 1 file changed, 105 insertions(+), 7 deletions(-) diff --git a/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy b/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy index 8101cf5..f97f6ac 100644 --- a/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy +++ b/cli/src/main/groovy/com/jdbernard/wdiwtlt/cli/CommandLineInterface.groovy @@ -36,6 +36,8 @@ Usage: wdiwtlt [options] wdiwtlt --version wdiwtlt --help + wdiwtlt --sync + wdiwtlt --sync-only Options: -c --config @@ -55,6 +57,22 @@ Options: HikariConfig constructor. (see https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby) + -R --remote-database-config + + 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: """ @@ -185,12 +203,72 @@ Configuration: // Create our database instance def dbapi try { + logger.debug('Using datasource config:\n\t{}', hcfg.dataSourceProperties) def hds = new HikariDataSource(hcfg) dbapi = new DbApi(hds) } 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" + 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 def vlcj try { @@ -204,6 +282,8 @@ Configuration: try { def cliInst = new CommandLineInterface(new MediaLibrary(dbapi, libRoot), vlcj, System.in, System.out, givenCfg) + if (opts['--sync'] || givenCfg['sync']?.toLowerCase() == 'true') + cliInst.consoleReadBuffer.add('scan') cliInst.repl() } catch (Exception e) { e.printStackTrace(); exitErr(e.localizedMessage) } finally { if (vlcj) vlcj.release() } @@ -234,7 +314,7 @@ Configuration: playBookmark.playlistId = playQueue.id playBookmark.playIndex = 0 - playBookmark.mediaFileId = 0 + playBookmark.mediaFileId = null this.vlcj = vlcj @@ -367,10 +447,18 @@ Configuration: } public MediaLibrary scanMediaLibrary() { - status.text = "Scanning media library..." + msg "Scanning media library..." + drawLeader() def counts = library.rescanLibrary() - msg("Scanned ${counts.total} files. Added ${counts.new} and " + - "ignored ${counts.ignored} files.") + String scanResults = "\n\n--------------------\nScan complete:\n\t${counts.total} files total." + + 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 } private String processList(String options, def selection) { @@ -380,7 +468,8 @@ Configuration: else if (options != 'selection') selection = select(options, selection) 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 processSelect(String options, List selection) { currentSelection = select(options, selection) @@ -423,6 +512,9 @@ Configuration: return selection.collectMany { library.getWhere(modelClass, [(idKeyFor(selectionClass)): it.id]) }.findAll() + case ~/absent files/: + return library.getMediaFilesWhere(presentLocally: false) + case ~/files tagged( as){0,1}((\s[^\s]+)+)/: selectedTags = Matcher.lastMatcher[0][2].split(/\s/) .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* 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 ...${normalStyle} Select all media files tagged with the given tags. If multiple tags are @@ -1083,6 +1180,7 @@ Selecting files: select files tagged as ... and not as ... select playing { albums | artists | files | playlists | tags } select queued { albums | artists | files | playlists | tags } + select absent files list selection list @@ -1197,8 +1295,8 @@ Library Management: if (!matches) err "Nothing matches." String englishName = toEnglish(matches[0].class) - if (matches.size() > 1) err "Multiple ${englishName}s match: " + - matches.collect { "${it.id}: ${it.name}" }.join(', ') + if (matches.size() > 1) err "Multiple ${englishName}s match:\n\t" + + matches.collect { "${it.id}: ${it.name}" }.join('\n\t') return matches[0] }