diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index bbe53ee..85e7999 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -48,6 +48,20 @@ description="Configure JHelper" icon="/name/admitriev/jhelper/icons/settings.png" /> + + + + + + pathToFile; + final String curDate; + int archivedCount = 0; + + public RecursiveArchiver(@NotNull Project project, @NotNull VirtualFile projectBaseDir, @NotNull String archiveDirectoryRelativePath, @NotNull VirtualFile selectedFile) { + this.project = project; + this.projectBaseDir = projectBaseDir; + this.archiveDirectory = archiveDirectoryRelativePath; + this.selectedFile = selectedFile; + this.pathToFile = new ArrayList<>(); + this.curDate = DateTimeFormatter.ofPattern("dd_MM_yyyy_HH:mm:ss").format(LocalDateTime.now()); + } + + @Override + public void run() { + try { + VfsUtil.createDirectoryIfMissing(archiveDirectory); + } catch (IOException ex) { + throw new NotificationException("Unable to create archive directory", "Root of archive directory does not exist and failed to be created"); + } + VfsUtilCore.visitChildrenRecursively(selectedFile, new VirtualFileVisitor<>() { + @Override + public boolean visitFile(@NotNull VirtualFile file) { + if (file.isDirectory()) { + pathToFile.add(file.getName()); + return true; + } + handleSourceFile(file); + return false; + } + + @Override + public void afterChildrenVisited(@NotNull VirtualFile file) { + pathToFile.remove(pathToFile.size() - 1); + } + }); + if (archivedCount > 0) { + LocalFileSystem.getInstance().refresh(true); // to notify Vfs about new files created in archive + Notificator.showNotification( + "All tasks in " + selectedFile.getName() + " were successfully archived", + NotificationType.INFORMATION + ); + DeleteTaskAction.selectSomeTaskConfiguration(RunManagerEx.getInstanceEx(project)); + } + } + + private void handleSourceFile(@NotNull VirtualFile file) { + RunnerAndConfigurationSettings taskConfigurationAndSettings = findJHelperRCForFile(file, project); + if (taskConfigurationAndSettings == null) { + return; + } + TaskConfiguration taskConfiguration = ((TaskConfiguration) taskConfigurationAndSettings.getConfiguration()); + VirtualFile directory = getFinalArchiveDirectoryOfFile(file); + saveCodeToArchive(file, directory, taskConfiguration.getClassName() + "_" + curDate + ".cpp"); + saveRCToArchive(taskConfiguration, directory, taskConfiguration.getClassName() + "_" + curDate + ".xml"); + deleteFileWithCode(file); + deleteRC(taskConfigurationAndSettings); + ++archivedCount; + } + + private void deleteRC(RunnerAndConfigurationSettings taskConfigurationAndSettings) { + RunManagerEx runManager = RunManagerEx.getInstanceEx(project); + runManager.removeConfiguration(taskConfigurationAndSettings); + } + + private void deleteFileWithCode(@NotNull VirtualFile file) { + try { + file.delete(this); + } catch (IOException e) { + throw new NotificationException("Archiving of the task failed", "Unable to delete sources of the task, caused by " + e.getMessage()); + } + } + + private void saveCodeToArchive(@NotNull VirtualFile fileWithCode, VirtualFile directoryInArchiveForTheTask, String archiveTaskFileName) { + try { + VfsUtil.copyFile(this, fileWithCode, directoryInArchiveForTheTask, archiveTaskFileName); + } catch (IOException e) { + throw new NotificationException("Archiving of the task failed", "Archiving of the code of the solution failed, caused by " + e.getMessage()); + } + } + + private void saveRCToArchive(TaskConfiguration taskConfiguration, VirtualFile directoryInArchiveForTheTask, String archiveRCFileName) { + try { + String rcArchiveFqn = directoryInArchiveForTheTask.getPath() + "/" + archiveRCFileName; + Element rc = new Element("RC"); + Document doc = new Document(rc); + taskConfiguration.writeExternal(rc); + XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat()); + xmlOutputter.output(doc, new FileOutputStream(rcArchiveFqn)); + } catch (IOException e) { + throw new NotificationException("Archiving of the task failed", "Unable to archive the task, caused by " + e.getMessage()); + } + } + + @NotNull + private VirtualFile getFinalArchiveDirectoryOfFile(@NotNull VirtualFile file) { + String relativePathToParentInArchive; + if (pathToFile.isEmpty()) { + relativePathToParentInArchive = archiveDirectory + "/" + file.getParent().getName(); + } else { + relativePathToParentInArchive = archiveDirectory + "/" + String.join("/", pathToFile); + } + VirtualFile parent; + try { + parent = VfsUtil.createDirectoryIfMissing(Paths.get(projectBaseDir.getPath(), relativePathToParentInArchive).toString()); + } catch (IOException e) { + throw new NotificationException("Archiving of the task failed", "Unable to create directory in archive for the task, cased by " + e.getMessage()); + } + return parent == null ? Objects.requireNonNull(projectBaseDir.findFileByRelativePath(relativePathToParentInArchive)) : parent; + } + } + + private static @Nullable RunnerAndConfigurationSettings findJHelperRCForFile(@NotNull VirtualFile file, @NotNull Project project) { + RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project); + for (RunnerAndConfigurationSettings configuration : runManager.getAllSettings()) { + RunConfiguration rc = configuration.getConfiguration(); + if (rc instanceof TaskConfiguration) { + TaskConfiguration task = (TaskConfiguration) rc; + String pathToClassFile = task.getCppPath(); + VirtualFile expectedFie = FileUtilities.getFile(project, pathToClassFile); + if (file.equals(expectedFie)) { + return configuration; + } + } + } + return null; + } +} diff --git a/src/name/admitriev/jhelper/actions/DeleteTaskAction.java b/src/name/admitriev/jhelper/actions/DeleteTaskAction.java index c7a096d..ea4d26d 100644 --- a/src/name/admitriev/jhelper/actions/DeleteTaskAction.java +++ b/src/name/admitriev/jhelper/actions/DeleteTaskAction.java @@ -66,7 +66,7 @@ public void run() { ); } - private static void selectSomeTaskConfiguration(RunManagerEx runManager) { + public static void selectSomeTaskConfiguration(RunManagerEx runManager) { for (RunnerAndConfigurationSettings settings : runManager.getAllSettings()) { if (settings.getConfiguration() instanceof TaskConfiguration) { runManager.setSelectedConfiguration(settings); diff --git a/src/name/admitriev/jhelper/actions/UnarchiveTaskAction.java b/src/name/admitriev/jhelper/actions/UnarchiveTaskAction.java new file mode 100644 index 0000000..48635ff --- /dev/null +++ b/src/name/admitriev/jhelper/actions/UnarchiveTaskAction.java @@ -0,0 +1,158 @@ +package name.admitriev.jhelper.actions; + +import com.intellij.execution.RunManager; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileVisitor; +import com.intellij.psi.PsiManager; +import com.jetbrains.cidr.lang.psi.OCFile; +import name.admitriev.jhelper.IDEUtils; +import name.admitriev.jhelper.configuration.TaskConfiguration; +import name.admitriev.jhelper.configuration.TaskConfigurationType; +import name.admitriev.jhelper.exceptions.NotificationException; +import name.admitriev.jhelper.ui.UIUtils; +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; + +public class UnarchiveTaskAction extends BaseAction { + @Override + protected void performAction(AnActionEvent e) { + Project project = e.getProject(); + if (project == null) { + throw new NotificationException("No project found", "Are you in any project?"); + } + VirtualFile file = e.getDataContext().getData(CommonDataKeys.VIRTUAL_FILE); + if (file == null) { + throw new NotificationException("No task or directory with tasks selected", "To unarchive a solution or a directory you should select it first"); + } + VirtualFile projectBaseDir = ProjectUtil.guessProjectDir(project); + if (projectBaseDir == null) { + throw new NotificationException("Unable to find base directory of project", "If you are in default project then switch to the normal one"); + } + ApplicationManager.getApplication().runWriteAction(new RecursiveUnarchiver(project, projectBaseDir, file)); + } + + private static final class RecursiveUnarchiver implements Runnable { + final Project project; + final VirtualFile projectBaseDir; + final VirtualFile selectedFile; + VirtualFile lastGeneratedFile; + + public RecursiveUnarchiver(@NotNull Project project, @NotNull VirtualFile projectBaseDirPath, @NotNull VirtualFile selectedFile) { + this.project = project; + this.projectBaseDir = projectBaseDirPath; + this.selectedFile = selectedFile; + } + + @Override + public void run() { + VfsUtilCore.visitChildrenRecursively(selectedFile, new VirtualFileVisitor<>() { + @Override + public boolean visitFile(@NotNull VirtualFile file) { + if (file.isDirectory()) { + return true; + } + handleSourceFile(file); + return false; + } + }); + UIUtils.openMethodInEditor(project, (OCFile) Objects.requireNonNull(PsiManager.getInstance(project).findFile(lastGeneratedFile)), "solve"); + IDEUtils.reloadProject(project); + } + + private void handleSourceFile(@NotNull VirtualFile file) { + Pair xmlAndCppToUnarchive = getXmlAndCppToUnarchive(file); + if (xmlAndCppToUnarchive == null) { + return; + } + VirtualFile xmlRC = xmlAndCppToUnarchive.first; + VirtualFile cppFile = xmlAndCppToUnarchive.second; + RunnerAndConfigurationSettings configuration = restoreTaskSettings(file, xmlRC); + TaskConfiguration taskConfiguration = ((TaskConfiguration) configuration.getConfiguration()); + + VirtualFile directory = getUnarchivedDirectoryOfTask(taskConfiguration.getCppPath()); + try { + lastGeneratedFile = VfsUtil.copyFile(this, cppFile, directory, Paths.get(taskConfiguration.getCppPath()).getFileName().toString()); + } catch (IOException e) { + throw new NotificationException("Restoring of task " + file.getNameWithoutExtension() + " failed", "Unable to restore source of the solution, caused by " + e.getMessage()); + } + configuration.storeInDotIdeaFolder(); + RunManager manager = RunManager.getInstance(project); + manager.addConfiguration(configuration); + + try { + xmlRC.delete(this); + cppFile.delete(this); + } catch (IOException e) { + throw new NotificationException("Restoring of task " + file.getNameWithoutExtension() + " failed", + "Unable to delete archived rc and solution, caused by " + e.getMessage()); + } + } + + private RunnerAndConfigurationSettings restoreTaskSettings(@NotNull VirtualFile file, VirtualFile xmlRC) { + TaskConfigurationType taskConfigurationType = new TaskConfigurationType(); + TaskConfiguration taskConfiguration = ((TaskConfiguration) taskConfigurationType.createTemplateConfiguration(project)); + SAXBuilder saxBuilder = new SAXBuilder(); + File inputFile = new File(xmlRC.getPath()); + Document document; + try { + document = saxBuilder.build(inputFile); + } catch (JDOMException | IOException e) { + throw new NotificationException("Restoring of task " + file.getNameWithoutExtension() + " failed", "Unable to restore RC, caused by " + e.getMessage()); + } + RunManager manager = RunManager.getInstance(project); + taskConfiguration.readExternal(document.getRootElement()); + ConfigurationFactory factory = taskConfigurationType.getConfigurationFactories()[0]; + return manager.createConfiguration(taskConfiguration, factory); + } + + private VirtualFile getUnarchivedDirectoryOfTask(String relativeCppPath) { + VirtualFile directory; + try { + directory = VfsUtil.createDirectoryIfMissing(Paths.get(projectBaseDir.getPath(), relativeCppPath).getParent().toString()); + } catch (IOException e) { + throw new NotificationException("Unarchive of the task failed", "Unable to create directory for the task, cased by " + e.getMessage()); + } + return directory == null ? Objects.requireNonNull(projectBaseDir.findFileByRelativePath(Paths.get(relativeCppPath).getParent().toString())) : directory; + } + + /* file can be either xml with RC or cpp with solution */ + private static @Nullable Pair getXmlAndCppToUnarchive(@NotNull VirtualFile file) { + String prefix = file.getNameWithoutExtension(); + VirtualFile xml = null; + VirtualFile cpp = null; + for (VirtualFile otherFile : file.getParent().getChildren()) { + if (otherFile.getNameWithoutExtension().equals(prefix)) { + String extension = otherFile.getExtension(); + if (extension != null && extension.equals("cpp")) { + cpp = otherFile; + } + if (extension != null && extension.equals("xml")) { + xml = otherFile; + } + } + } + if (xml == null || cpp == null) { + return null; + } + return new Pair<>(xml, cpp); + } + } +} diff --git a/src/name/admitriev/jhelper/components/Configurator.java b/src/name/admitriev/jhelper/components/Configurator.java index dfeb412..b72a25c 100644 --- a/src/name/admitriev/jhelper/components/Configurator.java +++ b/src/name/admitriev/jhelper/components/Configurator.java @@ -32,6 +32,7 @@ public static class State { private String tasksDirectory; private String outputFile; private String runFile; + private String archiveDirectory; private boolean codeEliminationOn; private boolean codeReformattingOn; @@ -40,6 +41,7 @@ public State( String tasksDirectory, String outputFile, String runFile, + String archiveDirectory, boolean codeEliminationOn, boolean codeReformattingOn ) { @@ -47,12 +49,13 @@ public State( this.tasksDirectory = tasksDirectory; this.outputFile = outputFile; this.runFile = runFile; + this.archiveDirectory = archiveDirectory; this.codeEliminationOn = codeEliminationOn; this.codeReformattingOn = codeReformattingOn; } public State() { - this("", "tasks", "output/main.cpp", "testrunner/main.cpp", false, false); + this("", "tasks", "output/main.cpp", "testrunner/main.cpp", "archive", false, false); } public String getAuthor() { @@ -71,6 +74,10 @@ public String getRunFile() { return runFile; } + public String getArchiveDirectory() { + return archiveDirectory; + } + public boolean isCodeEliminationOn() { return codeEliminationOn; } diff --git a/src/name/admitriev/jhelper/configuration/TaskConfiguration.java b/src/name/admitriev/jhelper/configuration/TaskConfiguration.java index 0c6e0a2..28e0e10 100644 --- a/src/name/admitriev/jhelper/configuration/TaskConfiguration.java +++ b/src/name/admitriev/jhelper/configuration/TaskConfiguration.java @@ -134,6 +134,7 @@ private static StreamConfiguration readStreamConfiguration( @Override public void readExternal(Element element) { super.readExternal(element); + setName(element.getAttributeValue("name","")); className = element.getAttributeValue("className", ""); cppPath = element.getAttributeValue("cppPath", ""); input = readStreamConfiguration(element, "inputPath", "inputFile"); @@ -166,6 +167,7 @@ private static Test readTest(Element element) { @Override public void writeExternal(Element element) { + element.setAttribute("name", getName()); element.setAttribute("className", className); element.setAttribute("cppPath", cppPath); element.setAttribute("inputType", String.valueOf(input.type.name())); diff --git a/src/name/admitriev/jhelper/ui/ConfigurationDialog.java b/src/name/admitriev/jhelper/ui/ConfigurationDialog.java index 1dd9572..5691115 100644 --- a/src/name/admitriev/jhelper/ui/ConfigurationDialog.java +++ b/src/name/admitriev/jhelper/ui/ConfigurationDialog.java @@ -16,6 +16,7 @@ public class ConfigurationDialog extends DialogWrapper { private FileSelector tasksDirectory; private FileSelector outputFile; private FileSelector runFile; + private FileSelector archiveDirectory; private JCheckBox codeEliminationOn; private JCheckBox codeReformattingOn; @@ -40,6 +41,11 @@ public ConfigurationDialog(@NotNull Project project, Configurator.State configur configuration.getRunFile(), RelativeFileChooserDescriptor.fileChooser(project.getBaseDir()) ); + archiveDirectory = new FileSelector( + project, + configuration.getArchiveDirectory(), + RelativeFileChooserDescriptor.fileChooser(project.getBaseDir()) + ); codeEliminationOn = new JCheckBox("Eliminate code?", configuration.isCodeEliminationOn()); codeReformattingOn = new JCheckBox("Reformat code?", configuration.isCodeReformattingOn()); @@ -49,6 +55,7 @@ public ConfigurationDialog(@NotNull Project project, Configurator.State configur panel.add(LabeledComponent.create(tasksDirectory, "Tasks directory")); panel.add(LabeledComponent.create(outputFile, "Output file")); panel.add(LabeledComponent.create(runFile, "Run File")); + panel.add(LabeledComponent.create(archiveDirectory, "Archive Directory")); panel.add(codeEliminationOn); panel.add(codeReformattingOn); @@ -69,6 +76,7 @@ public Configurator.State getConfiguration() { tasksDirectory.getText(), outputFile.getText(), runFile.getText(), + archiveDirectory.getText(), codeEliminationOn.isSelected(), codeReformattingOn.isSelected() );