commit 75e95e84303d5cb2b00834ee12e64adc9aefa8dd Author: HorizonCode Date: Wed Jun 29 14:56:42 2022 +0200 initial push diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d60c11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +# AWS User-specific +.idea/**/aws.xml +# Generated files +.idea/**/contentModel.xml +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml +# Gradle +.idea/**/gradle.xml +.idea/**/libraries +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr +# CMake +cmake-build-*/ +# Mongo Explorer plugin +.idea/**/mongoSettings.xml +# File-based project format +*.iws +# IntelliJ +out/ +# mpeltonen/sbt-idea plugin +.idea_modules/ +# JIRA plugin +atlassian-ide-plugin.xml +# Cursive Clojure plugin +.idea/replstate.xml +# SonarLint plugin +.idea/sonarlint/ +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +# Editor-based Rest Client +.idea/httpRequests +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser +target/ +run/ +/target/ +/run/ +/dependency-reduced-pom.xml \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..67b5e3d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml new file mode 100644 index 0000000..8b57f45 --- /dev/null +++ b/.idea/google-java-format.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..accd629 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9591860 --- /dev/null +++ b/pom.xml @@ -0,0 +1,115 @@ + + + + 4.0.0 + + net.horizoncode + SysBackup + 1.0-SNAPSHOT + + SysBackup + + + UTF-8 + 11 + 11 + + + + + org.jline + jline + 3.21.0 + + + org.tomlj + tomlj + 1.0.0 + + + org.projectlombok + lombok + 1.18.24 + provided + + + commons-io + commons-io + 2.11.0 + + + com.google.guava + guava + 31.1-jre + + + net.lingala.zip4j + zip4j + 2.11.1 + + + me.tongfei + progressbar + 0.9.3 + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + junit + junit + 4.13.2 + test + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/src/main/java/Bootstrapper.java b/src/main/java/Bootstrapper.java new file mode 100644 index 0000000..17a925c --- /dev/null +++ b/src/main/java/Bootstrapper.java @@ -0,0 +1,8 @@ +import net.horizoncode.sysbackup.App; + +public class Bootstrapper { + + public static void main(String[] args) { + App.getInstance().start(args); + } +} diff --git a/src/main/java/net/horizoncode/sysbackup/App.java b/src/main/java/net/horizoncode/sysbackup/App.java new file mode 100644 index 0000000..7d5f77c --- /dev/null +++ b/src/main/java/net/horizoncode/sysbackup/App.java @@ -0,0 +1,17 @@ +package net.horizoncode.sysbackup; + +import net.horizoncode.sysbackup.cli.CLIProcessor; + +public class App { + private static App instance; + + public static App getInstance() { + if (instance != null) return instance; + return instance = new App(); + } + + public void start(String[] args){ + CLIProcessor cliProcessor = new CLIProcessor(); + cliProcessor.startCLI(args); + } +} diff --git a/src/main/java/net/horizoncode/sysbackup/cli/CLIProcessor.java b/src/main/java/net/horizoncode/sysbackup/cli/CLIProcessor.java new file mode 100644 index 0000000..62e4dbc --- /dev/null +++ b/src/main/java/net/horizoncode/sysbackup/cli/CLIProcessor.java @@ -0,0 +1,123 @@ +package net.horizoncode.sysbackup.cli; + +import net.horizoncode.sysbackup.App; +import net.horizoncode.sysbackup.config.Config; +import net.horizoncode.sysbackup.tasks.TaskBuilder; +import org.apache.commons.io.FileUtils; +import org.tomlj.Toml; +import org.tomlj.TomlParseResult; + +import java.io.File; +import java.io.IOException; +import java.util.Locale; +import java.util.Objects; + +public class CLIProcessor { + + public static void usage() { + String[] usage = { + "Usage: java -jar sysbackup.jar [action] ", + " Backup:", + " backup create a backup based on a backup task configuration file", + " Miscellaneous:", + " checkTaskConf checks a backup task configuration file", + " generateTaskConf generate a example backup task configuration file", + " Examples:", + " java -jar sysbackup.jar generateTaskConf magento", + " java -jar sysbackup.jar backup magento" + }; + for (String u : usage) { + System.out.println(u); + } + } + + public void startCLI(String[] args) { + try { + if ((args == null) || (args.length == 0)) { + usage(); + return; + } + + for (int index = 0; index < args.length; index++) { + switch (args[index].toLowerCase(Locale.ROOT)) { + case "backup": + { + if (args.length <= 1) { + System.err.println("Please specify a output task config name!"); + return; + } + String fileName = args[1]; + File tasksFolder = new File("tasks"); + if (!tasksFolder.exists()) + if (!tasksFolder.mkdir()) System.err.println("Failed to create tasks folder!"); + File taskFile = new File(tasksFolder, fileName + ".toml"); + if (!taskFile.exists()) { + System.err.println("TaskFile " + fileName + ".toml does not exist!"); + return; + } + + Config taskConfig = new Config(taskFile); + TaskBuilder taskBuilder = TaskBuilder.builder().taskConfig(taskConfig).build(); + taskBuilder.start(); + break; + } + case "generatetaskconf": + { + if (args.length <= 1) { + System.err.println("Please specify a output task config name!"); + return; + } + String fileName = args[1]; + File tasksFolder = new File("tasks"); + if (!tasksFolder.exists()) + if (!tasksFolder.mkdir()) System.err.println("Failed to create tasks folder!"); + System.out.println("Saving task config " + fileName + ".toml..."); + FileUtils.copyInputStreamToFile( + Objects.requireNonNull(App.class.getResourceAsStream("/" + "exampletask.toml")), + new File(tasksFolder, fileName + ".toml")); + System.out.println(fileName + ".toml saved!"); + break; + } + case "checktaskconf": + { + if (args.length <= 1) { + System.err.println("Please specify a output task config name!"); + return; + } + String fileName = args[1]; + File tasksFolder = new File("tasks"); + if (!tasksFolder.exists()) + if (!tasksFolder.mkdir()) System.err.println("Failed to create tasks folder!"); + File taskFile = new File(tasksFolder, fileName + ".toml"); + if (!taskFile.exists()) { + System.err.println("TaskFile " + fileName + ".toml does not exist!"); + return; + } + TomlParseResult toml; + try { + toml = Toml.parse(taskFile.toPath()); + } catch (IOException e) { + System.err.println("failed to read TaskFile."); + throw new RuntimeException(e); + } + if (toml.hasErrors()) { + System.err.printf( + "TaskFile checked: found %d issues!:\n", (long) toml.errors().size()); + toml.errors().forEach(error -> System.err.println(error.toString())); + } else { + System.out.println("TaskFile checked successfully: no issues found!"); + } + break; + } + default: + if (index == 0) { + usage(); + return; + } + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } +} diff --git a/src/main/java/net/horizoncode/sysbackup/config/Config.java b/src/main/java/net/horizoncode/sysbackup/config/Config.java new file mode 100644 index 0000000..1e063ef --- /dev/null +++ b/src/main/java/net/horizoncode/sysbackup/config/Config.java @@ -0,0 +1,79 @@ +package net.horizoncode.sysbackup.config; + +import lombok.AccessLevel; +import lombok.Getter; +import net.horizoncode.sysbackup.App; +import org.apache.commons.io.FileUtils; +import org.tomlj.Toml; +import org.tomlj.TomlArray; +import org.tomlj.TomlParseResult; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +@Getter +public class Config { + + private final File configFile; + + private TomlParseResult toml; + + private boolean justCreated; + + public Config(File configFile) { + this.configFile = configFile; + if (!configFile.exists()) { + try { + FileUtils.copyInputStreamToFile( + Objects.requireNonNull(App.class.getResourceAsStream("/" + configFile.getName())), + configFile); + justCreated = true; + } catch (IOException e) { + e.printStackTrace(); + } + } else { + if (configFile.isDirectory()) { + try { + FileUtils.copyInputStreamToFile( + Objects.requireNonNull(App.class.getResourceAsStream("/" + configFile.getName())), + configFile); + justCreated = true; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + if (justCreated) return; + + try { + toml = Toml.parse(configFile.toPath()); + if (toml.hasErrors()) { + toml.errors().forEach(error -> System.err.println(error.toString())); + System.exit(-1); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public int getIntOrDefault(String key, int defaultValue) { + return getToml().contains(key) && getToml().get(key) instanceof Number + ? Math.toIntExact((long) getToml().get(key)) + : defaultValue; + } + + public boolean getBooleanOrDefault(String key, boolean defaultValue) { + return getToml().contains(key) && getToml().get(key) instanceof Boolean + ? getToml().getBoolean(key) + : defaultValue; + } + + public String getStringOrDefault(String key, String defaultValue) { + return getToml().contains(key) ? getToml().getString(key) : defaultValue; + } + + public TomlArray getArray(String key) { + return getToml().getArrayOrEmpty(key); + } +} diff --git a/src/main/java/net/horizoncode/sysbackup/tasks/Task.java b/src/main/java/net/horizoncode/sysbackup/tasks/Task.java new file mode 100644 index 0000000..13c1024 --- /dev/null +++ b/src/main/java/net/horizoncode/sysbackup/tasks/Task.java @@ -0,0 +1,7 @@ +package net.horizoncode.sysbackup.tasks; + +public class Task { + public void start() {} + + public void onDone() {} +} diff --git a/src/main/java/net/horizoncode/sysbackup/tasks/TaskBuilder.java b/src/main/java/net/horizoncode/sysbackup/tasks/TaskBuilder.java new file mode 100644 index 0000000..f5804da --- /dev/null +++ b/src/main/java/net/horizoncode/sysbackup/tasks/TaskBuilder.java @@ -0,0 +1,72 @@ +package net.horizoncode.sysbackup.tasks; + +import lombok.Builder; +import lombok.Getter; +import net.horizoncode.sysbackup.config.Config; +import net.horizoncode.sysbackup.tasks.impl.FileSystemTask; +import org.apache.commons.io.FilenameUtils; +import org.tomlj.TomlArray; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.stream.IntStream; + +@Builder +@Getter +public class TaskBuilder { + + private final Config taskConfig; + + @Builder.Default private final LinkedBlockingQueue taskList = new LinkedBlockingQueue<>(); + + public void start() { + + File backupDir = new File("backups"); + if (!backupDir.exists()) + if (!backupDir.mkdir()) { + System.err.println("Failed to create backups directory!"); + System.exit(2); + } + + SimpleDateFormat sdf = + new SimpleDateFormat( + getTaskConfig().getStringOrDefault("general.dateFormat", "yyyy-MM-dd HH-mm-ss")); + String fileName = + getTaskConfig().getStringOrDefault("filesystem.fileName", "{date} {taskName}") + ".zip"; + + fileName = + fileName + .replace( + "{taskName}", + FilenameUtils.removeExtension(getTaskConfig().getConfigFile().getName())) + .replace("{date}", sdf.format(new Date())); + + File outputFile = new File(backupDir, fileName); + + if (getTaskConfig().getToml().contains("filesystem.targets")) { + TomlArray filesArray = getTaskConfig().getArray("filesystem.targets"); + + IntStream.range(0, filesArray.size()) + .forEach( + value -> { + String target = filesArray.getString(value); + taskList.add( + new FileSystemTask(target, outputFile) { + @Override + public void onDone() { + executeNextTask(); + } + }); + }); + } + + executeNextTask(); + } + + private void executeNextTask() { + Task nextTask = taskList.poll(); + if (nextTask != null) nextTask.start(); + } +} diff --git a/src/main/java/net/horizoncode/sysbackup/tasks/impl/FileSystemTask.java b/src/main/java/net/horizoncode/sysbackup/tasks/impl/FileSystemTask.java new file mode 100644 index 0000000..aa26bb8 --- /dev/null +++ b/src/main/java/net/horizoncode/sysbackup/tasks/impl/FileSystemTask.java @@ -0,0 +1,68 @@ +package net.horizoncode.sysbackup.tasks.impl; + +import me.tongfei.progressbar.ProgressBar; +import me.tongfei.progressbar.ProgressBarBuilder; +import me.tongfei.progressbar.ProgressBarStyle; +import net.horizoncode.sysbackup.tasks.Task; +import net.horizoncode.sysbackup.threading.ThreadPool; +import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.progress.ProgressMonitor; + +import java.io.File; +import java.nio.file.Paths; + +public class FileSystemTask extends Task { + + private final File target; + private File outputZipFile; + + private final ThreadPool threadPool; + + public FileSystemTask(String folderOrFilePath, File outputZipFile) { + this.threadPool = new ThreadPool(3, 10); + this.target = Paths.get(folderOrFilePath).toFile(); + if (!target.exists()) { + onDone(); + System.err.println("File or folder named \"" + folderOrFilePath + "\" does not exist."); + System.exit(2); + return; + } + + this.outputZipFile = outputZipFile; + } + + @Override + public void start() { + threadPool + .getPool() + .submit( + () -> { + try (ZipFile zipFile = new ZipFile(outputZipFile)) { + System.out.println("Indexing files..."); + ProgressMonitor progressMonitor = zipFile.getProgressMonitor(); + zipFile.setRunInThread(true); + zipFile.addFolder(target); + ProgressBarBuilder pbb = + new ProgressBarBuilder() + .setStyle(ProgressBarStyle.ASCII) + .setInitialMax(progressMonitor.getTotalWork()) + .setTaskName("Adding Files..."); + try (ProgressBar pb = pbb.build()) { + while (!progressMonitor.getState().equals(ProgressMonitor.State.READY)) { + pb.stepTo(progressMonitor.getWorkCompleted()); + Thread.sleep(1); + } + pb.stepTo(progressMonitor.getTotalWork()); + } catch (Exception exception) { + exception.printStackTrace(); + onDone(); + } + progressMonitor.endProgressMonitor(); + onDone(); + } catch (Exception ex) { + ex.printStackTrace(); + onDone(); + } + }); + } +} diff --git a/src/main/java/net/horizoncode/sysbackup/threading/ThreadPool.java b/src/main/java/net/horizoncode/sysbackup/threading/ThreadPool.java new file mode 100644 index 0000000..564b276 --- /dev/null +++ b/src/main/java/net/horizoncode/sysbackup/threading/ThreadPool.java @@ -0,0 +1,23 @@ +package net.horizoncode.sysbackup.threading; + +import lombok.Getter; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +public class ThreadPool { + + @Getter private final ExecutorService pool; + + public ThreadPool(int corePoolSize, int maxPoolSize) { + this.pool = scheduledExecutorService(corePoolSize, maxPoolSize); + } + + private ScheduledExecutorService scheduledExecutorService(int corePoolSize, int maxPoolSize) { + ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = + new ScheduledThreadPoolExecutor(corePoolSize); + scheduledThreadPoolExecutor.setMaximumPoolSize(maxPoolSize); + return scheduledThreadPoolExecutor; + } +} diff --git a/src/main/resources/exampletask.toml b/src/main/resources/exampletask.toml new file mode 100644 index 0000000..26342de --- /dev/null +++ b/src/main/resources/exampletask.toml @@ -0,0 +1,16 @@ +[general] +dateFormat = "yyyy-MM-dd" + +[mysql] +enabled = true +host = "" +port = 3306 +database = "magento" +user = "" +password = "" +fileName = "{date} - {taskName}" + +[filesystem] +targets = ["/home/magento/", "/home/test/test.ini"] +fileName = "{date} - {taskName}" + diff --git a/src/test/java/net/horizoncode/sysbackup/TOMLArrayIterationTest.java b/src/test/java/net/horizoncode/sysbackup/TOMLArrayIterationTest.java new file mode 100644 index 0000000..233e29a --- /dev/null +++ b/src/test/java/net/horizoncode/sysbackup/TOMLArrayIterationTest.java @@ -0,0 +1,29 @@ +package net.horizoncode.sysbackup; + +import net.horizoncode.sysbackup.config.Config; +import org.junit.Test; +import org.tomlj.TomlArray; + +import java.io.File; +import java.util.stream.IntStream; + +public class TOMLArrayIterationTest { + + @Test + public void testTOMLIteration() { + File tasksFolder = new File("tasks"); + if (!tasksFolder.exists()) + if (!tasksFolder.mkdir()) System.err.println("Failed to create tasks folder!"); + Config config = new Config(new File(tasksFolder, "magento.toml")); + if (config.getToml().contains("filesystem.targets")) { + TomlArray filesArray = config.getArray("filesystem.targets"); + + IntStream.range(0, filesArray.size()) + .forEach( + value -> { + String target = filesArray.getString(value); + System.out.println(target); + }); + } + } +}