Skip to content

Commit b46be7f

Browse files
committed
feat: implement eln style writing for reader.ZipStrategy
1 parent bccbf0b commit b46be7f

4 files changed

Lines changed: 196 additions & 15 deletions

File tree

src/main/java/edu/kit/datamanager/ro_crate/preview/CratePreview.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
import java.io.File;
44
import java.io.IOException;
5+
import java.nio.file.Files;
6+
7+
import edu.kit.datamanager.ro_crate.Crate;
8+
import edu.kit.datamanager.ro_crate.writer.CrateWriter;
9+
import edu.kit.datamanager.ro_crate.writer.FolderStrategy;
510
import net.lingala.zip4j.ZipFile;
611
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
12+
import org.apache.commons.io.FileUtils;
713

814
/**
915
* Interface for the ROCrate preview. This manages the human-readable
@@ -15,10 +21,77 @@
1521
*/
1622
public interface CratePreview {
1723

24+
/**
25+
* Generate a preview of the crate and store it into the given target directory.
26+
* It is the caller's responsibility to handle, e.g. delete after use, the result
27+
* (The caller takes ownership of the result).
28+
* <p>
29+
* <b>IMPORTANT NOTE:</b> This method currently has a default implementation that relies
30+
* on deprecated methods. In future, you will have to implement this method directly.
31+
*
32+
* @param crate the crate to generate a preview for.
33+
* @param targetDir the target directory to store the preview in,
34+
* owned by the caller.
35+
* @throws IOException if an error occurs while generating the preview.
36+
*/
37+
default void generate(Crate crate, File targetDir) throws IOException {
38+
// disable preview generation to avoid recursion,
39+
// as this is usually called in the process of writing a crate
40+
// (including preview)
41+
new CrateWriter<>(new FolderStrategy().disablePreview())
42+
.save(crate, targetDir.getAbsolutePath());
43+
this.saveAllToFolder(targetDir);
44+
try (var stream = Files.list(targetDir.toPath())) {
45+
stream
46+
.filter(path -> !path.getFileName().toString().equals("ro-crate-preview.html"))
47+
.filter(path -> !path.getFileName().toString().equals("ro-crate-preview_files"))
48+
.forEach(path -> {
49+
try {
50+
if (Files.isDirectory(path)) {
51+
FileUtils.deleteDirectory(path.toFile());
52+
} else {
53+
Files.delete(path);
54+
}
55+
} catch (IOException e) {
56+
// Silently ignore deletion errors
57+
}
58+
});
59+
}
60+
}
61+
62+
/**
63+
* Takes a crate in form of a zip file and generates a preview of it,
64+
* which will be stored within the crate.
65+
*
66+
* @param zipFile the zip file with the crate, which should receive a preview.
67+
* @throws IOException if an error occurs while saving the preview
68+
*
69+
* @deprecated Use {@link #generate(Crate, File)} instead.
70+
*/
71+
@Deprecated(since = "2.1.0", forRemoval = true)
1872
void saveAllToZip(ZipFile zipFile) throws IOException;
1973

74+
/**
75+
* Saves the preview, given by the folder, into the given folder.
76+
*
77+
* @param folder the folder (containing a crate) to save the preview in.
78+
* @throws IOException if an error occurs while saving the preview.
79+
*
80+
* @deprecated Use {@link #generate(Crate, File)} instead.
81+
*/
82+
@Deprecated(since = "2.1.0", forRemoval = true)
2083
void saveAllToFolder(File folder) throws IOException;
21-
84+
85+
/**
86+
* Saves the preview, given by the metadata, into the given stream.
87+
*
88+
* @param metadata the metadata of the crate to save the preview in.
89+
* @param stream the stream to save the preview in.
90+
* @throws IOException if an error occurs while saving the preview.
91+
*
92+
* @deprecated Use {@link #generate(Crate, File)} instead.
93+
*/
94+
@Deprecated(since = "2.1.0", forRemoval = true)
2295
void saveAllToStream(String metadata, ZipOutputStream stream) throws IOException;
2396

2497
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package edu.kit.datamanager.ro_crate.writer;
2+
3+
import java.io.IOException;
4+
5+
public interface ElnFormatWriter<SOURCE_TYPE> extends GenericWriterStrategy<SOURCE_TYPE> {
6+
7+
/**
8+
* Write in ELN format style, meaning with a root subfolder in the zip file.
9+
* Same as {@link #withRootSubdirectory()}.
10+
*
11+
* @throws IOException if an error occurs
12+
*/
13+
ElnFormatWriter<SOURCE_TYPE> usingElnStyle();
14+
15+
/**
16+
* Alias with more generic name for {@link #usingElnStyle()}.
17+
*
18+
* @throws IOException if an error occurs
19+
*/
20+
default ElnFormatWriter<SOURCE_TYPE> withRootSubdirectory() {
21+
return this.usingElnStyle();
22+
}
23+
}

src/main/java/edu/kit/datamanager/ro_crate/writer/FolderStrategy.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ public class FolderStrategy implements GenericWriterStrategy<String> {
2525

2626
private static final Logger logger = LoggerFactory.getLogger(FolderStrategy.class);
2727

28+
protected boolean writePreview = true;
29+
30+
/**
31+
* For internal use. Skips the preview generation when writing the crate.
32+
*
33+
* @return this instance of FolderStrategy
34+
*
35+
* @deprecated May be removed in future versions. Not intended for public use.
36+
*/
37+
@Deprecated(since = "2.1.0", forRemoval = true)
38+
public FolderStrategy disablePreview() {
39+
this.writePreview = false;
40+
return this;
41+
}
42+
2843
@Override
2944
public void save(Crate crate, String destination) throws IOException {
3045
File file = new File(destination);
@@ -38,7 +53,7 @@ public void save(Crate crate, String destination) throws IOException {
3853
FileUtils.copyInputStreamToFile(inputStream, json);
3954
inputStream.close();
4055
// save also the preview files to the crate destination
41-
if (crate.getPreview() != null) {
56+
if (crate.getPreview() != null && this.writePreview) {
4257
crate.getPreview().saveAllToFolder(file);
4358
}
4459
for (var e : crate.getUntrackedFiles()) {

src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStrategy.java

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,78 @@
55
import edu.kit.datamanager.ro_crate.Crate;
66
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
77
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
8+
import edu.kit.datamanager.ro_crate.preview.CratePreview;
89
import net.lingala.zip4j.ZipFile;
910
import net.lingala.zip4j.model.ZipParameters;
11+
import org.apache.commons.io.FileUtils;
1012
import org.slf4j.Logger;
1113
import org.slf4j.LoggerFactory;
1214

1315
import java.io.ByteArrayInputStream;
16+
import java.io.File;
1417
import java.io.IOException;
1518
import java.io.InputStream;
1619
import java.nio.charset.StandardCharsets;
20+
import java.nio.file.Path;
21+
import java.util.Optional;
22+
import java.util.UUID;
23+
import java.util.regex.Matcher;
1724

1825
/**
1926
* Implementation of the writing strategy to provide a way of writing crates to
2027
* a zip archive.
2128
*/
22-
public class ZipStrategy implements GenericWriterStrategy<String> {
23-
29+
public class ZipStrategy implements
30+
GenericWriterStrategy<String>,
31+
ElnFormatWriter<String>
32+
{
2433
private static final Logger logger = LoggerFactory.getLogger(ZipStrategy.class);
2534

35+
/**
36+
* Defines if the zip file will directly contain the crate,
37+
* or if it will contain a subdirectory with the crate.
38+
*/
39+
protected boolean createRootSubdir = false;
40+
41+
@Override
42+
public ElnFormatWriter<String> usingElnStyle() {
43+
this.createRootSubdir = true;
44+
return this;
45+
}
46+
2647
@Override
2748
public void save(Crate crate, String destination) throws IOException {
49+
String innerFolderName = "";
50+
if (this.createRootSubdir) {
51+
String dot = Matcher.quoteReplacement(".");
52+
String end = Matcher.quoteReplacement("$");
53+
innerFolderName = Path.of(destination).getFileName()
54+
.toString()
55+
// remove .zip or .eln from the end of the file name
56+
// (?i) removes case sensitivity
57+
.replaceFirst("(?i)" + dot + "zip" + end, "")
58+
.replaceFirst("(?i)" + dot + "eln" + end, "");
59+
if (!innerFolderName.endsWith("/")) {
60+
innerFolderName += "/";
61+
}
62+
}
2863
try (ZipFile zipFile = new ZipFile(destination)) {
29-
saveMetadataJson(crate, zipFile);
30-
saveDataEntities(crate, zipFile);
64+
saveMetadataJson(crate, zipFile, innerFolderName);
65+
saveDataEntities(crate, zipFile, innerFolderName);
66+
savePreview(crate, zipFile, innerFolderName);
3167
}
3268
}
3369

34-
private void saveDataEntities(Crate crate, ZipFile zipFile) throws IOException {
70+
private void saveDataEntities(Crate crate, ZipFile zipFile, String prefix) throws IOException {
3571
for (DataEntity dataEntity : crate.getAllDataEntities()) {
36-
this.saveToZip(dataEntity, zipFile);
72+
this.saveToZip(dataEntity, zipFile, prefix);
3773
}
3874
}
3975

40-
private void saveMetadataJson(Crate crate, ZipFile zipFile) throws IOException {
76+
private void saveMetadataJson(Crate crate, ZipFile zipFile, String prefix) throws IOException {
4177
// write the metadata.json file
4278
ZipParameters zipParameters = new ZipParameters();
43-
zipParameters.setFileNameInZip("ro-crate-metadata.json");
79+
zipParameters.setFileNameInZip(prefix + "ro-crate-metadata.json");
4480
ObjectMapper objectMapper = MyObjectMapper.getMapper();
4581
// we create an JsonNode only to have the file written pretty
4682
JsonNode node = objectMapper.readTree(crate.getJsonMetadata());
@@ -49,25 +85,59 @@ private void saveMetadataJson(Crate crate, ZipFile zipFile) throws IOException {
4985
// write the ro-crate-metadata
5086
zipFile.addStream(inputStream, zipParameters);
5187
}
52-
if (crate.getPreview() != null) {
53-
crate.getPreview().saveAllToZip(zipFile);
88+
}
89+
90+
private void savePreview(Crate crate, ZipFile zipFile, String prefix) throws IOException {
91+
Optional<CratePreview> preview = Optional.ofNullable(crate.getPreview());
92+
if (preview.isEmpty()) {
93+
return;
94+
}
95+
final String ID = UUID.randomUUID().toString();
96+
File tmpPreviewFolder = Path.of("./.tmp/ro-crate-java/writer-zipStrategy/")
97+
.resolve(ID)
98+
.toFile();
99+
FileUtils.forceMkdir(tmpPreviewFolder);
100+
FileUtils.forceDeleteOnExit(tmpPreviewFolder);
101+
102+
preview.get().generate(crate, tmpPreviewFolder);
103+
String[] paths = tmpPreviewFolder.list();
104+
if (paths == null) {
105+
throw new IOException("No files found in temporary folder");
106+
}
107+
for (String path : paths) {
108+
File file = tmpPreviewFolder.toPath().resolve(path).toFile();
109+
if (file.isDirectory()) {
110+
ZipParameters parameters = new ZipParameters();
111+
parameters.setRootFolderNameInZip(prefix + path);
112+
parameters.setIncludeRootFolder(false);
113+
zipFile.addFolder(file, parameters);
114+
} else {
115+
ZipParameters zipParameters = new ZipParameters();
116+
zipParameters.setFileNameInZip(prefix + path);
117+
zipFile.addFile(file, zipParameters);
118+
}
119+
}
120+
try {
121+
FileUtils.forceDelete(tmpPreviewFolder);
122+
} catch (IOException e) {
123+
logger.error("Could not delete temporary preview folder: {}", tmpPreviewFolder);
54124
}
55125
}
56126

57-
private void saveToZip(DataEntity entity, ZipFile zipFile) throws IOException {
127+
private void saveToZip(DataEntity entity, ZipFile zipFile, String prefix) throws IOException {
58128
if (entity == null || entity.getPath() == null) {
59129
return;
60130
}
61131

62132
boolean isDirectory = entity.getPath().toFile().isDirectory();
63133
if (isDirectory) {
64134
ZipParameters parameters = new ZipParameters();
65-
parameters.setRootFolderNameInZip(entity.getId());
135+
parameters.setRootFolderNameInZip(prefix + entity.getId());
66136
parameters.setIncludeRootFolder(false);
67137
zipFile.addFolder(entity.getPath().toFile(), parameters);
68138
} else {
69139
ZipParameters zipParameters = new ZipParameters();
70-
zipParameters.setFileNameInZip(entity.getId());
140+
zipParameters.setFileNameInZip(prefix + entity.getId());
71141
zipFile.addFile(entity.getPath().toFile(), zipParameters);
72142
}
73143
}

0 commit comments

Comments
 (0)