package magic.ui; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.Properties; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import javax.swing.SwingWorker; import magic.data.CardDefinitions; import magic.data.DuelConfig; import magic.data.GeneralConfig; import magic.model.MagicLogger; import magic.model.player.PlayerProfiles; import magic.translate.MText; import magic.utility.FileIO; import magic.utility.MagicFileSystem; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.commons.io.filefilter.WildcardFileFilter; public class ImportWorker extends SwingWorker<Boolean, Void> { // translatable strings private static final String _S1 = "FAIL"; private static final String _S2 = "- new cards snapshot..."; private static final String _S3 = "OK"; private static final String _S4 = "- themes..."; private static final String _S5 = "- preferences..."; private static final String _S6 = "- avatars..."; private static final String _S7 = "- custom decks..."; private static final String _S8 = "- player profiles..."; private static final String _S9 = "- new duel settings..."; private static final String _S10 = "- card images..."; private static final String _S11 = "There was problem during the import process."; private static final String _S12 = "Please see the following file for more details -"; private static final String _S13 = "- mods..."; private static final String _S14 = "- game stats..."; private static final String OK_STRING = String.format("%s\n", MText.get(_S3)); private static final String FAIL_STRING = String.format("%s\n", MText.get(_S1)); private static final FileFilter MODS_THEME_FILE_FILTER = (final File file) -> (file.isFile() && file.getName().endsWith("_theme.zip")) || (file.isDirectory() && file.getName().endsWith("_theme")); private final Path importDataPath; private String progressNote = ""; private final MagicLogger logger; private boolean isFailed = false; public ImportWorker(final File magarenaDirectory) { this.importDataPath = magarenaDirectory.toPath().resolve(MagicFileSystem.DATA_DIRECTORY_NAME); logger = new MagicLogger("import", "Magarena Import Log"); } @Override public Boolean doInBackground() throws IOException { if (!isCancelled()) { importPreferences(); } if (!isCancelled()) { importGameStats(); } if (!isCancelled()) { importNewDuelConfig(); } if (!isCancelled()) { importPlayerProfiles(); } if (!isCancelled()) { importCustomDecks(); } if (!isCancelled()) { importAvatars(); } if (!isCancelled()) { importThemes(); } if (!isCancelled()) { importMods(); } if (!isCancelled()) { importCardImages(); } if (!isCancelled()) { updateNewCardsLog(); } MagicImages.clearCache(); return !isFailed; } @Override public void done() { boolean isOk = true; try { isOk = get(); } catch (InterruptedException | ExecutionException ex) { System.err.println(ex.getCause()); logger.log(ex.getCause().toString()); setProgressNote(FAIL_STRING); isOk = false; } catch (CancellationException ex) { // cancelled by user. } setProgressNote(""); setProgress(0); logger.writeLog(); if (!isOk) { ScreenController.showWarningMessage(String.format("%s\n\n%s\n%s", MText.get(_S11), MText.get(_S12), "...\\Magarena\\logs\\import.log") ); } } /** * Copies H2 game stats database file BUT ONLY if the stats folder * has been not yet been created (ie. post-install, not if you re-run * the import process via the "Reset & restart" option). */ private void importGameStats() throws IOException { setProgressNote(MText.get(_S14)); String directoryName = "stats"; Path sourcePath = importDataPath.resolve(directoryName); Path targetPath = MagicFileSystem.getDataPath().resolve(directoryName); if (sourcePath.toFile().exists() && MagicFileSystem.isMissingOrEmpty(targetPath)) { IOFileFilter dbSuffixFilter = FileFilterUtils.suffixFileFilter(".db"); FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile(), dbSuffixFilter); } setProgressNote(OK_STRING); } /** * Rebuilds the "newcards.log" file so that it contains all the new playable cards which * have been added since the imported and current versions. */ private void updateNewCardsLog() { setProgressNote(MText.get(_S2)); setProgress(0); final File scriptsDirectory = this.importDataPath.resolve("scripts").toFile(); final File[] scriptFiles = MagicFileSystem.getSortedScriptFiles(scriptsDirectory); final List<String> cards = new ArrayList<>(); final int countMax = scriptFiles.length; int count = 0; for (final File file : scriptFiles) { final Properties content = FileIO.toProp(file); cards.add(content.getProperty("name")); count++; setProgress((int)((count / (double)countMax) * 100)); } CardDefinitions.updateNewCardsLog(cards); setProgressNote(OK_STRING); } /** * If imported version does not have an existing "themes" folder then * copies any existing "*_theme" folders or files from "mods" to "themes". */ private void migrateModThemes() throws IOException { final Path targetPath = MagicFileSystem.getDataPath(MagicFileSystem.DataPath.THEMES); File[] modThemes = importDataPath.resolve("mods").toFile().listFiles(MODS_THEME_FILE_FILTER); for (File themeFile : modThemes) { if (themeFile.isDirectory()) { FileUtils.copyDirectoryToDirectory(themeFile, targetPath.toFile()); } else { FileUtils.copyFileToDirectory(themeFile, targetPath.toFile()); } } } /** * Merges "themes" folder and sub-folders. * If file already exists then imported version takes precedence. * (see also {@link migrateModThemes}) */ private void importThemes() throws IOException { setProgressNote(MText.get(_S4)); final String directoryName = "themes"; final Path targetPath = MagicFileSystem.getDataPath(MagicFileSystem.DataPath.THEMES); final Path sourcePath = importDataPath.resolve(directoryName); if (sourcePath.toFile().exists()) { FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile()); } else { migrateModThemes(); } setProgressNote(OK_STRING); } /** * Merges "mods" folder and sub-folders. * If file already exists then imported version takes precedence. */ private void importMods() throws IOException { setProgressNote(MText.get(_S13)); final String directoryName = "mods"; final Path sourcePath = importDataPath.resolve(directoryName); if (sourcePath.toFile().exists()) { final Path targetPath = MagicFileSystem.getDataPath().resolve(directoryName); FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile(), getModsFileFilter()); } setProgressNote(OK_STRING); } /** * Creates a filter that returns everything in the "mods" folder except * the specified cubes which are distributed with each new release and * any existing themes which are now found in the "themes" folder. */ private FileFilter getModsFileFilter() { final String[] excludedCubes = new String[]{ "legacy_cube.txt", "modern_cube.txt", "standard_cube.txt", "extended_cube.txt", "ubeefx_cube.txt" }; final IOFileFilter cubesFilter = new NameFileFilter(excludedCubes, IOCase.INSENSITIVE); final IOFileFilter excludeCubes = FileFilterUtils.notFileFilter(cubesFilter); final IOFileFilter excludeThemes = FileFilterUtils.notFileFilter(new WildcardFileFilter("*_theme*")); return FileFilterUtils.and(excludeCubes, excludeThemes); } /** * Merges "general.cfg" file. * If setting already exists then imported value takes precedence. */ private void importPreferences() throws IOException { setProgressNote(MText.get(_S5)); final String CONFIG_FILENAME = "general.cfg"; // Create new config file with default settings. final File thisConfigFile = MagicFileSystem.getDataPath().resolve(CONFIG_FILENAME).toFile(); if (thisConfigFile.exists()) { thisConfigFile.delete(); } GeneralConfig.getInstance().save(); final Properties thisProperties = FileIO.toProp(thisConfigFile); final Properties theirProperties = FileIO.toProp(importDataPath.resolve(CONFIG_FILENAME).toFile()); // list of latest config settings. final List<String> thisSettings = new ArrayList<>(thisProperties.stringPropertyNames()); // not interested in importing these settings. thisSettings.removeAll(Arrays.asList( "left", "fullScreen", "top", "height", "width", "translation")); // import settings... for (String setting : thisSettings) { if (theirProperties.containsKey(setting)) { thisProperties.setProperty(setting, theirProperties.getProperty(setting)); } } // save updated preferences and reload. FileIO.toFile(thisConfigFile, thisProperties, "General configuration"); GeneralConfig.getInstance().load(); // override download dates to catch any missed "image_updated" updates. final Calendar dt = Calendar.getInstance(); dt.add(Calendar.DAY_OF_MONTH, -60); GeneralConfig.getInstance().setUnimplementedImagesDownloadDate(dt.getTime()); GeneralConfig.getInstance().setPlayableImagesDownloadDate(dt.getTime()); GeneralConfig.getInstance().save(); setProgressNote(OK_STRING); } /** * Merges "avatars" folder and sub-folders. * If file already exists then imported version takes precedence. */ private void importAvatars() throws IOException { setProgressNote(MText.get(_S6)); final String directoryName = "avatars"; final Path sourcePath = importDataPath.resolve(directoryName); if (sourcePath.toFile().exists()) { final Path targetPath = MagicFileSystem.getDataPath().resolve(directoryName); FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile()); } setProgressNote(OK_STRING); } /** * Merges top level "decks" folder only. * Does not import sub-folders (prebuilt, firemind, etc). * If file already exists then imported version takes precedence. */ private void importCustomDecks() throws IOException { setProgressNote(MText.get(_S7)); final String directoryName = "decks"; final Path sourcePath = importDataPath.resolve(directoryName); if (sourcePath.toFile().exists()) { final Path targetPath = MagicFileSystem.getDataPath().resolve(directoryName); final IOFileFilter deckSuffixFilter = FileFilterUtils.suffixFileFilter(".dec"); FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile(), deckSuffixFilter); } setProgressNote(OK_STRING); } /** * Delete "players" folder and replace with imported copy. */ private void importPlayerProfiles() throws IOException { setProgressNote(MText.get(_S8)); final String directoryName = "players"; final Path targetPath = MagicFileSystem.getDataPath().resolve(directoryName); FileUtils.deleteDirectory(targetPath.toFile()); final Path sourcePath = importDataPath.resolve(directoryName); if (sourcePath.toFile().exists()) { FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile()); PlayerProfiles.refreshMap(); } setProgressNote(OK_STRING); } /** * Delete "duels" folder and replace with imported copy. */ private void importNewDuelConfig() { setProgressNote(MText.get(_S9)); boolean isOk = true; final String directoryName = "duels"; final Path targetPath = MagicFileSystem.getDataPath().resolve(directoryName); final Path sourcePath = importDataPath.resolve(directoryName); if (sourcePath.toFile().exists()) { try { FileUtils.deleteDirectory(targetPath.toFile()); FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile()); } catch (IOException ex) { System.err.println(ex); logger.log(ex.toString()); isOk = false; } DuelConfig.getInstance().load(); } setProgressNote(isOk ? OK_STRING : FAIL_STRING); } /** * Moves an existing images folder to the current "\Magarena\images" folder. * <p> * Normally this will just be a case of "renaming" the folder which will be * instantaneous. However if the new location is on a different drive or * filesystem then a "copy and delete" operation will be required which * could take some time depending on how many image files are in the folder * to be moved. */ private boolean moveImages(final String directoryName) { boolean isOk = true; final File imagesFolder = MagicFileSystem.getDataPath(MagicFileSystem.DataPath.IMAGES).toFile(); // pre-version 1.67: default images folder location = "\Magarena". File importFolder = new File(importDataPath.toFile(), directoryName); if (!importFolder.exists()) { // version 1.67+: default images folder location = "\Magarena\<images>". final String folderName = MagicFileSystem.DataPath.IMAGES.getPath().getFileName().toString(); importFolder = new File(importDataPath.resolve(folderName).toFile(), directoryName); } if (importFolder.exists()) { try { FileUtils.moveDirectoryToDirectory(importFolder, imagesFolder, true); } catch (IOException ex) { System.err.println(ex); logger.log(ex.toString()); isOk = false; } } return isOk; } private void importCardImages() { setProgressNote(MText.get(_S10)); // skip if user-defined location. This is stored in the general.cfg file. if (GeneralConfig.getInstance().isCustomCardImagesPath()) { setProgressNote(OK_STRING); return; } String result = OK_STRING; if (!moveImages(MagicFileSystem.TOKEN_IMAGE_FOLDER)) { result = FAIL_STRING; } if (!moveImages(MagicFileSystem.CARD_IMAGE_FOLDER)) { result = FAIL_STRING; } setProgressNote(result); isFailed = !OK_STRING.equals(result); } private void setProgressNote(final String progressNote) { if (getPropertyChangeSupport().hasListeners("progressNote")) { firePropertyChange("progressNote", this.progressNote, progressNote); } this.progressNote = progressNote; if (!progressNote.isEmpty()) { logger.log(progressNote); } } }