From d6edd3f11dbdd93d88e41a57256b5cf434a4c702 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Wed, 30 Mar 2016 01:12:41 -0500 Subject: [PATCH] Added core support for DB synchronization. --- .../com/jdbernard/wdiwtlt/MediaLibrary.groovy | 23 ++++++- .../com/jdbernard/wdiwtlt/db/DbApi.groovy | 68 +++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy b/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy index 1be2f27..e7dca11 100644 --- a/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy +++ b/core/src/main/groovy/com/jdbernard/wdiwtlt/MediaLibrary.groovy @@ -50,7 +50,10 @@ public class MediaLibrary { staleBookmarks.each { dbapi.delete(it) } } } public def rescanLibrary() { - def results = [ total: 0, ignored: 0, new: 0] + def results = [ total: 0, ignored: 0, new: 0, present: 0, absent: 0] + + List missingFiles = dbapi.getMediaFiles() + List foundFiles = [] Date startDate = new Date() libraryRoot.eachFileRecurse { file -> @@ -60,7 +63,23 @@ public class MediaLibrary { results.total++ if (!mf) results.ignored++ - else if (mf.dateAdded > startDate) results.new++ } + else { + foundFiles << mf + if (missingFiles.contains(mf)) missingFiles.remove(mf) + if (mf.dateAdded > startDate) results.new++ } } + + foundFiles.each { mf -> + if (!mf.presentLocally) { + mf.presentLocally = true + dbapi.update(mf) } } + + missingFiles.each { mf -> + if (mf.presentLocally) { + mf.presentLocally = false + dbapi.update(mf) } } + + results.present = foundFiles.size() + results.absent = missingFiles.size() return results } diff --git a/core/src/main/groovy/com/jdbernard/wdiwtlt/db/DbApi.groovy b/core/src/main/groovy/com/jdbernard/wdiwtlt/db/DbApi.groovy index 650e0d2..952df67 100644 --- a/core/src/main/groovy/com/jdbernard/wdiwtlt/db/DbApi.groovy +++ b/core/src/main/groovy/com/jdbernard/wdiwtlt/db/DbApi.groovy @@ -927,4 +927,72 @@ public class DbApi { static def getInstanceFields(Class modelClass) { return modelClass.fields.findAll { !Modifier.isStatic(it.modifiers) } } + + /// ### DB Sync/Replication + + public def diffWith(DbApi that) { + + def results = [ + ours: [ modelIds: [:], associations: [:] ], + theirs:[ modelIds: [:], associations: [:] ] ] + + [Album, Artist, Image, MediaFile, Playlist, Bookmark, + Tag].each { modelClass -> + + List ourIds = this.getAllIds(modelClass) + List theirIds = that.getAllIds(modelClass) + + results.ours.modelIds[modelClass] = ourIds - theirIds + results.theirs.modelIds[modelClass] = theirIds - ourIds } + + ['albums_images', 'albums_media_files', 'artists_albums', + 'artists_images', 'artists_media_files', 'media_files_tags', + 'playlists_media_files'].each { tableName -> + + def query = 'SELECT * FROM ' + tableName; + def allOurRows = this.sql.rows(query) + def allTheirRows = that.sql.rows(query) + + def ourRows = allOurRows.clone() + def theirRows = allTheirRows.clone() + + allOurRows.each { ourRow -> + allTheirRows.each { theirRow -> + if (ourRow[0] == theirRow[0]) { + ourRows.remove(ourRow) + theirRows.remove(theirRow) } } } + + results.ours.associations[tableName] = ourRows + results.theirs.associations[tableName] = theirRows } + + return results } + + public def ingestDiff(DbApi that, def diff) { + + diff.modelIds.each { modelClass, ids -> + ids.each { id -> this.create(that.getById(modelClass, id)) } } + + diff.associations.each { tableName, rows -> + String placeholders = null + String query = null + rows.each { row -> + if (!placeholders) + placeholders = (1..row.size()).collect { '?' }.join(', ') + + if (!query) + query = "INSERT INTO ${tableName} VALUES (${placeholders})" + + logger.debug("Adding association.\n\tSQL: {}\n\tPARAMS: {}", + query, row.values() as List) + this.sql.executeInsert(query, row.values() as List) } } } + + public def syncWith(DbApi that, boolean pull = true, + boolean push = false) { + def diff = this.diffWith(that) + + if (pull) this.ingestDiff(that, diff.theirs) + if (push) that.ingestDiff(this, diff.ours) + + return diff } + }