// Load the specified database template and then the associated images. // templateDatabasePath is the Fully qualified path to the template database file. // Returns true only if both the template and image database file are loaded (regardless of whether any images were loaded) , false otherwise private async Task <bool> TryOpenTemplateAndBeginLoadFoldersAsync(string templateDatabasePath) { // Try to create or open the template database // First, check the file path length and notify the user the template couldn't be loaded because its path is too long if (IsCondition.IsPathLengthTooLong(templateDatabasePath)) { Mouse.OverrideCursor = null; Dialogs.TemplatePathTooLongDialog(this, templateDatabasePath); return(false); } // Second, check to see if we can actually open it. // As we can't have out parameters in an async method, we return the state and the desired templateDatabase as a tuple Tuple <bool, TemplateDatabase> tupleResult = await TemplateDatabase.TryCreateOrOpenAsync(templateDatabasePath).ConfigureAwait(true); this.templateDatabase = tupleResult.Item2; if (!tupleResult.Item1) { // Notify the user the template couldn't be loaded rather than silently doing nothing Mouse.OverrideCursor = null; Dialogs.TemplateFileNotLoadedAsCorruptDialog(this, templateDatabasePath); return(false); } // The .tdb templateDatabase should now be loaded // Try to get the image database file path // importImages will be true if its a new image database file, (meaning we should later ask the user to try to import some images) if (this.TrySelectDatabaseFile(templateDatabasePath, out string fileDatabaseFilePath, out bool importImages) == false) { // No image database file was selected return(false); } // Check the file path length of the .ddb file and notify the user the ddb couldn't be loaded because its path is too long if (IsCondition.IsPathLengthTooLong(fileDatabaseFilePath)) { Mouse.OverrideCursor = null; Dialogs.DatabasePathTooLongDialog(this, fileDatabaseFilePath); return(false); } // Check the expected file path length of the backup files, and warn the user if backups may not be made because thier path is too long if (IsCondition.IsBackupPathLengthTooLong(templateDatabasePath) || IsCondition.IsBackupPathLengthTooLong(fileDatabaseFilePath)) { Mouse.OverrideCursor = null; Dialogs.BackupPathTooLongDialog(this); } // Before fully loading an existing image database, // - upgrade the template tables if needed for backwards compatability (done automatically) // - compare the controls in the .tdb and .ddb template tables to see if there are any added or missing controls TemplateSyncResults templateSyncResults = new Database.TemplateSyncResults(); bool backUpJustMade = false; using (FileDatabase fileDB = await FileDatabase.UpgradeDatabasesAndCompareTemplates(fileDatabaseFilePath, this.templateDatabase, templateSyncResults).ConfigureAwait(true)) { // A file database was available to open if (fileDB != null) { if (templateSyncResults.ControlSynchronizationErrors.Count > 0 || (templateSyncResults.ControlSynchronizationWarnings.Count > 0 && templateSyncResults.SyncRequiredAsDataLabelsDiffer == false)) { // There are unresolvable syncronization issues. Report them now as we cannot use this template. // Depending on the user response, we either abort Timelapse or use the template found in the ddb file Mouse.OverrideCursor = null; Dialog.TemplateSynchronization templatesNotCompatibleDialog; templatesNotCompatibleDialog = new Dialog.TemplateSynchronization(templateSyncResults.ControlSynchronizationErrors, templateSyncResults.ControlSynchronizationWarnings, this); bool?result = templatesNotCompatibleDialog.ShowDialog(); if (result == false) { // user indicates exiting rather than continuing. Application.Current.Shutdown(); return(false); } else { templateSyncResults.UseTemplateDBTemplate = templatesNotCompatibleDialog.UseNewTemplate; templateSyncResults.SyncRequiredAsChoiceMenusDiffer = templateSyncResults.ControlSynchronizationWarnings.Count > 0; } } else if (templateSyncResults.SyncRequiredAsDataLabelsDiffer) { // If there are any new or missing columns, report them now // Depending on the user response, set the useTemplateDBTemplate to signal whether we should: // - update the template and image data columns in the image database // - use the old template Mouse.OverrideCursor = null; TemplateChangedAndUpdate templateChangedAndUpdate = new TemplateChangedAndUpdate(templateSyncResults, this); bool?result1 = templateChangedAndUpdate.ShowDialog(); templateSyncResults.UseTemplateDBTemplate = result1 == true; } else if (templateSyncResults.SyncRequiredAsNonCriticalFieldsDiffer) { // Non critical differences in template, so these don't need reporting templateSyncResults.UseTemplateDBTemplate = true; } backUpJustMade = fileDB.mostRecentBackup != DateTime.MinValue; } else if (File.Exists(fileDatabaseFilePath) == true) { // The .ddb file (which exists) is for some reason unreadable. // It is likely due to an empty or corrupt or otherwise unreadable database in the file. // Raise an error message bool isEmpty = File.Exists(fileDatabaseFilePath) && new FileInfo(fileDatabaseFilePath).Length == 0; Mouse.OverrideCursor = null; Dialogs.DatabaseFileNotLoadedAsCorruptDialog(this, fileDatabaseFilePath, isEmpty); return(false); } ; } // At this point: // - for backwards compatability, all old databases will have been updated (if needed) to the current version standard // - we should have a valid template and image database loaded // - we know if the user wants to use the old or the new template // So lets load the database for real. The useTemplateDBTemplate signals whether to use the template stored in the DDB, or to use the TDB template. FileDatabase fileDatabase = await FileDatabase.CreateOrOpenAsync(fileDatabaseFilePath, this.templateDatabase, this.State.CustomSelectionTermCombiningOperator, templateSyncResults, backUpJustMade).ConfigureAwait(true); // The next test is to test and syncronize (if needed) the default values stored in the fileDB table schema to those stored in the template Dictionary <string, string> columndefaultdict = fileDatabase.SchemaGetColumnsAndDefaultValues(Constant.DBTables.FileData); char[] quote = { '\'' }; foreach (KeyValuePair <string, string> pair in columndefaultdict) { ControlRow row = this.templateDatabase.GetControlFromTemplateTable(pair.Key); if (row != null && pair.Value.Trim(quote) != row.DefaultValue) { // If even one default is different between the schema default and the template default, update the entire file table. fileDatabase.UpgradeFileDBSchemaDefaultsFromTemplate(); break; } } // Check to see if the root folder stored in the database is the same as the actual root folder. If not, ask the user if it should be changed. this.CheckAndCorrectRootFolder(fileDatabase); // Check to see if there are any missing folders as specified by the relative paths. For those missing, ask the user to try to locate those folders. int missingFoldersCount = TimelapseWindow.GetMissingFolders(fileDatabase).Count; if (missingFoldersCount > 0) { Dialogs.MissingFoldersInformationDialog(this, missingFoldersCount); } // Generate and render the data entry controls, regardless of whether there are actually any files in the files database. this.DataHandler = new DataEntryHandler(fileDatabase); this.DataEntryControls.CreateControls(fileDatabase, this.DataHandler); this.SetUserInterfaceCallbacks(); this.MarkableCanvas.DataEntryControls = this.DataEntryControls; // so the markable canvas can access the controls this.DataHandler.ThumbnailGrid = this.MarkableCanvas.ThumbnailGrid; this.DataHandler.MarkableCanvas = this.MarkableCanvas; this.Title = Constant.Defaults.MainWindowBaseTitle + " (" + Path.GetFileName(fileDatabase.FilePath) + ")"; this.State.MostRecentImageSets.SetMostRecent(templateDatabasePath); this.RecentFileSets_Refresh(); // Record the version number of the currently executing version of Timelapse only if its greater than the one already stored in the ImageSet Table. // This will indicate the latest timelapse version that is compatable with the database structure. string currentVersionNumberAsString = VersionChecks.GetTimelapseCurrentVersionNumber().ToString(); if (VersionChecks.IsVersion1GreaterThanVersion2(currentVersionNumberAsString, this.DataHandler.FileDatabase.ImageSet.VersionCompatability)) { this.DataHandler.FileDatabase.ImageSet.VersionCompatability = currentVersionNumberAsString; this.DataHandler.FileDatabase.UpdateSyncImageSetToDatabase(); } // Create an index on RelativePath, File,and RelativePath/File if it doesn't already exist // This is really just a version check in case old databases don't have the index created, // Newer databases (from 2.2.4.4 onwards) will have these indexes created and updated whenever images are loaded or added for the first time. // If the index exists, this is a very cheap operation so there really is no need to do it by a version number check. this.DataHandler.FileDatabase.IndexCreateForFileAndRelativePathIfNotExists(); // If this is a new image database, try to load images (if any) from the folder... if (importImages) { this.TryBeginImageFolderLoad(this.FolderPath, this.FolderPath); } else { await this.OnFolderLoadingCompleteAsync(false).ConfigureAwait(true); } return(true); }
// Given // - a path to a .tdb file (specifying the root folder) // - a list of ddbFiles (which must be located in sub-folders relative to the root folder) // create a .ddb File in the root folder that merges data found in the .ddbFiles into it, in particular, the tables: // - DataTable // - Detections // If fatal errors occur in the merge, abort // Return the relevant error messages in the ErrorsAndWarnings object. // Note: if a merged .ddb File already exists in that root folder, it will be backed up and then over-written public async static Task <ErrorsAndWarnings> TryMergeDatabasesAsync(string tdbFile, List <string> sourceDDBFilePaths, IProgress <ProgressBarArguments> progress) { ErrorsAndWarnings errorMessages = new ErrorsAndWarnings(); string rootFolderPath = Path.GetDirectoryName(tdbFile); string destinationDDBFileName = Constant.File.MergedFileName; string destinationDDBFilePath = Path.Combine(rootFolderPath, destinationDDBFileName); string rootFolderName = rootFolderPath.Split(Path.DirectorySeparatorChar).Last(); if (sourceDDBFilePaths == null) { errorMessages.Errors.Add("No databases (.ddb files) were found in the sub-folders, so there was nothing to merge."); return(errorMessages); } // if the mergedatabase file was previously created, it may be included in the source list. // So just skip over it, as it no longer exists and we don't actually want it sourceDDBFilePaths.RemoveAll(Item => Item == destinationDDBFilePath); if (sourceDDBFilePaths.Count == 0) { errorMessages.Errors.Add("No databases (.ddb files) were found in the sub-folders, so there was nothing to merge."); return(errorMessages); } // Check to see if we can actually open the template. // As we can't have out parameters in an async method, we return the state and the desired templateDatabase as a tuple // Original form: if (!(await TemplateDatabase.TryCreateOrOpenAsync(templateDatabasePath, out this.templateDatabase).ConfigureAwait(true)) Tuple <bool, TemplateDatabase> tupleResult = await TemplateDatabase.TryCreateOrOpenAsync(tdbFile).ConfigureAwait(true); TemplateDatabase templateDatabase = tupleResult.Item2; if (!tupleResult.Item1) { // notify the user the template couldn't be loaded rather than silently doing nothing errorMessages.Errors.Add("Could not open the template .tdb file: " + tdbFile); return(errorMessages); } // if the merge file exists, move it to the backup folder as we will be overwriting it. bool backupMade = false; if (File.Exists(destinationDDBFilePath)) { // Backup the old merge file by moving it to the backup folder // Note that we do the move instead of copy as we will be overwriting the file anyways backupMade = FileBackup.TryCreateBackup(destinationDDBFilePath, true); } FileDatabase fd = await FileDatabase.CreateEmptyDatabase(destinationDDBFilePath, templateDatabase).ConfigureAwait(true); fd.Dispose(); fd = null; // Open the database SQLiteWrapper destinationDDB = new SQLiteWrapper(destinationDDBFilePath); // Get the DataLabels from the DataTable in the main database. // We will later check to see if they match their counterparts in each database to merge in List <string> mergedDDBDataLabels = destinationDDB.SchemaGetColumns(Constant.DBTables.FileData); int sourceDDBFilePathsCount = sourceDDBFilePaths.Count; for (int i = 0; i < sourceDDBFilePathsCount; i++) { if (sourceDDBFilePaths[i].Equals(destinationDDBFilePath)) { // if the mergedatabase file was previously created, it may be included in the source list. // So just skip over it, as it no longer exists and we don't actually want it continue; } // Try to merge each database into the merged database await Task.Run(() => { // Report progress, introducing a delay to allow the UI thread to update and to make the progress bar linger on the display progress.Report(new ProgressBarArguments((int)((i + 1) / (double)sourceDDBFilePathsCount * 100.0), String.Format("Merging {0}/{1} databases. Please wait...", i + 1, sourceDDBFilePathsCount), "Merging...", false, false)); Thread.Sleep(250); ListComparisonEnum listComparisonEnum = MergeDatabases.InsertSourceDataBaseTablesintoDestinationDatabase(destinationDDB, sourceDDBFilePaths[i], rootFolderPath, mergedDDBDataLabels); if (listComparisonEnum != ListComparisonEnum.Identical) { string message = listComparisonEnum == ListComparisonEnum.ElementsDiffer ? "Its template uses different data labels" : "Its template has the same data labels, but in a different order"; string trimmedPath = sourceDDBFilePaths[i].Substring(rootFolderPath.Length + 1); errorMessages.Warnings.Add(String.Format("'{0}' was skipped. {1}", trimmedPath, message)); } }).ConfigureAwait(true); } // After the merged database is constructed, set the Folder column to the current root folder if (!String.IsNullOrEmpty(rootFolderName)) { destinationDDB.ExecuteNonQuery(Sql.Update + Constant.DBTables.FileData + Sql.Set + Constant.DatabaseColumn.Folder + Sql.Equal + Sql.Quote(rootFolderName)); } // After the merged database is constructed, reset fields in the ImageSetTable to the defaults i.e., first row, selection all, if (!String.IsNullOrEmpty(rootFolderName)) { destinationDDB.ExecuteNonQuery(Sql.Update + Constant.DBTables.ImageSet + Sql.Set + Constant.DatabaseColumn.MostRecentFileID + Sql.Equal + "1"); destinationDDB.ExecuteNonQuery(Sql.Update + Constant.DBTables.ImageSet + Sql.Set + Constant.DatabaseColumn.Selection + Sql.Equal + ((int)FileSelectionEnum.All).ToString()); destinationDDB.ExecuteNonQuery(Sql.Update + Constant.DBTables.ImageSet + Sql.Set + Constant.DatabaseColumn.SortTerms + Sql.Equal + Sql.Quote(Constant.DatabaseValues.DefaultSortTerms)); } if (backupMade && (errorMessages.Errors.Any() || errorMessages.Warnings.Any())) { errorMessages.Warnings.Add(String.Format("Note: A backup of your original {0} can be found in the {1} folder", destinationDDBFileName, Constant.File.BackupFolder)); } return(errorMessages); }