private static void Merge(Options.MergeOptions options) { RobustIO.DeleteDirectoryAndContents(options.WorkDir); Settings.TempDir = options.WorkDir; // restore previous data var restoreOptions = new Options.RestoreOptions(options); RestoreLanguageDepot(restoreOptions); RestoreMongoDb(restoreOptions); // run merge LfMergeHelper.Run($"--project {options.Project} --clone --action=Synchronize"); Console.WriteLine("Successfully merged test data"); // save merged data var saveOptions = new Options.SaveOptions(options) { WorkDir = Path.Combine(options.WorkDir, "LanguageDepot"), CommitMsg = options.CommitMsg ?? "Merged test data" }; SaveLanguageDepot(saveOptions); SaveLanguageDepotNoOpPatchIfNecessary(options.ModelVersion, options.LanguageDepotVersion + 1); SaveMongoDb(saveOptions); }
public void RefreshPatches(string modelVersion) { // Import the initial patch file in a temporary directory var sourceMongoSource = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); InitSourceDir(sourceMongoSource); var destMongoSource = GetMongoSourceDir(modelVersion); InitSourceDir(destMongoSource); var mongoPatchDir = GetMongoPatchDir(modelVersion); var patchFiles = Directory.GetFiles(mongoPatchDir, "*.patch"); Array.Sort(patchFiles); foreach (var file in patchFiles) { Console.WriteLine($"\tProcessing {Path.GetFileName(file)}"); var patchNoStr = Path.GetFileName(file).Substring(0, 4); var patchNo = int.Parse(patchNoStr); TestHelper.Run(Git, $"am --ignore-whitespace {file}", sourceMongoSource); TestHelper.Run(Git, $"tag r{patchNo}", sourceMongoSource); CopyFiles(sourceMongoSource, destMongoSource, true); RestoreDatabaseInternal(modelVersion); SaveDatabaseInternal(mongoPatchDir, modelVersion, ExtractSubject(file), patchNo, destMongoSource); } RobustIO.DeleteDirectoryAndContents(sourceMongoSource); }
public void HandleModifiedFile_NoLocalBook_DoesNothing() { // Setup // // Simulate that a book appeared remotely. We should eventually get a created notice. // Sometimes, for reasons we don't fully understand, we get a modified notice // first. Or the book might be modified again before we fetch it. In any case, // we don't need modify messages until we fetch a local copy. const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); // Writes the book to disk based on the above specified values string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); // pretending we had nothing local before the change. RobustIO.DeleteDirectory(bookFolderPath, true); // Anticipate verification var prevMessages = _tcLog.Messages.Count; _mockTcManager.Setup(m => m.RaiseBookStatusChanged(It.IsAny <BookStatusChangeEventArgs>())) .Throws(new ArgumentException("RaiseBookStatus should not be called")); // System Under Test // _collection.HandleModifiedFile(new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); // Verification Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages)); }
public static void DeleteFolderThatMayBeInUse(string folder) { if (Directory.Exists(folder)) { try { RobustIO.DeleteDirectory(folder, true); } catch (Exception e) { try { Debug.WriteLine(e.Message); //maybe we can at least clear it out a bit string[] files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories); foreach (string s in files) { try { RobustFile.Delete(s); } catch (Exception) { } } //sleep and try again (in case some other thread will let go of them) Thread.Sleep(1000); RobustIO.DeleteDirectory(folder, true); } catch (Exception) { } } } }
public void MigrateOldConfigurationsIfNeeded_MatchesLabelsWhenUIIsLocalized() { // Localize a Part's label to German (sufficient to cause a mismatched nodes crash if one config's labels are localized) var localizedPartLabels = new Dictionary <string, string>(); localizedPartLabels["Main Entry"] = "Haupteintrag"; var pathsToL10NStrings = (Dictionary <string, Dictionary <string, string> >)ReflectionHelper.GetField(StringTable.Table, "m_pathsToStrings"); pathsToL10NStrings["group[@id = 'LocalizedAttributes']/"] = localizedPartLabels; var configSettingsDir = LcmFileHelper.GetConfigSettingsDir(Path.GetDirectoryName(Cache.ProjectId.Path)); var newConfigFilePath = Path.Combine(configSettingsDir, DictionaryConfigurationListener.DictionaryConfigurationDirectoryName, "Lexeme" + DictionaryConfigurationModel.FileExtension); Assert.False(File.Exists(newConfigFilePath), "should not yet be migrated"); Directory.CreateDirectory(configSettingsDir); File.WriteAllLines(Path.Combine(configSettingsDir, "Test.fwlayout"), new[] { @"<layoutType label='Lexeme-based (complex forms as main entries)' layout='publishStem'><configure class='LexEntry' label='Main Entry' layout='publishStemEntry' />", @"<configure class='LexEntry' label='Minor Entry' layout='publishStemMinorEntry' hideConfig='true' /></layoutType>'" }); var migrator = new DictionaryConfigurationMigrator(m_propertyTable, m_mediator); Assert.DoesNotThrow(() => migrator.MigrateOldConfigurationsIfNeeded(), "ArgumentException indicates localized labels."); // SUT var updatedConfigModel = new DictionaryConfigurationModel(newConfigFilePath, Cache); Assert.AreEqual(2, updatedConfigModel.Parts.Count, "Should have 2 top-level nodes"); Assert.AreEqual("Main Entry", updatedConfigModel.Parts[0].Label); RobustIO.DeleteDirectoryAndContents(configSettingsDir); }
public static void SimulateRename(Bloom.TeamCollection.TeamCollection tc, string oldName, string newName) { var oldPath = Path.Combine(tc.LocalCollectionFolder, oldName); var newPath = Path.Combine(tc.LocalCollectionFolder, newName); RobustIO.MoveDirectory(oldPath, newPath); RobustFile.Move(Path.Combine(newPath, oldName + ".htm"), Path.Combine(newPath, newName + ".htm")); tc.HandleBookRename(oldName, newName); }
public void AsyncLocalCheckIn_NoPreviousRepoCreation_Throws() { Assert.Throws <InvalidOperationException>(() => { //simulate not having previously created a repository RobustIO.DeleteDirectoryAndContents(_pathToTestRoot.CombineForPath(".hg")); _model.AsyncLocalCheckIn("testing", null); }); }
public override void FixtureTeardown() { RobustIO.DeleteDirectoryAndContents(Cache.ProjectId.Path); base.FixtureTeardown(); m_application.Dispose(); m_window.Dispose(); m_mediator.Dispose(); FwRegistrySettings.Release(); }
public void LoadXElement_WithNRFileName_DoesNotCrash() { using (var folder = new TemporaryFolder("Balangao کتابونه")) { var input = new XElement("root"); var fileName = Path.Combine(folder.Path, "Balangao کتابونه.xml"); input.Save(fileName); Assert.DoesNotThrow(() => RobustIO.LoadXElement(fileName)); } }
public void DeleteDirectoryAndContents_NoOverrideContainsReadOnlyFile_ReturnsFalse() { using (var tempDir = new TemporaryFolder("DeleteDirectoryAndContents_NoOverrideContainsReadOnlyFile_ReturnsFalse")) { var fileName = tempDir.Combine("tempFile.txt"); File.WriteAllText(fileName, @"Some test text"); new FileInfo(fileName).IsReadOnly = true; Assert.IsFalse(RobustIO.DeleteDirectoryAndContents(tempDir.Path, overrideReadOnly: false)); Assert.IsTrue(Directory.Exists(tempDir.Path), "Did not expect it to delete directory because of the readonly file"); } }
public void DeleteDirectoryAndContents_ContainsReadOnlyFile_StillRemoves() { using (var tempDir = new TemporaryFolder("DeleteDirectoryAndContents_ContainsReadOnlyFile_StillRemoves")) { var fileName = tempDir.Combine("tempFile.txt"); File.WriteAllText(fileName, @"Some test text"); new FileInfo(fileName).IsReadOnly = true; RobustIO.DeleteDirectoryAndContents(tempDir.Path); Assert.IsFalse(Directory.Exists(tempDir.Path), "Did not delete directory"); } }
public void SaveXml() { using (var temp = new TempFile()) { var doc = new XmlDocument(); doc.LoadXml("<?xml version='1.0' encoding='utf-8'?><root>This is a test</root>"); RobustIO.SaveXml(doc, temp.Path); // Not a great test, since it captures some non-essential features of how XmlDocument writes its content. // But this is at least a reasonable result to get from doing the above. Assert.That(File.ReadAllText(temp.Path, Encoding.UTF8), Is.EqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + Environment.NewLine + "<root>This is a test</root>")); } }
public static void UpdateImgMetadataAttributesToMatchImage(string folderPath, XmlElement imgElement, IProgress progress, Metadata metadata) { //see also PageEditingModel.UpdateMetadataAttributesOnImage(), which does the same thing but on the browser dom var url = HtmlDom.GetImageElementUrl(new ElementProxy(imgElement)); string fileName = url.PathOnly.NotEncoded; if (fileName.ToLowerInvariant() == "placeholder.png" || fileName.ToLowerInvariant() == "license.png") { return; } if (string.IsNullOrEmpty(fileName)) { Logger.WriteEvent("Book.UpdateImgMetdataAttributesToMatchImage() Warning: img has no or empty src attribute"); //Debug.Fail(" (Debug only) img has no or empty src attribute"); return; // they have bigger problems, which aren't appropriate to deal with here. } if (metadata == null) { // The fileName might be URL encoded. See https://silbloom.myjetbrains.com/youtrack/issue/BL-3901. var path = UrlPathString.GetFullyDecodedPath(folderPath, ref fileName); progress.WriteStatus("Reading metadata from " + fileName); if (!RobustFile.Exists(path)) // they have bigger problems, which aren't appropriate to deal with here. { imgElement.RemoveAttribute("data-copyright"); imgElement.RemoveAttribute("data-creator"); imgElement.RemoveAttribute("data-license"); Logger.WriteEvent("Book.UpdateImgMetdataAttributesToMatchImage() Image " + path + " is missing"); //Debug.Fail(" (Debug only) Image " + path + " is missing"); return; } try { metadata = RobustIO.MetadataFromFile(path); } catch (UnauthorizedAccessException e) { throw new BloomUnauthorizedAccessException(path, e); } } progress.WriteStatus("Writing metadata to HTML for " + fileName); imgElement.SetAttribute("data-copyright", String.IsNullOrEmpty(metadata.CopyrightNotice) ? "" : metadata.CopyrightNotice); imgElement.SetAttribute("data-creator", String.IsNullOrEmpty(metadata.Creator) ? "" : metadata.Creator); imgElement.SetAttribute("data-license", metadata.License == null ? "" : metadata.License.ToString()); }
public static void InitializeNewProject(string modelVersion, string initialPatchFile) { // Import the initial patch file in a temporary directory var tempMongoSource = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); InitSourceDir(tempMongoSource); TestHelper.Run(Git, $"am --ignore-whitespace {initialPatchFile}", tempMongoSource); // Then copy the files to the real mongoSourceDir var mongoSourceDir = GetMongoSourceDir(modelVersion); InitSourceDir(mongoSourceDir); CopyFiles(tempMongoSource, mongoSourceDir); RobustIO.DeleteDirectoryAndContents(tempMongoSource); }
private static void RestoreLanguageDepot(Options.RestoreOptions options) { var dir = Path.Combine(Settings.TempDir, "LanguageDepot"); RobustIO.DeleteDirectoryAndContents(dir); using (var ld = new LanguageDepotHelper(true)) { for (var i = 0; i <= options.LanguageDepotVersion.Value; i++) { ld.ApplySinglePatch(options.ModelVersion, i); } } // Now in FLEx get project from colleague from USB Console.WriteLine("Successfully restored languagedepot test data for model " + $"{options.ModelVersion}"); }
protected override void Dispose(bool disposing) { base.Dispose(disposing); if (!disposing) { return; } if (string.IsNullOrEmpty(RepoDir)) { return; } RobustIO.DeleteDirectoryAndContents(RepoDir); RepoDir = null; }
public void MigrateOldConfigurationsIfNeeded_BringsPreHistoricFileToCurrentVersion() { var configSettingsDir = LcmFileHelper.GetConfigSettingsDir(Path.GetDirectoryName(Cache.ProjectId.Path)); var newConfigFilePath = Path.Combine(configSettingsDir, DictionaryConfigurationListener.DictionaryConfigurationDirectoryName, "Lexeme" + DictionaryConfigurationModel.FileExtension); Assert.False(File.Exists(newConfigFilePath), "should not yet be migrated"); Directory.CreateDirectory(configSettingsDir); File.WriteAllLines(Path.Combine(configSettingsDir, "Test.fwlayout"), new[] { @"<layoutType label='Lexeme-based (complex forms as main entries)' layout='publishStem'><configure class='LexEntry' label='Main Entry' layout='publishStemEntry' />", @"<configure class='LexEntry' label='Minor Entry' layout='publishStemMinorEntry' hideConfig='true' /></layoutType>'" }); var migrator = new DictionaryConfigurationMigrator(m_propertyTable, m_mediator); migrator.MigrateOldConfigurationsIfNeeded(); // SUT var updatedConfigModel = new DictionaryConfigurationModel(newConfigFilePath, Cache); Assert.AreEqual(DictionaryConfigurationMigrator.VersionCurrent, updatedConfigModel.Version); RobustIO.DeleteDirectoryAndContents(configSettingsDir); }
public void EnchantDictionaryMigrated() { // make temp folder with enchant dictionary in it var outputFolder = Path.Combine(Path.GetTempPath(), "EnchantDictionaryMigrationTestOut"); var inputFolder = Path.Combine(Path.GetTempPath(), "EnchantDictionaryMigrationTestIn"); RobustIO.DeleteDirectoryAndContents(outputFolder); RobustIO.DeleteDirectoryAndContents(inputFolder); Directory.CreateDirectory(outputFolder); Directory.CreateDirectory(inputFolder); // ReSharper disable LocalizableElement File.AppendAllText(Path.Combine(inputFolder, "test.dic"), "nil"); File.AppendAllText(Path.Combine(inputFolder, "test.exc"), "nil"); // ReSharper restore LocalizableElement // SUT Assert.DoesNotThrow(() => SpellingHelper.AddAnySpellingExceptionsFromBackup(inputFolder, outputFolder)); // Verify that the correct files were copied Assert.True(File.Exists(Path.Combine(outputFolder, "test.exc")), "The exception file should have been copied."); Assert.False(File.Exists(Path.Combine(outputFolder, "test.dic")), "The .dic file should not have been copied."); }
private void HandleOkButtonClick(object sender, EventArgs e) { if (m_model.RecordingProjectName != RecordingProjectName && File.Exists(Project.GetProjectFilePath(IsoCode, PublicationId, RecordingProjectName))) { var msg = string.Format(LocalizationManager.GetString("DialogBoxes.ProjectSettingsDlg.OverwriteProjectPrompt", "A {0} project with an ID of {1} and a Recording Project Name of {2} already exists for this language. Do you want to overwrite it?"), ProductName, m_txtPublicationId.Text, RecordingProjectName); if (MessageBox.Show(this, msg, ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) != DialogResult.Yes) { DialogResult = DialogResult.None; return; } var existingProjectPath = Project.GetProjectFolderPath(IsoCode, PublicationId, RecordingProjectName); if (!RobustIO.DeleteDirectoryAndContents(existingProjectPath)) { var failedMsg = string.Format(LocalizationManager.GetString("DialogBoxes.ProjectSettingsDlg.OverwriteProjectFailed", "{0} was unable to delete all of the files in {1}. You can try to clean this up manually and then re-attempt saving these changes."), ProductName, existingProjectPath); MessageBox.Show(this, failedMsg, ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } } m_model.RecordingProjectName = RecordingProjectName; m_model.AudioStockNumber = AudioStockNumber; m_model.ChapterAnnouncementStyle = ChapterAnnouncementStyle; m_model.SkipChapterAnnouncementForFirstChapter = !m_chkChapterOneAnnouncements.Checked; m_model.Project.ReferenceTextProxy = ((KeyValuePair <string, ReferenceTextProxy>)m_ReferenceText.SelectedItem).Value; m_model.Project.DramatizationPreferences.BookIntroductionsDramatization = (ExtraBiblicalMaterialSpeakerOption)m_bookIntro.SelectedValue; m_model.Project.DramatizationPreferences.SectionHeadDramatization = (ExtraBiblicalMaterialSpeakerOption)m_sectionHeadings.SelectedValue; m_model.Project.DramatizationPreferences.BookTitleAndChapterDramatization = (ExtraBiblicalMaterialSpeakerOption)m_titleChapters.SelectedValue; m_model.Project.ProjectSettingsStatus = ProjectSettingsStatus.Reviewed; DialogResult = DialogResult.OK; Close(); }
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection) { lock (LockObject) { // We need to forget any cached version of the XML. Otherwise, when more than one lot of settings // is saved in the same file, the provider that is doing the save for one of them may have stale // (or missing) settings for the other. We want to write the dirty properties over a current // version of everything else that has been saved in the file. _settingsXml = null; //Iterate through the settings to be stored, only dirty settings for this provider are in collection foreach (SettingsPropertyValue propval in collection) { var groupName = context["GroupName"].ToString(); var groupNode = SettingsXml.SelectSingleNode("/configuration/userSettings/" + context["GroupName"]); if (groupNode == null) { var parentNode = SettingsXml.SelectSingleNode("/configuration/userSettings"); groupNode = SettingsXml.CreateElement(groupName); parentNode.AppendChild(groupNode); } var section = (XmlElement)SettingsXml.SelectSingleNode("/configuration/configSections/sectionGroup/section"); if (section == null) { var parentNode = SettingsXml.SelectSingleNode("/configuration/configSections/sectionGroup"); section = SettingsXml.CreateElement("section"); section.SetAttribute("name", groupName); section.SetAttribute("type", String.Format("{0}, {1}", typeof(ClientSettingsSection), Assembly.GetAssembly(typeof(ClientSettingsSection)))); parentNode.AppendChild(section); } SetValue(groupNode, propval); } Directory.CreateDirectory(UserConfigLocation); RobustIO.SaveXml(SettingsXml, Path.Combine(UserConfigLocation, UserConfigFileName)); } }
public void MigrateOldConfigurationsIfNeeded_PreservesOrderOfBibliographies() { var configSettingsDir = LcmFileHelper.GetConfigSettingsDir(Path.GetDirectoryName(Cache.ProjectId.Path)); var newConfigFilePath = Path.Combine(configSettingsDir, DictionaryConfigurationListener.ReversalIndexConfigurationDirectoryName, "AllReversalIndexes" + DictionaryConfigurationModel.FileExtension); Assert.False(File.Exists(newConfigFilePath), "should not yet be migrated"); Directory.CreateDirectory(configSettingsDir); File.WriteAllLines(Path.Combine(configSettingsDir, "Test.fwlayout"), new[] { @"<layoutType label='All Reversal Indexes' layout='publishReversal'>", @"<configure class='ReversalIndexEntry' label='Reversal Entry' layout='publishReversalEntry' /></layoutType>'" }); var migrator = new DictionaryConfigurationMigrator(m_propertyTable, m_mediator); migrator.MigrateOldConfigurationsIfNeeded(); // SUT var updatedConfigModel = new DictionaryConfigurationModel(newConfigFilePath, Cache); var refdSenseChildren = updatedConfigModel.Parts[0].Children.Find(n => n.Label == "Referenced Senses").Children; var bibCount = 0; for (var i = 0; i < refdSenseChildren.Count; i++) { var bibNode = refdSenseChildren[i]; if (!bibNode.Label.StartsWith("Bibliography")) { continue; } StringAssert.StartsWith("Bibliography (", bibNode.Label, "Should specify (entry|sense), lest we never know"); Assert.False(bibNode.IsCustomField, bibNode.Label + " should not be custom."); // Rough test to ensure Bibliography nodes aren't bumped to the end of the list. In the defaults, the later Bibliography // node is a little more than five nodes from the end Assert.LessOrEqual(i, refdSenseChildren.Count - 5, "Bibliography nodes should not have been bumped to the end of the list"); ++bibCount; } Assert.AreEqual(2, bibCount, "Should be exactly two Bibliography nodes (sense and entry)"); RobustIO.DeleteDirectoryAndContents(configSettingsDir); }
public void CompressExportedFiles_IncludesAcceptableMediaTypes() { var view = new MockWebonaryDlg(); var tempDirectoryToCompress = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(tempDirectoryToCompress); try { var zipFileToUpload = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); // TIFF var tiffFilename = Path.GetFileName(Path.GetTempFileName() + ".tif"); var tiffPath = Path.Combine(tempDirectoryToCompress, tiffFilename); var tiffMagicNumber = new byte[] { 0x49, 0x49, 0x2A }; File.WriteAllBytes(tiffPath, tiffMagicNumber); // JPEG var jpegFilename = Path.GetFileName(Path.GetTempFileName() + ".jpg"); var jpegPath = Path.Combine(tempDirectoryToCompress, jpegFilename); var jpegMagicNumber = new byte[] { 0xff, 0xd8 }; File.WriteAllBytes(jpegPath, jpegMagicNumber); // MP4 var mp4Filename = Path.GetFileName(Path.GetTempFileName() + ".mp4"); var mp4Path = Path.Combine(tempDirectoryToCompress, mp4Filename); var mp4MagicNumber = new byte[] { 0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32 }; File.WriteAllBytes(mp4Path, mp4MagicNumber); var xhtmlFilename = Path.GetFileName(Path.GetTempFileName() + ".xhtml"); var xhtmlPath = Path.Combine(tempDirectoryToCompress, xhtmlFilename); var xhtmlContent = "<xhtml/>"; File.WriteAllText(xhtmlPath, xhtmlContent); // SUT UploadToWebonaryController.CompressExportedFiles(tempDirectoryToCompress, zipFileToUpload, view); // Verification const string unsupported = ".*nsupported.*"; const string unsupportedRegex = ".*{0}" + unsupported; using (var uploadZip = new ZipFile(zipFileToUpload)) { Assert.False(uploadZip.EntryFileNames.Contains(tiffFilename), "Should not have included unsupported TIFF file in file to upload."); Assert.True(uploadZip.EntryFileNames.Contains(jpegFilename), "Should have included supported JPEG file in file to upload."); Assert.True(uploadZip.EntryFileNames.Contains(mp4Filename), "Should have included supported MP4 file in file to upload."); } var query = string.Format(unsupportedRegex, tiffFilename); Assert.True(view.StatusStrings.Exists(statusString => Regex.Matches(statusString, query).Count == 1), "Lack of support for the tiff file should have been reported to the user."); query = string.Format(unsupportedRegex, jpegFilename); Assert.False(view.StatusStrings.Exists(statusString => Regex.Matches(statusString, query).Count == 1), "Should not have reported lack of support for the jpeg file."); query = string.Format(unsupportedRegex, mp4Filename); Assert.False(view.StatusStrings.Exists(statusString => Regex.Matches(statusString, query).Count == 1), "Should not have reported lack of support for the mp4 file."); Assert.That(view.StatusStrings.Count(statusString => Regex.Matches(statusString, unsupported).Count > 0), Is.EqualTo(1), "Too many unsupported files reported."); } finally { RobustIO.DeleteDirectoryAndContents(tempDirectoryToCompress); } }
///<summary> /// Perform the migration ///</summary> ///<exception cref="InvalidOperationException"></exception> public virtual void Migrate() { var problems = new List <FolderMigratorProblem>(); // Clean up root backup path if (Directory.Exists(MigrationPath)) { if (!RobustIO.DeleteDirectoryAndContents(MigrationPath)) { //for user, ah well //for programmer, tell us Debug.Fail("(Debug mode only) Couldn't delete the migration folder"); } } //check if there is anything to migrate. If not, don't do anything if (!Directory.Exists(SourcePath)) { return; } if (GetLowestVersionInFolder(SourcePath) == ToVersion) { return; } // Backup current folder to backup path under backup root CopyDirectory(SourcePath, BackupPath, MigrationPath); string currentPath = BackupPath; int lowestVersionInFolder; int lowestVersoinInFolder1 = -1; while ((lowestVersionInFolder = GetLowestVersionInFolder(currentPath)) != ToVersion) { //This guards against an empty Folder if (lowestVersionInFolder == int.MaxValue) { break; } if (lowestVersionInFolder == lowestVersoinInFolder1) { var fileNotMigrated = _versionCache.First(info => info.Version == lowestVersionInFolder).FileName; throw new ApplicationException( String.Format("The migration strategy for {0} failed to migrate the file '{1} from version {2}", SearchPattern, fileNotMigrated, lowestVersoinInFolder1) ); } int currentVersion = lowestVersionInFolder; // Get all files in folder with this version var fileNamesToMigrate = GetFilesOfVersion(currentVersion, currentPath); // Find a strategy to migrate this version IMigrationStrategy strategy = MigrationStrategies.Find(s => s.FromVersion == currentVersion); if (strategy == null) { throw new InvalidOperationException( String.Format("No migration strategy could be found for version {0}", currentVersion) ); } string destinationPath = Path.Combine( MigrationPath, String.Format("{0}_{1}", strategy.FromVersion, strategy.ToVersion) ); Directory.CreateDirectory(destinationPath); // Migrate all the files of the current version foreach (var filePath in Directory.GetFiles(currentPath, SearchPattern)) { string fileName = Path.GetFileName(filePath); if (fileName == null) { continue; } string sourceFilePath = Path.Combine(currentPath, fileName); string targetFilePath = Path.Combine(destinationPath, fileName); if (fileNamesToMigrate.Contains(sourceFilePath)) { strategy.Migrate(sourceFilePath, targetFilePath); } else { File.Copy(sourceFilePath, targetFilePath); } } try { strategy.PostMigrate(currentPath, destinationPath); } catch (Exception e) { problems.Add(new FolderMigratorProblem { Exception = e, FilePath = currentPath }); } // Setup for the next iteration currentPath = destinationPath; lowestVersoinInFolder1 = lowestVersionInFolder; } // Delete all the files in SourcePath matching SearchPattern foreach (var filePath in Directory.GetFiles(SourcePath, SearchPattern)) { File.Delete(filePath); } // Copy the migration results into SourcePath CopyDirectory(currentPath, SourcePath, ""); if (!RobustIO.DeleteDirectoryAndContents(MigrationPath)) { //for user, ah well //for programmer, tell us Debug.Fail("(Debug mode only) Couldn't delete the migration folder"); } // Call the problem handler if we encountered problems during migration. if (problems.Count > 0) { _problemHandler(problems); } }
public static void Cleanup() { RobustIO.DeleteDirectoryAndContents(BaseDir); }
private static void Wizard(Options.WizardOptions wizardOptions) { var workdir = wizardOptions.WorkDir; MongoHelper.AddTestUser(Settings.DataDir); for (var modelVersion = wizardOptions.MinModel; modelVersion <= wizardOptions.MaxModel; modelVersion++) { if (modelVersion == 7000071) { continue; } Console.WriteLine( "--------------------------------------------------------------"); Console.WriteLine($"Processing model version {modelVersion}"); if (string.IsNullOrEmpty(workdir)) { wizardOptions.WorkDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Settings.TempDir = wizardOptions.WorkDir; Directory.CreateDirectory(wizardOptions.WorkDir); } wizardOptions.ModelVersion = modelVersion; var patchDir = Path.Combine(Settings.DataDir, modelVersion.ToString()); if (!Directory.Exists(patchDir)) { Directory.CreateDirectory(patchDir); } var mongoDir = Path.Combine(patchDir, "mongo"); if (!Directory.Exists(mongoDir)) { Directory.CreateDirectory(mongoDir); } var outputDir = Path.Combine(wizardOptions.FwRoot, $"Output{modelVersion}"); if (!Directory.Exists(outputDir)) { Console.WriteLine($"Can't find FW output directory {outputDir}"); continue; } var sourceEnviron = modelVersion < 7000072 ? "fwenviron" : "fwenviron9"; var targetEnviron = Path.Combine(outputDir, "fwenviron"); if (!File.Exists(targetEnviron)) { File.Copy( Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), sourceEnviron), targetEnviron); } // LanguageDepot if (wizardOptions.NewProject) { wizardOptions.LanguageDepotVersion = 0; wizardOptions.MongoVersion = 0; // Delete FW project RobustIO.DeleteDirectoryAndContents( Path.Combine(wizardOptions.GetFwProjectDirectory(modelVersion), $"test-{modelVersion}")); RobustIO.DeleteDirectoryAndContents( Path.Combine(wizardOptions.UsbDirectory, $"test-{modelVersion}")); Console.WriteLine( $"Create a new project 'test-{modelVersion}' and send this project for the first time to the USB stick"); Run("/bin/bash", $"-i -c \"cd {outputDir} && . fwenviron && cd Output_$(uname -m)/Debug && mono --debug FieldWorks.exe > /dev/null\"", outputDir, true); } else { Console.WriteLine($"Restoring version {wizardOptions.LanguageDepotVersion} " + $"of chorus repo for model {modelVersion}"); RestoreLanguageDepot(new Options.RestoreOptions(wizardOptions)); DirectoryHelper.Copy(Path.Combine(wizardOptions.WorkDir, "LanguageDepot", ".hg"), Path.Combine(wizardOptions.UsbDirectory, $"test-{modelVersion}", ".hg"), true); // Delete FW project RobustIO.DeleteDirectoryAndContents( Path.Combine(wizardOptions.GetFwProjectDirectory(modelVersion), $"test-{modelVersion}")); Console.WriteLine( $"Now get project 'test-{modelVersion}' from USB stick and make changes and afterwards do a s/r with the USB stick."); // for whatever reason we have to pass -i, otherwise 7000069 won't be able to bring // up the s/r dialog! Run("/bin/bash", $"-i -c \"cd {outputDir} && . fwenviron && cd Output_$(uname -m)/Debug && mono --debug FieldWorks.exe > /dev/null\"", outputDir, true); } Console.WriteLine($"Saving chorus repo test data for {modelVersion}"); DirectoryHelper.Copy( Path.Combine(wizardOptions.UsbDirectory, $"test-{modelVersion}", ".hg"), Path.Combine(wizardOptions.WorkDir, "LanguageDepot", ".hg"), true); SaveLanguageDepot(new Options.SaveOptions(wizardOptions) { WorkDir = null, CommitMsg = wizardOptions.CommitMsg ?? "New test data" }); SaveLanguageDepotNoOpPatchIfNecessary(modelVersion, wizardOptions.LanguageDepotVersion + 1); // Mongo if (wizardOptions.NewProject) { // Since this is a new project, we copy the first patch for mongo from an // older model version, if we can find it int tmpModelVersion; var mongoPatchFile = string.Empty; for (tmpModelVersion = Settings.MinModelVersion; tmpModelVersion <= Settings.MaxModelVersion; tmpModelVersion++) { var dirInfo = new DirectoryInfo(Path.Combine(Settings.DataDir, tmpModelVersion.ToString(), "mongo")); var patchFiles = dirInfo.GetFiles("0001-*.patch"); if (patchFiles.Length != 1) { continue; } mongoPatchFile = patchFiles[0].FullName; break; } if (tmpModelVersion > Settings.MaxModelVersion) { Console.WriteLine( "ERROR: Can't find first mongo patch. Please create mongo project some other way."); return; } MongoHelper.InitializeNewProject(modelVersion.ToString(), mongoPatchFile); Console.WriteLine($"Saving LanguageForge test data for {modelVersion}"); SaveMongoDb(new Options.SaveOptions(wizardOptions) { CommitMsg = "Add empty project" }); } else { Console.WriteLine( $"Restoring LanguageForge mongo data version {wizardOptions.MongoVersion} for model {modelVersion}"); RestoreMongoDb(new Options.RestoreOptions(wizardOptions)); Console.WriteLine( $"Now make the changes to '{wizardOptions.Project}' in your local" + " LanguageForge, then press return. Don't do a send/receive!"); Console.WriteLine( "(You might have to empty the cached IndexedDB data in your " + "browser's developer tools)"); Console.WriteLine("Login as user 'test' with password 'passwordfortest'"); Run("/bin/bash", "-c \"xdg-open http://languageforge.local/app/projects\"", outputDir); Console.ReadLine(); Console.WriteLine($"Saving LanguageForge test data for {modelVersion}"); SaveMongoDb(new Options.SaveOptions(wizardOptions) { CommitMsg = wizardOptions.CommitMsg ?? "New test data" }); // Merge the data we just created Console.WriteLine($"Merge test data for {modelVersion}"); Merge(new Options.MergeOptions(wizardOptions) { LanguageDepotVersion = wizardOptions.LanguageDepotVersion + 1, MongoVersion = wizardOptions.MongoVersion + 1 }); } if (string.IsNullOrEmpty(workdir)) { // we created a temporary workdir, so delete it again RobustIO.DeleteDirectoryAndContents(wizardOptions.WorkDir); } } }
public static void Cleanup() { RobustIO.DeleteDirectoryAndContents(Path.Combine(Settings.TempDir, "patches")); }
public void OneTimeSetup() { _repoFolder = new TemporaryFolder("SyncAtStartup_Repo"); _collectionFolder = new TemporaryFolder("SyncAtStartup_Local"); FolderTeamCollection.CreateTeamCollectionLinkFile(_collectionFolder.FolderPath, _repoFolder.FolderPath); _mockTcManager = new Mock <ITeamCollectionManager>(); _tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(_collectionFolder.FolderPath)); _collection = new FolderTeamCollection(_mockTcManager.Object, _collectionFolder.FolderPath, _repoFolder.FolderPath, tcLog: _tcLog); _collection.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); // Simulate a book that was once shared, but has been deleted from the repo folder (has a tombstone). MakeBook("Should be deleted", "This should be deleted as it has local status but is not shared", true); var bookFolderPath = Path.Combine(_collectionFolder.FolderPath, "Should be deleted"); _collection.DeleteBookFromRepo(bookFolderPath); // Simulate a book that was once shared, but has been deleted from the repo folder. But there is no tombstone. // (Despite the name, it is only converted to a new local in the default case. When we do a First Time Join, // it just gets copied into the repo.) MakeBook("Should be converted to new local", "This should become a new local (no status) book as it has local status but is not in the repo", true); var delPath = Path.Combine(_repoFolder.FolderPath, "Books", "Should be converted to new local.bloom"); RobustFile.Delete(delPath); // Simulate a book newly created locally. Not in repo, but should not be deleted. MakeBook("A book", "This should survive as it has no local status", false); // By the way, like most new books, it got renamed early in life...twice SimulateRename(_collection, "A book", "An early name"); SimulateRename(_collection, "An early name", "New book"); // Simulate a book that needs nothing done to it. It's the same locally and on the repo. MakeBook("Keep me", "This needs nothing done to it"); // Simulate a book that is checked out locally to the current user, but the file has // been deleted on the repo. MakeBook("Keep me too", "This also needs nothing done", false); _collection.WriteLocalStatus("Keep me too", new BookStatus().WithLockedBy("*****@*****.**")); // Simlulate a book that is only in the team repo MakeBook("Add me", "Fetch to local"); var delPathAddMe = Path.Combine(_collectionFolder.FolderPath, "Add me"); SIL.IO.RobustIO.DeleteDirectoryAndContents(delPathAddMe); // Simulate a book that was checked in, then checked out again and renamed, // but not yet checked in. Both "A renamed book" folder and content and "An old name.bloom" // should survive. (Except for an obscure reason when joining a TC...see comment in the test.) MakeBook("An old name", "Should be kept in both places with different names"); _collection.AttemptLock("An old name", "*****@*****.**"); SimulateRename(_collection, "An old name", "an intermediate name"); SimulateRename(_collection, "an intermediate name", "A renamed book"); // Simulate a book that is not checked out locally and has been modified elsewhere MakeBook("Update me", "Needs to be become this locally"); UpdateLocalBook("Update me", "This is supposed to be an older value, not edited locally"); // Simulate a book that is checked out locally but not in the repo, and where the saved local // checksum equals the repo checksum, and it is not checked out in the repo. This would // typically indicate that someone remote forced a checkout, perhaps while this user was // offline, but checked in again without making changes. // Also pretend it has been modified locally. // Test result: collection is updated to indicate the local checkout. Local changes are not lost. MakeBook("Check me out", "Local and remote checksums correspond to this"); UpdateLocalBook("Check me out", "This is supposed to be a newer value from local editing", false); var oldLocalStatus = _collection.GetLocalStatus("Check me out"); var newLocalStatus = oldLocalStatus.WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser); _checkMeOutOriginalChecksum = oldLocalStatus.checksum; _collection.WriteLocalStatus("Check me out", newLocalStatus); // Simulate a book that appears newly-created locally (no local status) but is also in the // repo. This would indicate two people coincidentally creating a book with the same name. // Test result: the local book should get renamed (both folder and htm). // When merging while joining a new TC, this case is treated as a conflict and the // local book is moved to Lost and Found. MakeBook("Rename local", "This content is on the server"); _collection.AttemptLock("Rename local", "*****@*****.**"); UpdateLocalBook("Rename local", "This is a new book created independently"); var statusFilePath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath("Rename local", _collectionFolder.FolderPath); RobustFile.Delete(statusFilePath); // Simulate a book that is checked out locally but also checked out, to a different user // or machine, on the repo. This would indicate some sort of manual intervention, perhaps // while this user was long offline. The book has not been modified locally, but the local // status is out of date. // Test result: local status is updated to reflect the remote checkout, book content updated to repo. MakeBook("Update and undo checkout", "This content is everywhere"); _collection.AttemptLock("Update and undo checkout", "*****@*****.**"); _collection.WriteLocalStatus("Update and undo checkout", _collection.GetStatus("Update and undo checkout").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser)); // Simulate a book that is checked out locally and not on the server, but the repo and (old) // local checksums are different. The book has not been edited locally. // Test result: book is updated to match repo. Local and remote status should match...review: which wins? MakeBook("Update and checkout", "This content is on the server"); UpdateLocalBook("Update and checkout", "This simulates older content changed remotely but not locally"); _collection.WriteLocalStatus("Update and checkout", _collection.GetLocalStatus("Update and checkout").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser)); // Simulate a book that is checked out and modified locally, but has also been modified // remotely. // Test result: current local state is saved in lost-and-found. Repo version of book and state // copied to local. Warning to user. MakeBook("Update content and status and warn", "This simulates new content on server"); _collection.AttemptLock("Update content and status and warn", "*****@*****.**"); UpdateLocalBook("Update content and status and warn", "This is supposed to be the newest value from local editing"); var newStatus = _collection.GetStatus("Update content and status and warn").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser) .WithChecksum("different from either"); _collection.WriteLocalStatus("Update content and status and warn", newStatus); // Simulate a book that is checked out and modified locally, but is also checked out by another // user or machine in the repo. It has not (yet) been modified remotely. // Test result: current local state is saved in lost-and-found. Repo version of book and state // copied to local. Warning to user. MakeBook("Update content and status and warn2", "This simulates new content on server"); _collection.AttemptLock("Update content and status and warn2", "*****@*****.**"); UpdateLocalBook("Update content and status and warn2", "This is supposed to be the newest value from local editing", false); newStatus = _collection.GetStatus("Update content and status and warn2").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser); _collection.WriteLocalStatus("Update content and status and warn2", newStatus); // Simulate a book which has no local status, but for which the computed checksum matches // the repo one. This could happen if a user obtained the same book independently, // or during initial merging of a local and team collection, where much of the material // was previously duplicated. // Test result: status is copied to local MakeBook("copy status", "Same content in both places"); _collection.AttemptLock("copy status", "*****@*****.**"); statusFilePath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath("copy status", _collectionFolder.FolderPath); RobustFile.Delete(statusFilePath); // Simulate a book that was copied from another TC, using File Explorer. // It therefore has a book.status file, but with a different guid. // Test result: it should survive, and on a new collection sync get copied into the repo var copiedEx = "copied with Explorer"; MakeBook(copiedEx, "This content is only local", false); _collection.WriteLocalStatus(copiedEx, new BookStatus(), collectionId: Bloom.TeamCollection.TeamCollection.GenerateCollectionId()); // Simulate a book that appeared in DropBox when their software found a conflict. // It should NOT be copied locally, but instead moved to Lost and Found, with a report. MakeBook(kConflictName, "This content is only on the repo, apart from conflicting copies"); var conflictFolderPath = Path.Combine(_collectionFolder.FolderPath, kConflictName); SIL.IO.RobustIO.DeleteDirectoryAndContents(conflictFolderPath); _collection.WriteLocalStatus(copiedEx, new BookStatus(), collectionId: Bloom.TeamCollection.TeamCollection.GenerateCollectionId()); // Simulate a corrupt zip file, only in the repo File.WriteAllText(Path.Combine(_repoFolder.FolderPath, "Books", "new corrupt book.bloom"), "This is not a valid zip!"); // Simulate a corrupt zip file that corresponds to a local book. var badZip = "has a bad zip in repo"; MakeBook(badZip, "This book seems to be in both places, but the repo is corrupt"); File.WriteAllText(Path.Combine(_repoFolder.FolderPath, "Books", badZip + ".bloom"), "This is also not a valid zip!"); // Simulate a book that was renamed remotely. That is, there's a local book Old Name, with local status, // and there's no repo book by that name, but there's a repo book New Name (and no such local book). // The book's meta.json indicates they are the same book. // We'll initially make both, with the new name and new content. MakeBook(kNewNameForRemoteRename, "This is the new book content after remote editing and rename"); var oldFolder = Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely); var newFolder = Path.Combine(_collectionFolder.FolderPath, kNewNameForRemoteRename); RobustIO.MoveDirectory(newFolder, oldFolder); // made at new path, simulate still at old. var oldPath = Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely, kBookRenamedRemotely + ".htm"); // simulate old book name and content RobustFile.WriteAllText(oldPath, "This is the simulated original book content"); RobustFile.Delete(Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely, kNewNameForRemoteRename + ".htm")); // get rid of the 'new' content // Simulate a book that is in the repo, where there is a local book that has no status, a different name, // and the same ID. This might indicate (a) that it was renamed by someone else after this user's pre-TC // copy of the collection diverged; (b) that it was renamed by this user after the divergence; // (c) that they were independently copied from some common-ID source. // We will treat this as a conflict, moving the local version to lost and found, even on a first time join. MakeBook(kRepoNameForIdConflict, "This is the repo version of a book that has a no-status local book with the same ID."); // Move the local version to a new folder var oldFolder2 = Path.Combine(_collectionFolder.FolderPath, kRepoNameForIdConflict); var newFolder2 = Path.Combine(_collectionFolder.FolderPath, kLocalNameForIdConflict); RobustIO.MoveDirectory(oldFolder2, newFolder2); var localStatusPath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath(kLocalNameForIdConflict, _collectionFolder.FolderPath); RobustFile.Delete(localStatusPath); // Make a couple of folders that are legitimately present, but not books. var allowedWords = Path.Combine(_collectionFolder.FolderPath, "Allowed Words"); Directory.CreateDirectory(allowedWords); File.WriteAllText(Path.Combine(allowedWords, "some sample.txt"), "This a fake word list"); var sampleTexts = Path.Combine(_collectionFolder.FolderPath, "Sample Texts"); Directory.CreateDirectory(sampleTexts); File.WriteAllText(Path.Combine(sampleTexts, "a sample.txt"), "This a fake sample text"); _progressSpy = new ProgressSpy(); // sut for the whole suite! Assert.That(_collection.SyncAtStartup(_progressSpy, FirstTimeJoin()), Is.True); }
private static bool UpgradeToCurrentDataFormatVersion(ApplicationMetadata info) { if (info.DataVersion >= Settings.Default.DataFormatVersion) { return(false); } bool retVal = true; Analytics.Track("DataVersionUpgrade", new Dictionary <string, string> { { "old", info.DataVersion.ToString(CultureInfo.InvariantCulture) }, { "new", Settings.Default.DataFormatVersion.ToString(CultureInfo.InvariantCulture) } }); switch (info.DataVersion) { case 0: foreach (var publicationFolder in Project.AllPublicationFolders) { var filesToMove = Directory.GetFiles(publicationFolder); if (!filesToMove.Any()) { continue; } var projectFilePath = Directory.GetFiles(publicationFolder, "*" + kOldProjectExtension).FirstOrDefault(); if (projectFilePath != null) { Exception exception; var metadata = GlyssenDblTextMetadata.Load <GlyssenDblTextMetadata>(projectFilePath, out exception); string recordingProjectName; if (exception != null) { // Just add a directory layer and don't worry about it for now. recordingProjectName = Path.GetFileName(publicationFolder); } else { if (metadata.Identification != null && !string.IsNullOrEmpty(metadata.Identification.Name)) { recordingProjectName = metadata.Identification.Name; } else { recordingProjectName = metadata.Id; } } recordingProjectName = Project.GetDefaultRecordingProjectName(recordingProjectName); var recordingProjectFolder = Path.Combine(publicationFolder, recordingProjectName); Directory.CreateDirectory(recordingProjectFolder); foreach (var file in filesToMove) { File.Move(file, Path.Combine(recordingProjectFolder, Path.GetFileName(file))); } if (Settings.Default.CurrentProject == projectFilePath) { Settings.Default.CurrentProject = Path.Combine(recordingProjectFolder, Path.GetFileName(projectFilePath)); } } } goto case 1; case 1: foreach (var recordingProjectFolder in Project.AllRecordingProjectFolders.ToList()) { var versificationPath = Path.Combine(recordingProjectFolder, DblBundleFileUtils.kVersificationFileName); if (!File.Exists(versificationPath)) { var projectFilePath = Directory.GetFiles(recordingProjectFolder, "*" + kOldProjectExtension).FirstOrDefault(); if (projectFilePath != null) { if (projectFilePath.Equals(SampleProject.SampleProjectFilePath, StringComparison.OrdinalIgnoreCase)) { File.WriteAllText(versificationPath, Resources.EnglishVersification); } else { Exception exception; var metadata = GlyssenDblTextMetadata.Load <GlyssenDblTextMetadata>(projectFilePath, out exception); var origBundlePath = metadata.OriginalReleaseBundlePath; if (string.IsNullOrEmpty(origBundlePath)) { // Note: We didn't support Paratext-based projects until settings version 3 (Glyssen 1.1), // so for this step in the migration process (going from 0 to 1), any project without // OriginalReleaseBundlePath set is invalid (possibly from a really early version of Glyssen // or some g;itch arising from development activity or external mangling of the file). So // we should be able to safely blow this away. try { Project.DeleteProjectFolderAndEmptyContainingFolders(recordingProjectFolder, true); } catch (Exception) { // Oh, well, we tried. Not the end of the world. } } else { var bundle = new GlyssenBundle(origBundlePath); var errorlogPath = Path.Combine(recordingProjectFolder, "errorlog.txt"); bundle.CopyVersificationFile(versificationPath); try { ProjectBase.LoadVersification(versificationPath); } catch (InvalidVersificationLineException ex) { var msg = string.Format(LocalizationManager.GetString("DataMigration.InvalidVersificationFile", "Invalid versification file encountered during data migration. Errors must be fixed or subsequent " + "attempts to open this project will fail.\r\n" + "Project: {0}\r\n" + "Text release Bundle: {1}\r\n" + "Versification file: {2}\r\n" + "Error: {3}"), projectFilePath, origBundlePath, versificationPath, ex.Message); MessageBox.Show(msg, GlyssenInfo.kProduct, MessageBoxButtons.OK, MessageBoxIcon.Warning); File.WriteAllText(errorlogPath, msg); } } } } } } goto case 2; case 2: foreach (var pgProjFile in Project.AllRecordingProjectFolders.SelectMany(d => Directory.GetFiles(d, "*" + kOldProjectExtension))) { var newName = Path.ChangeExtension(pgProjFile, Constants.kProjectFileExtension); File.Move(pgProjFile, newName); if (Settings.Default.CurrentProject == pgProjFile) { Settings.Default.CurrentProject = newName; } } break; // No need to go to case 3, since the problem it fixes would only have been caused by a version of Glyssen with data version 3 case 3: try { RobustIO.DeleteDirectory(Path.GetDirectoryName(SampleProject.SampleProjectFilePath) + " Audio", true); } catch (IOException e) { Logger.WriteError("Unable to clean up superfluous sample Audio Audio folder.", e); } var safeReplacements = new List <Tuple <string, string> >(); var unsafeReplacements = new List <Tuple <string, string> >(); foreach (var folder in Project.AllRecordingProjectFolders.Where(d => d.EndsWith(" Audio Audio"))) { // Because of the way this bug (PG-1192) worked, the most likely thing is that the "correct" // version of the project will have been initially created but then all the actual work will // have gotten saved into the "incorrect" version. If this looks to be the case, we can // probably safely delete the correct one and then rename the incorrect one to have the correct // name. var baseFolder = Path.GetDirectoryName(folder); Debug.Assert(baseFolder != null); var languageFolder = Path.GetDirectoryName(baseFolder); Debug.Assert(languageFolder != null); var langCode = Path.GetFileName(languageFolder); Debug.Assert(langCode != null); var incorrectProjName = Path.GetFileName(folder); Debug.Assert(incorrectProjName != null); var correctProjectName = incorrectProjName.Substring(0, incorrectProjName.Length - " Audio".Length); var correctProjectFolder = Path.Combine(baseFolder, correctProjectName); if (Directory.Exists(correctProjectFolder)) { var glyssenProjFilename = langCode + Constants.kProjectFileExtension; var incorrectProjectFilePath = Path.Combine(folder, glyssenProjFilename); var correctProjectFilePath = Path.Combine(correctProjectFolder, glyssenProjFilename); var finfoIncorrectProject = new FileInfo(incorrectProjectFilePath); var finfoCorrectProject = new FileInfo(correctProjectFilePath); if (finfoCorrectProject.Exists && finfoIncorrectProject.Exists) { if (finfoCorrectProject.LastWriteTimeUtc < finfoIncorrectProject.LastWriteTimeUtc) { var books = Directory.GetFiles(correctProjectFolder, "???.xml").Where(b => Canon.IsBookIdValid(Path.GetFileNameWithoutExtension(b))) .Select(XmlSerializationHelper.DeserializeFromFile <BookScript>); foreach (var book in books) { // If book == null, there was a problem loading it. It may be locked or be in some incompatible format. // In any case, we shouldn't risk assuming we can safely replace it. if (book == null || book.GetScriptBlocks().Any(b => b.UserConfirmed || b.MatchesReferenceText)) { unsafeReplacements.Add(new Tuple <string, string>(folder, correctProjectFolder)); break; } } if (unsafeReplacements.LastOrDefault()?.Item1 == folder) { continue; } try { var projToBackUp = Project.Load(correctProjectFilePath); projToBackUp.CreateBackup("Overwritten by migration 3-4"); } catch (Exception e) { Logger.WriteError("Unable to load project and create backup", e); safeReplacements.Add(new Tuple <string, string>(folder, correctProjectFolder)); continue; } try { RobustIO.DeleteDirectory(correctProjectFolder, true); RobustIO.MoveDirectory(folder, correctProjectFolder); } catch (IOException e) { Logger.WriteError("Unable to replace project after making backup", e); Console.WriteLine(e); unsafeReplacements.Add(new Tuple <string, string>(folder, correctProjectFolder)); } } else { unsafeReplacements.Add(new Tuple <string, string>(folder, correctProjectFolder)); } } } } if (safeReplacements.Any()) { string fmt; if (safeReplacements.Count == 1) { fmt = LocalizationManager.GetString("DataMigration.ConfirmReplacementOfAudioAudio", "Doing this will replace the existing project by the same name, which was originally created by {0}. " + "Since none of the blocks in the project to be overwritten have any user decisions recorded, this seems " + "to be safe, but since {0} failed to make a backup, you need to confirm this. If you choose not to confirm " + "this action, you can either clean up the problem project yourself or verify that is is safe and then restart " + "{0}. You will be asked about this each time you start the program as long as this problem remains unresolved.\r\n" + "Confirm overwriting?", "Param: \"Glyssen\" (product name); " + "This follows the \"AudioAudioProblemPreambleSingle\"."); } else { fmt = LocalizationManager.GetString("DataMigration.ConfirmReplacementsOfAudioAudio", "Doing this will replace the existing projects by the same name, which were originally created by {0}. " + "Since none of the blocks in the projects to be overwritten have any user decisions recorded, this seems " + "to be safe, but since {0} failed to make a backup, you need to confirm this. If you choose not to confirm " + "this action, you can either clean up the problem projects yourself or verify that is is safe and then restart " + "{0}. You will be asked about this each time you start the program as long as these problems remains unresolved.\r\n" + "Confirm overwriting?", "Param: \"Glyssen\" (product name); " + "This follows the \"AudioAudioProblemPreambleMultiple\"."); } var msg = GetAudioAudioProblemPreamble(safeReplacements.Count) + String.Join(Environment.NewLine, safeReplacements.Select(r => r.Item1)) + Environment.NewLine + Environment.NewLine + String.Format(fmt, GlyssenInfo.kProduct); if (DialogResult.Yes == MessageBox.Show(msg, GlyssenInfo.kProduct, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2)) { foreach (var replacement in safeReplacements) { RobustIO.DeleteDirectory(replacement.Item2, true); RobustIO.MoveDirectory(replacement.Item1, replacement.Item2); } safeReplacements.Clear(); } } if (unsafeReplacements.Any()) { string fmt; if (unsafeReplacements.Count == 1) { fmt = LocalizationManager.GetString("DataMigration.NoticeToManuallyFixAudioAudioProject", "However, doing this would replace the existing project by the same name. " + "Since {0} was unable to determine whether this was safe or otherwise failed to do the replacement, it is recommended " + "that you clean up the problem project yourself. You are encouraged to contact a local support person if needed or " + "seek help on {1}. You will be reminded about this each time you start the program as long as this problem remains unresolved.", "Param 0: \"Glyssen\" (product name); " + "Param 1: \"https://community.scripture.software.sil.org/\"" + "This follows the \"AudioAudioProblemPreambleSingle\"."); } else { fmt = LocalizationManager.GetString("DataMigration.NoticeToManuallyFixAudioAudioProjects", "However, doing this would replace the existing projects by the same name. " + "Since {0} was unable to determine whether this was safe or otherwise failed to do the replacements, it is recommended " + "that you clean up the problem projects yourself. You are encouraged to contact a local support person if needed or " + "seek help on {1}. You will be reminded about this each time you start the program as long as these problems remains unresolved.", "Param 0: \"Glyssen\" (product name); " + "Param 1: \"https://community.scripture.software.sil.org/\"" + "This follows the \"AudioAudioProblemPreambleMultiple\"."); } var msg = GetAudioAudioProblemPreamble(unsafeReplacements.Count) + String.Join(Environment.NewLine, unsafeReplacements.Select(r => r.Item1)) + Environment.NewLine + Environment.NewLine + String.Format(fmt, GlyssenInfo.kProduct, Constants.kSupportSite); MessageBox.Show(msg, GlyssenInfo.kProduct, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } if (unsafeReplacements.Any() || safeReplacements.Any()) { retVal = false; } break; default: throw new Exception("No migration found from the existing data version!"); } if (retVal) { info.DataVersion = Settings.Default.DataFormatVersion; } return(retVal); }
[RequestSizeLimit(250_000_000)] // 250MB. public async Task <IActionResult> UploadLiftFile(string projectId, [FromForm] FileUpload fileUpload) { if (!await _permissionService.HasProjectPermission(HttpContext, Permission.ImportExport)) { return(Forbid()); } // Sanitize projectId if (!Sanitization.SanitizeId(projectId)) { return(new UnsupportedMediaTypeResult()); } // Ensure Lift file has not already been imported. if (!await _projRepo.CanImportLift(projectId)) { return(BadRequest("A Lift file has already been uploaded.")); } var liftStoragePath = FileStorage.GenerateLiftImportDirPath(projectId); // Clear out any files left by a failed import RobustIO.DeleteDirectoryAndContents(liftStoragePath); var file = fileUpload.File; if (file is null) { return(BadRequest("Null File")); } // Ensure file is not empty if (file.Length == 0) { return(BadRequest("Empty File")); } // Copy zip file data to a new temporary file fileUpload.FilePath = Path.GetTempFileName(); await using (var fs = new FileStream(fileUpload.FilePath, FileMode.OpenOrCreate)) { await file.CopyToAsync(fs); } // Make temporary destination for extracted files var extractDir = FileOperations.GetRandomTempDir(); // Extract the zip to new created directory. FileOperations.ExtractZipFile(fileUpload.FilePath, extractDir, true); // Check number of directories extracted var directoriesExtracted = Directory.GetDirectories(extractDir); var extractedDirPath = ""; switch (directoriesExtracted.Length) { // If there was one directory, we're good case 1: { extractedDirPath = directoriesExtracted.First(); break; } // If there were two, and there was a __MACOSX directory, ignore it case 2: { var numDirs = 0; foreach (var dir in directoriesExtracted) { if (dir.EndsWith("__MACOSX")) { Directory.Delete(dir, true); } else // This directory probably matters { extractedDirPath = dir; numDirs++; } } // Both directories seemed important if (numDirs == 2) { return(BadRequest("Your zip file should have one directory.")); } break; } // There were 0 or more than 2 directories default: { return(BadRequest( "Your zip file structure has the wrong number of directories.")); } } // Copy the extracted contents into the persistent storage location for the project. FileOperations.CopyDirectory(extractedDirPath, liftStoragePath); Directory.Delete(extractDir, true); // Search for the lift file within the extracted files var extractedLiftNameArr = Directory.GetFiles(liftStoragePath); var extractedLiftPath = Array.FindAll(extractedLiftNameArr, x => x.EndsWith(".lift")); if (extractedLiftPath.Length > 1) { return(BadRequest("More than one .lift file detected.")); } if (extractedLiftPath.Length == 0) { return(BadRequest("No lift files detected.")); } int liftParseResult; // Sets the projectId of our parser to add words to that project var liftMerger = _liftService.GetLiftImporterExporter(projectId, _wordRepo); try { // Add character set to project from ldml file var proj = await _projRepo.GetProject(projectId); if (proj is null) { return(NotFound(projectId)); } _liftService.LdmlImport( Path.Combine(liftStoragePath, "WritingSystems"), proj.VernacularWritingSystem.Bcp47, _projRepo, proj); var parser = new LiftParser <LiftObject, LiftEntry, LiftSense, LiftExample>(liftMerger); // Import words from lift file liftParseResult = parser.ReadLiftFile(extractedLiftPath.FirstOrDefault()); await liftMerger.SaveImportEntries(); } catch (Exception e) { _logger.LogError(e, $"Error importing lift file {fileUpload.Name} into project {projectId}."); return(BadRequest("Error processing the lift data. Contact support for help.")); } // Store that we have imported Lift data already for this project to signal the frontend // not to attempt to import again. var project = await _projRepo.GetProject(projectId); if (project is null) { return(NotFound(projectId)); } project.LiftImported = true; await _projRepo.Update(projectId, project); return(Ok(liftParseResult)); }