This is the codeAbility Sharing Platform! Learn more about the codeAbility Sharing Platform.

Skip to content
Snippets Groups Projects
Commit 8b3c4564 authored by Daniel Crazzolara's avatar Daniel Crazzolara
Browse files

Refactoring 'storeZip' to increase stability

parent a4981c9a
Branches
2 merge requests!188Merging Peer Reviewing et. al to Master,!164211 peer reviewing functionality
......@@ -19,6 +19,7 @@ import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.zip.ZipEntry;
......@@ -61,7 +62,7 @@ public class ExerciseService {
/**
* Retrieve a zipped Artemis Exercise from a given URL
* and store it in 'tmp-import' directory
* and store it in a temporary directory
* @param exerciseUrl the url of the exposed exercise
* @throws ArtemisImportError
*/
......@@ -97,7 +98,7 @@ public class ExerciseService {
* - Copy, commit and push files to the new repository
*
* @param exerciseInfo of the exercise to import
* @param exerciseToken of the stored exercise (in '/tmp-import' directory) to import
* @param exerciseToken of the stored exercise (in '/tmp' directory) to import
*/
public void importExercise(ArtemisExerciseInfo exerciseInfo, String exerciseToken, Integer gitlabGroupId)
throws GitLabApiException, GitAPIException, IOException, ArtemisImportError {
......@@ -108,11 +109,11 @@ public class ExerciseService {
Group group = gitlabService.getGroupById(gitlabGroupId);
String repoUrl = gitlabService.createRepository(group, exerciseInfo.getTitle());
Repository repo = gitlabService.checkoutRepo(repoUrl);
gitlabService.commitAndPushToRepo(repo, new File("tmp-import", exerciseToken));
gitlabService.commitAndPushToRepo(repo, new File("/tmp", exerciseToken));
repo.close();
try {
FileUtils.deleteDirectory(new File("tmp-import", exerciseToken));
FileUtils.deleteDirectory(new File("/tmp", exerciseToken));
FileUtils.deleteDirectory(repo.getDirectory().getParentFile());
} catch (IOException e) {
log.error("Could not remove directory: {} \n", e.getMessage());
......@@ -132,7 +133,7 @@ public class ExerciseService {
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
File exerciseDetailsFile = Objects.requireNonNull(
new File("tmp-import", exerciseToken).listFiles((file, name) -> name.startsWith("Exercise-Details-") && name.endsWith(".json"))
new File("/tmp", exerciseToken).listFiles((file, name) -> name.startsWith("Exercise-Details-") && name.endsWith(".json"))
)[0];
mapArtemisExerciseInfoValues(exerciseDetailsFile);
......@@ -223,70 +224,88 @@ public class ExerciseService {
* @param subDir name of the tmp-subdirectory
*/
private void storeZipInTmp(ZipInputStream zipInputStream, String subDir) throws IOException {
if (!new File("tmp-import").exists() && !new File("tmp-import").mkdir()) {
throw new IOException("Could not create missing '/tmp-import' directory");
}
File tmpDir = new File("tmp-import", subDir);
if (!tmpDir.exists() && !tmpDir.mkdir()) {
throw new IOException("Temporary exercise directory could not be created");
Path targetDir;
try {
targetDir = Files.createTempDirectory(subDir);
} catch (IllegalArgumentException e) { // We expect this when calling this method recursively for contained Zip files
targetDir = Files.createDirectories(Paths.get(subDir));
}
ZipEntry zipEntry;
// https://www.baeldung.com/java-compress-and-uncompress
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (zipEntry.getName().contains(".git")) { // important as Artemis exercises come in submodules
continue;
}
File entryFile = new File(tmpDir, zipEntry.getName());
if (zipEntry.getName().endsWith(".zip")) {
writeFile(entryFile, zipInputStream);
Path zipEntryPath = Paths.get(targetDir.toString(), zipEntry.getName());
if (isZipFile(zipEntry)) {
writeFile(zipEntryPath.toFile(), zipInputStream);
String newSubDir;
if (StringUtils.endsWithAny(zipEntry.getName(), new String[] { "-exercise.zip", "-solution.zip", "-tests.zip" })) {
// extract repo directory name only
newSubDir =
Paths
.get(
entryFile.getParentFile().getParent(),
zipEntry.getName().substring(zipEntry.getName().lastIndexOf("-") + 1, zipEntry.getName().lastIndexOf("."))
)
.toString();
newSubDir = Paths.get(targetDir.toString(), getRepoNameFromZipEntry(zipEntry)).toString();
} else {
newSubDir = Paths.get(subDir, entryFile.getName()).toString();
newSubDir = newSubDir.substring(0, newSubDir.lastIndexOf(".")); // remove '.zip' from path
}
if (newSubDir.startsWith("tmp-import")) {
newSubDir = newSubDir.replaceFirst("tmp-import/", "");
newSubDir =
Paths.get(targetDir.toString(), zipEntry.getName().substring(0, zipEntry.getName().lastIndexOf("."))).toString();
}
storeZipInTmp(new ZipInputStream(new FileInputStream(entryFile)), newSubDir);
if (Files.deleteIfExists(entryFile.toPath())) {
log.warn("Unable to delete temporary Zip file [{}] after extraction. Skipping..", entryFile.getPath());
storeZipInTmp(new ZipInputStream(new FileInputStream(zipEntryPath.toFile())), newSubDir);
if (Files.deleteIfExists(zipEntryPath)) {
log.warn("Unable to delete temporary Zip file [{}] after extraction. Skipping..", zipEntryPath);
}
} else if (zipEntry.isDirectory()) {
if (!entryFile.mkdir()) {
throw new IOException("Failed to create directory " + entryFile);
}
} else if (!zipEntry.getName().endsWith(".zip")) {
// fix for Windows-created archives
File parent = entryFile.getParentFile();
if (!parent.isDirectory() && !parent.mkdirs()) {
throw new IOException("Failed to create directory " + parent);
}
writeFile(entryFile, zipInputStream);
Files.createDirectory(zipEntryPath);
} else {
writeFile(zipEntryPath.toFile(), zipInputStream);
}
}
if (tmpDir.isDirectory() && fileService.isEmpty(tmpDir.toPath())) {
Files.delete(tmpDir.toPath());
}
cleanUpTmpDirectory(targetDir.toFile());
zipInputStream.close();
fileService.scheduleForDeletion(tmpDir.toPath(), 60);
}
/**
* Method used to write files from zip inputStreams
* Utility method used to check if a ZipEntry represents a Zip file
* @param zipEntry to check
* @return true if zipEntry is a Zip file, false otherwise
*/
private boolean isZipFile(ZipEntry zipEntry) {
return zipEntry.getName().endsWith(".zip");
}
/**
* Cleans up a temporary directory by removing it if empty
* or scheduling its deletion with a delay of 60 minutes, otherwise.
*
* @param tmpDir to clean up
*/
private void cleanUpTmpDirectory(File tmpDir) {
try {
if (tmpDir.exists() && tmpDir.isDirectory() && fileService.isEmpty(tmpDir.toPath())) {
Files.delete(tmpDir.toPath());
}
fileService.scheduleForDeletion(tmpDir.toPath(), 60);
} catch (IOException e) {
log.error("An error occurred while cleaning up a temporary directory: ", e);
}
}
/**
* Utility method used to extract the repository name from Zip files
* representing git submodules. (Artemis exercise exports come in
* submodules for 'exercise', 'solution' and 'test')
* @param zipEntry to get the repository name from
* @return the repository name
*/
private String getRepoNameFromZipEntry(ZipEntry zipEntry) {
return zipEntry.getName().substring(zipEntry.getName().lastIndexOf("-") + 1, zipEntry.getName().lastIndexOf("."));
}
/**
* Method used to write files from a zipInputStream
* @param entryFile File to write
* @param zipInputStream Byte stream to write
* @param zipInputStream Byte stream to read from
* @throws IOException
*/
private void writeFile(File entryFile, ZipInputStream zipInputStream) throws IOException {
......@@ -310,7 +329,7 @@ public class ExerciseService {
ObjectMapper mapper = new ObjectMapper(
new YAMLFactory().enable(YAMLGenerator.Feature.INDENT_ARRAYS).disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
);
mapper.writeValue(new File("tmp-import/" + exerciseToken, "metadata.yaml"), exerciseInfo.getExerciseInfoOnly());
mapper.writeValue(new File("/tmp/" + exerciseToken, "metadata.yaml"), exerciseInfo.getExerciseInfoOnly());
}
/**
......@@ -357,19 +376,17 @@ public class ExerciseService {
private void renameFiles(String exerciseToken) {
try {
File problemStatementFile = Objects.requireNonNull(
new File("tmp-import", exerciseToken)
.listFiles((file, name) -> name.startsWith("Problem-Statement-") && name.endsWith(".md"))
new File("/tmp", exerciseToken).listFiles((file, name) -> name.startsWith("Problem-Statement-") && name.endsWith(".md"))
)[0];
if (!problemStatementFile.renameTo(new File("tmp-import" + File.separator + exerciseToken, "exercise.md"))) {
if (!problemStatementFile.renameTo(new File("/tmp" + File.separator + exerciseToken, "exercise.md"))) {
log.error("Unable to rename file [{}]. Skipping..", problemStatementFile.getName());
}
File exerciseDetailsFile = Objects.requireNonNull(
new File("tmp-import", exerciseToken)
.listFiles((file, name) -> name.startsWith("Exercise-Details-") && name.endsWith(".json"))
new File("/tmp", exerciseToken).listFiles((file, name) -> name.startsWith("Exercise-Details-") && name.endsWith(".json"))
)[0];
Files.move(exerciseDetailsFile.toPath(), exerciseDetailsFile.toPath().resolveSibling("Exercise-Details.json"));
} catch (ArrayIndexOutOfBoundsException e) {
log.error("File to rename could not be found in [{}].", "tmp-import" + File.separator + exerciseToken);
log.error("File to rename could not be found in [{}].", "/tmp" + File.separator + exerciseToken);
} catch (IOException ioe) {
log.error("Error while renaming file: {}", ioe.getMessage());
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment