diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/ProjectsBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/ProjectsBean.java index c67100a3e39d2f039649bec72a6f01a80ac477d9..7aa062894601ec66efa9b4aa9442907993a0b0ab 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/ProjectsBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/ProjectsBean.java @@ -82,9 +82,9 @@ public final class ProjectsBean { } /** - * This method adds a new project with the given name to the application if this is possible. In either case (this means if the project has been created, if he + * This method adds a new project with the given name to the application if this is possible. In either case (this means if the project has been created, if the * project could not be created for any reason) the user will be informed via the growl component. In other words: A message will be delivered within the current - * context.The creation of a new project means in this context that a directory with its name will be created and that an empty but valid kax-file will be + * context. The creation of a new project means in this context that a directory with its name will be created and that an empty but valid kax-file will be * produced. After creating the project (provided that he creation was successful) the projects list of the "calling" {@link CurrentProjectOverviewBean} will be * updated.<br> * If something should go wrong, no exception will be thrown. Everything is caught if necessary. @@ -111,6 +111,38 @@ public final class ProjectsBean { } } + /** + * This method tries to copy the project with the given name and saves it under the given new name. In either case (this means if the project has been created, + * if the project could not be created for any reason) the user will be informed via the growl component. In other words: A message will be delivered within the + * current context. After creating the project (provided that he creation was successful) the projects list of the "calling" {@link CurrentProjectOverviewBean} + * will be updated.<br> + * If something should go wrong, no exception will be thrown. Everything is caught if necessary. + * + * @param sourceProject + * The name of the source project. + * @param destinationProject + * The name of the new (copied) project. + */ + public void copyProject(final String sourceProject, final String destinationProject) { + try { + // Try and use the FS-Manager to copy the project atomically. + FSManager.getInstance().copyProject(sourceProject, destinationProject); + // If there were no exception, everything went well. We can add the project to our list. + this.projects.add(destinationProject); + // Inform the user + ProjectsBean.showMessage(FacesMessage.SEVERITY_INFO, "Project created."); + // Update the list of the "calling" bean + CurrentProjectOverviewBean.getCurrentInstance().updateLists(); + } catch (final IOException ex) { + ProjectsBean.LOG.error("An error occured while creating the project.", ex); + ProjectsBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while creating the project."); + ex.printStackTrace(); + } catch (final ProjectAlreadyExistingException ex) { + ProjectsBean.LOG.info("A project with the same name exists already.", ex); + ProjectsBean.showMessage(FacesMessage.SEVERITY_WARN, "A project with the same name exists already."); + } + } + /** * This method can be used to open an already existing project. This means that the current state of the project on the file system is loaded into an instance of * {@link MIProject}. This instance can be modified at will and for example later saved by the {@link FSManager}. diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java index 692b5069cd17ad323eb48cb15b069a160f29d5d8..85b70ccc8611dab5019d4a5ed38d674788055660 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java @@ -23,12 +23,15 @@ package kieker.webgui.common; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.math.RoundingMode; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.channels.ByteChannel; +import java.nio.channels.FileChannel; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.DecimalFormat; @@ -82,9 +85,9 @@ public final class FSManager { // NOCS (Class Data Abstraction Coupling, Class F */ private static final String ROOT_DIRECTORY = "data"; /** - * This is the buffer (in bytes) used to copy files. + * This is the buffer (in bytes) used to copy and upload files. */ - private static final int BUF_SIZE_BYTES = 1024; + private static final int BUF_SIZE_BYTES = 1024 * 1024; /** * This is the singleton instance of this class. */ @@ -337,11 +340,83 @@ public final class FSManager { // NOCS (Class Data Abstraction Coupling, Class F throw new ProjectAlreadyExistingException("A project with the name '" + newName + "' exists already."); } - // TODO Copy all files + // Get the necessary paths + final File dstProjDir = new File(FSManager.ROOT_DIRECTORY + File.separator + newName); + final File srcLibDir = new File(FSManager.ROOT_DIRECTORY + File.separator + projectName + File.separator + FSManager.LIB_DIRECTORY); + final File dstLibDir = new File(FSManager.ROOT_DIRECTORY + File.separator + newName + File.separator + FSManager.LIB_DIRECTORY); + + final File srcKaxFile = new File(FSManager.ROOT_DIRECTORY + File.separator + projectName + File.separator + projectName + "." + + FSManager.KAX_EXTENSION); + final File dstKaxFile = new File(FSManager.ROOT_DIRECTORY + File.separator + newName + File.separator + newName + "." + FSManager.KAX_EXTENSION); + + // Create the directories + final boolean projDirCreated = dstProjDir.mkdir(); + final boolean libDirCreated = dstLibDir.mkdir(); + // The following part is only necessary to calm FindBugs... + @SuppressWarnings("unused") + final boolean createResults = projDirCreated && libDirCreated; + + // Copy the kax file + this.copyFile(srcKaxFile, dstKaxFile); + + // Copy the libs + for (final File lib : srcLibDir.listFiles()) { + final File dstLibFile = new File(dstLibDir, lib.getName()); + this.copyFile(lib, dstLibFile); + } + } } } + /** + * This method copies the given source file to the given destination file. + * + * @param src + * The source element. This should be a file. + * @param dst + * The destination element. This should be a (non existing) file. + * @throws IOException + * If something went wrong + */ + private void copyFile(final File src, final File dst) throws IOException { + // Open the files + final FileInputStream fileInputStream = new FileInputStream(src); + final FileOutputStream fileOutputStream = new FileOutputStream(dst); + final FileChannel inputChannel = fileInputStream.getChannel(); + final FileChannel outputChannel = fileOutputStream.getChannel(); + + // Copy the data + this.transfer(inputChannel, outputChannel, src.length()); + + // Close the streams + fileInputStream.close(); + fileOutputStream.close(); + + dst.setLastModified(src.lastModified()); + } + + /** + * This method transfers data from one channel to the other. + * + * @param fileChannel + * The input channel. + * @param byteChannel + * The output channel. + * @param lengthInBytes + * The size in bytes to be copied. + * @throws IOException + * If something went wrong. + */ + private void transfer(final FileChannel fileChannel, final ByteChannel byteChannel, final long lengthInBytes) throws IOException { + long overallBytesTransfered = 0L; + while (overallBytesTransfered < lengthInBytes) { + final long count = Math.min(FSManager.BUF_SIZE_BYTES, lengthInBytes - overallBytesTransfered); + final long bytesTransfered = fileChannel.transferTo(overallBytesTransfered, count, byteChannel); + overallBytesTransfered += bytesTransfered; + } + } + /** * Checks whether a project with the name exists on the file system. * diff --git a/Kieker.WebGUI/src/main/webapp/ProjectOverview.xhtml b/Kieker.WebGUI/src/main/webapp/ProjectOverview.xhtml index 11543e6a5fa961245be112488d86a03d9061d8d5..dbd05ea61d65f799703dd0701a871198dc214ef7 100644 --- a/Kieker.WebGUI/src/main/webapp/ProjectOverview.xhtml +++ b/Kieker.WebGUI/src/main/webapp/ProjectOverview.xhtml @@ -69,7 +69,7 @@ <p:menuitem icon="ui-icon-wrench" id="editAnalysisViews" styleClass="element-with-whitespace" value=" Cockpit Editor" ajax="false" action="#{currentCockpitEditorBean.setProject(project)}" /> <p:menuitem icon="ui-icon-image" id="showAnalysis" styleClass="element-with-whitespace" value=" Cockpit" ajax="false" action="#{currentCockpitBean.setProject(project)}" /> <p:separator/> - <p:menuitem id="copyButton" icon="ui-icon-copy" styleClass="element-with-whitespace" value=" Copy Project" action="#{currentProjectOverviewBean.setProjectName(project)}" onclick="copyProjectDialog.show()" disabled="true"/> + <p:menuitem id="copyButton" icon="ui-icon-copy" styleClass="element-with-whitespace" value=" Copy Project" action="#{currentProjectOverviewBean.setProjectName(project)}" onclick="copyProjectDialog.show()"/> <p:menuitem id="renameButton" icon="ui-icon-pencil" styleClass="element-with-whitespace" value=" Rename Project" action="#{currentProjectOverviewBean.setProjectName(project)}" onclick="renameProjectDialog.show()" disabled="true"/> <p:menuitem id="deleteButton" icon="ui-icon-trash" styleClass="element-with-whitespace" value=" Delete Project" action="#{currentProjectOverviewBean.setProjectName(project)}" onclick="deleteProjectDialog.show()" disabled="true"/> </p:menu>