Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: dewarim/cinnamon-db
base: 59d2fbfc79
...
head fork: dewarim/cinnamon-db
compare: 57d5f55668
Checking mergeability… Don't worry, you can still create the pull request.
  • 6 commits
  • 8 files changed
  • 0 commit comments
  • 1 contributor
View
2  CinnamonDbGrailsPlugin.groovy
@@ -3,7 +3,7 @@ class CinnamonDbGrailsPlugin {
// def packaging = "binary"
def groupId = 'cinnamon'
// the plugin version
- def version = "0.1.68"
+ def version = "0.1.81"
// the version or versions of Grails the plugin is designed for
def grailsVersion = "2.0 > *"
// the other plugins this plugin depends on
View
9 docs/changes.txt
@@ -35,3 +35,12 @@ COPY metaset_types (id, config, description, name, obj_version) FROM stdin WITH
7,<metaset />,tika,tika,0
8,<metaset />,translation_folder,translation_folder,0
\.
+
+alter table folder_types add column config character varying(10241024) default '<config />' NOT NULL;
+alter table objtypes add column config character varying(10241024) default '<config />' NOT NULL;
+alter table objects alter column version set default '1';
+alter table objects rename column version to cmn_version;
+alter table objects rename obj_version to version;
+alter table objects rename contentsize to content_size;
+alter table objects rename contentpath to content_path;
+
View
118 grails-app/domain/cinnamon/Folder.groovy
@@ -16,6 +16,12 @@ import org.dom4j.Node
import cinnamon.interfaces.IMetasetJoin
import org.slf4j.LoggerFactory
import org.slf4j.Logger
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.ArchiveStreamFactory
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.utils.IOUtils
+import cinnamon.utils.ZippedFolder
+import cinnamon.global.ConfThreadLocal
class Folder implements Ownable, Indexable, XmlConvertable, Serializable, IMetasetOwner {
@@ -407,10 +413,10 @@ class Folder implements Ownable, Indexable, XmlConvertable, Serializable, IMetas
* select only the newest versions of the objects it finds.
* @param folder the folder whose content you need.
* @param recursive if true, descend into sub folders recursively and get their content, too.
- * @param latestHead if true, return objects which have latestHead=true. If both latestHead and latestBranch are null,
+ * @param latestHead if != null, return objects which have latestHead=true. If both latestHead and latestBranch are null,
* return all objects. If latestHead and latestBranch are both set, return objects which satisfy one
* of the criteria (where latestHead _or_ latestBranch matches the parameter).
- * @param latestBranch if true, return objects which have latestBranch=true. If both latestHead and latestBranch are null,
+ * @param latestBranch if != null, return objects which have latestBranch=true. If both latestHead and latestBranch are null,
* return all objects. If latestHead and latestBranch are both set, return objects which satisfy one
* of the criteria (where latestHead _or_ latestBranch matches the parameter).
* @return a List of the objects found in this folder, as filtered by the parameters set.
@@ -418,28 +424,30 @@ class Folder implements Ownable, Indexable, XmlConvertable, Serializable, IMetas
public List<ObjectSystemData> fetchFolderContent(Boolean recursive, Boolean latestHead, Boolean latestBranch) {
def osds
if (latestHead != null && latestBranch != null) {
- osds = ObjectSystemData.findAll("select o from ObjectSystemData o where o.parent=:parent and (o.latestHead=:latestHead or o.latestBranch=:latestBranch)",
+ osds = ObjectSystemData.findAll("from ObjectSystemData o where o.parent=:parent and (o.latestHead=:latestHead or o.latestBranch=:latestBranch)",
[parent: this, latestHead: true, latestBranch: true])
}
else if (latestHead != null) {
- osds = ObjectSystemData.findAll("select o from ObjectSystemData o where o.parent=:parent and o.latestHead=:latestHead",
+ osds = ObjectSystemData.findAll("from ObjectSystemData o where o.parent=:parent and o.latestHead=:latestHead",
[parent: this, latestHead: true]
)
}
else if (latestBranch != null) {
- osds = ObjectSystemData.findAll("select o from ObjectSystemData o where o.parent=:parent and o.latestBranch=:latestBranch",
+ osds = ObjectSystemData.findAll("from ObjectSystemData o where o.parent=:parent and o.latestBranch=:latestBranch",
[parent: this, latestBranch: true]
)
}
else {
osds = ObjectSystemData.findAllByParent(this)
}
- if (recursive) {
+ if (recursive) {
List<Folder> subFolders = getSubfolders();
for (Folder f : subFolders) {
+ log.debug("recurse into: ${f.name}")
osds.addAll(f.fetchFolderContent(true, latestHead, latestBranch));
}
}
+ log.debug("folder content found: ${osds.size()}")
return osds;
}
@@ -579,4 +587,102 @@ class Folder implements Ownable, Indexable, XmlConvertable, Serializable, IMetas
return false;
}
+ /**
+ * Create a zipped folder containing those OSDs and subfolders (recursively) which the
+ * validator allows. <br/>
+ * Zip file encoding compatibility is difficult to achieve.<br/>
+ * Using Cp437 as encoding will generate zip archives which can be unpacked with MS Windows XP
+ * system utilities and also with the Linux unzip tool v6.0 (although the unzip tool will list them
+ * as corrupted filenames with "?" in place for the special characters, it should unpack them
+ * correctly). In tests, 7zip was unable to unpack those archives without messing up the filenames
+ * - it requires UTF8 as encoding, as far as I can tell.<br/>
+ * see: http://commons.apache.org/compress/zip.html#encoding<br/>
+ * to manually test this, use: https://github.com/dewarim/GrailsBasedTesting
+ * @param latestHead if set to true, only add objects with latestHead=true, if set to false include only
+ * objects with latestHead=false, if set to null: include everything regardless of
+ * latestHead status.
+ * @param latestBranch if set to true, only add objects with latestBranch=true, if set to false include only
+ * objects with latestBranch=false, if set to null: include everything regardless of
+ * latestBranch status.
+ * @param validator a Validator object which should be configured for the current user to check if access
+ * to objects and folders inside the given folder is allowed. The content of this folder
+ * will be filtered before it is added to the archive.
+ * @param repositoryName the repository (database name) where the data is stored, used to
+ * compute the file system path.
+ * @return the zip archive of the given folder
+ */
+ public ZippedFolder createZippedFolder(Boolean latestHead, Boolean latestBranch,
+ Validator validator, String repositoryName) {
+ final File sysTempDir = new File(System.getProperty("java.io.tmpdir"));
+ File tempFolder = new File(sysTempDir, UUID.randomUUID().toString());
+ if (!tempFolder.mkdirs()) {
+ throw new CinnamonException(("error.create.tempFolder.fail"));
+ }
+
+ List<Folder> folders = new ArrayList<Folder>();
+ folders.add(this);
+ folders.addAll(fetchSubfolders(true));
+ folders = validator.filterUnbrowsableFolders(folders);
+ log.debug("# of folders found: " + folders.size());
+ // create zip archive:
+ ZippedFolder zippedFolder;
+ try {
+ File zipFile = File.createTempFile("cinnamonArchive", "zip");
+ zippedFolder = new ZippedFolder(zipFile, this);
+
+ final OutputStream out = new FileOutputStream(zipFile);
+ ZipArchiveOutputStream zos = (ZipArchiveOutputStream) new ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, out);
+ String encoding = ConfThreadLocal.getConf().getField("zipFileEncoding", "Cp437");
+
+ log.debug("current file.encoding: " + System.getProperty("file.encoding"));
+ log.debug("current Encoding for ZipArchive: " + zos.getEncoding() + "; will now set: " + encoding);
+ zos.setEncoding(encoding);
+ zos.setFallbackToUTF8(true);
+ zos.setCreateUnicodeExtraFields(ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS);
+
+ for (Folder folder : folders) {
+
+ String path = folder.fetchPath().replace(fetchPath(), name); // do not include the parent folders up to root.
+ log.debug("zipFolderPath: " + path);
+ File currentFolder = new File(tempFolder, path);
+ if (!currentFolder.mkdirs()) {
+ // log.warn("failed to create folder for: "+currentFolder.getAbsolutePath());
+ }
+ List<ObjectSystemData> osds = validator.filterUnbrowsableObjects(
+ folder.fetchFolderContent(false, latestHead, latestBranch));
+ log.debug("objects found (filtered): ${osds.size()}")
+ if (osds.size() > 0) {
+ zippedFolder.addToFolders(folder); // do not add empty folders as they are excluded automatically.
+ }
+ for (ObjectSystemData osd : osds) {
+ if (osd.contentSize == null) {
+ log.debug("osd ${osd.id} ${osd.name} is empty - skip.")
+ continue;
+ }
+ zippedFolder.addToObjects(osd);
+ File outFile = osd.createFilenameFromName(currentFolder);
+ // the name in the archive should be the path without the temp folder part prepended.
+ String zipEntryPath = outFile.getAbsolutePath().replace(tempFolder.getAbsolutePath(), "");
+ if (zipEntryPath.startsWith(File.separator)) {
+ zipEntryPath = zipEntryPath.substring(1);
+ }
+ log.debug("zipEntryPath: " + zipEntryPath);
+
+ zipEntryPath = zipEntryPath.replaceAll("\\\\", "/");
+ zos.putArchiveEntry(new ZipArchiveEntry(zipEntryPath));
+ String contentFileName = osd.getFullContentPath(repositoryName)
+ log.debug("copy data from ${contentFileName} into archive.")
+ IOUtils.copy(new FileInputStream(contentFileName), zos);
+ zos.closeArchiveEntry();
+ }
+ }
+ zos.close();
+ } catch (Exception e) {
+ log.debug("Failed to create zipFolder:", e);
+ throw new CinnamonException("error.zipFolder.fail", e);
+ }
+ return zippedFolder;
+ }
+
+
}
View
2  grails-app/domain/cinnamon/FolderType.groovy
@@ -10,6 +10,7 @@ class FolderType {
static constraints = {
description( size: 0..Constants.DESCRIPTION_SIZE, blank: true)
name(size: 1..Constants.NAME_LENGTH, blank: false, unique: true)
+ config size: 1..Constants.METADATA_SIZE, blank: false
}
static mapping = {
@@ -20,6 +21,7 @@ class FolderType {
String name
String description
+ String config = '<meta />'
/**
View
29 grails-app/domain/cinnamon/ObjectSystemData.groovy
@@ -42,13 +42,13 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
appName(size: 0..255, blank: true)
metadata(size: 1..Constants.METADATA_SIZE)
procstate(size: 0..128, blank: true)
- version(size: 1..128)
+ cmnVersion(size: 1..128)
state(nullable: true)
}
static mapping = {
table('objects')
- version 'obj_version'
+// version 'obj_version'
appName column: 'appname'
}
@@ -79,7 +79,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
String procstate = ''
Boolean latestHead
Boolean latestBranch = true
- String version = '1'
+ String cmnVersion = '1'
LifeCycleState state
Set<OsdMetaset> metasets = []
@@ -157,9 +157,9 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
}
log.debug("set version label");
- version = createNewVersionLabel();
- log.debug("new version: " + version);
- latestHead = !version.contains(".");
+ cmnVersion = createNewVersionLabel();
+ log.debug("new version: " + cmnVersion);
+ latestHead = !cmnVersion.contains(".");
log.debug("set root");
if (predecessor == null) {
@@ -309,7 +309,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
predecessor = null;
procstate = that.getProcstate();
type = that.getType();
- version = "0";
+ cmnVersion = "0";
if (that.getState() != null) {
state = that.getState().getLifeCycleStateForCopy();
@@ -346,7 +346,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
twin.setProcstate(procstate)
twin.setRoot(root)
twin.setType(type)
- twin.setVersion(version)
+ twin.setCmnVersion(cmnVersion)
if (state != null) {
twin.setState(state.lifeCycleStateForCopy);
}
@@ -429,7 +429,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
Element data = DocumentHelper.createElement("object");
data.addElement("id").addText(String.valueOf(getId()));
data.addElement("name").addText(getName());
- data.addElement("version").addText(getVersion());
+ data.addElement("version").addText(getCmnVersion());
data.addElement("created").addText(ParamParser.dateToIsoString(getCreated()));
data.addElement("modified").addText(ParamParser.dateToIsoString(getModified()));
data.addElement("procstate").addText(getProcstate());
@@ -593,7 +593,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
return "1";
}
- String predecessorVersion = getPredecessor().getVersion();
+ String predecessorVersion = getPredecessor().getCmnVersion();
String[] branches = predecessorVersion.split("\\.");
String lastSegment = branches[branches.length - 1];
String[] lastBranch = lastSegment.split("-");
@@ -609,7 +609,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
else {
lastDescendant = versions.get(0);
}
- lastDescendantVersion = lastDescendant.getVersion();
+ lastDescendantVersion = lastDescendant.getCmnVersion();
} catch (NoResultException e) {
// no object with same predecessor
log.debug("no result for last-descendant-query");
@@ -667,7 +667,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
name = name.replaceAll("[^\\w]", "_");
File file = new File(path, name + extension);
if (file.exists()) {
- name = name + "_" + getVersion();
+ name = name + "_" + getCmnVersion();
file = new File(path, name + extension);
if (file.exists()) {
name = name + "_" + getId();
@@ -728,7 +728,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
if (root != that.root) return false
if (state != that.state) return false
if (type != that.type) return false
- if (version != that.version) return false
+ if (cmnVersion != that.cmnVersion) return false
return true
}
@@ -758,7 +758,7 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
result = 31 * result + (procstate != null ? procstate.hashCode() : 0)
result = 31 * result + (latestHead != null ? latestHead.hashCode() : 0)
result = 31 * result + (latestBranch != null ? latestBranch.hashCode() : 0)
- result = 31 * result + (version != null ? version.hashCode() : 0)
+ result = 31 * result + (cmnVersion != null ? cmnVersion.hashCode() : 0)
result = 31 * result + (state != null ? state.hashCode() : 0)
return result
}
@@ -947,4 +947,5 @@ class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertab
log.debug("Found formatList: " + formatListNode.getText());
return formatListNode.getText();
}
+
}
View
2  grails-app/domain/cinnamon/ObjectType.groovy
@@ -11,6 +11,7 @@ class ObjectType implements Serializable {
static constraints = {
description( size: 0..Constants.DESCRIPTION_SIZE, blank: true)
name(size: 1..Constants.NAME_LENGTH, blank: false, unique: true)
+ config size: 1..Constants.METADATA_SIZE, blank: false
}
static mapping = {
@@ -21,6 +22,7 @@ class ObjectType implements Serializable {
String name
String description
+ String config = '<meta />'
/**
* Add the ObjectType's fields as child-elements to a new element with the given name.
View
127 src/groovy/cinnamon/utils/ZippedFolder.groovy
@@ -0,0 +1,127 @@
+package cinnamon.utils
+
+import org.dom4j.Document
+import org.dom4j.Element
+import org.dom4j.Node
+import cinnamon.Folder
+import cinnamon.ObjectSystemData
+
+/**
+ * Store information about the files contained in a zipped folder.
+ */
+class ZippedFolder {
+
+ Folder rootFolder;
+ File zipFile;
+ Set<Folder> folders = new HashSet<Folder>();
+ Set<ObjectSystemData> osds =new HashSet<ObjectSystemData>();
+
+ public ZippedFolder() {
+ }
+
+ public ZippedFolder(Folder rootFolder) {
+ this.rootFolder = rootFolder;
+ addToFolders(rootFolder);
+ }
+
+ public ZippedFolder(File zipFile) {
+ this.zipFile = zipFile;
+ }
+
+ public ZippedFolder(File zipFile, Folder rootFolder) {
+ this.rootFolder = rootFolder;
+ addToFolders(rootFolder);
+ this.zipFile = zipFile;
+ }
+
+ public Boolean addToFolders(Folder folder){
+ return folders.add(folder);
+ }
+
+ public Boolean addToObjects(ObjectSystemData osd){
+ return osds.add(osd);
+ }
+
+ public File getZipFile() {
+ return zipFile;
+ }
+
+ public void setZipFile(File zipFile) {
+ this.zipFile = zipFile;
+ }
+
+ public Set<Folder> getFolders() {
+ return folders;
+ }
+
+ public void setFolders(Set<Folder> folders) {
+ this.folders = folders;
+ }
+
+ public Set<ObjectSystemData> getOsds() {
+ return osds;
+ }
+
+ public void setOsds(Set<ObjectSystemData> osds) {
+ this.osds = osds;
+ }
+
+ /**
+ * Generate a detailed content list of this zip file as a XML-String.
+ * @return a String which contains the serialized OSDs which were added to this zip file.
+ */
+ public String generateContentList(){
+ return generateContentListAsDocument().asXML();
+ }
+
+ /**
+ * Generate a detailed content list of this document as an XML document.
+ * @return a dom4j-Document which contains the XML serializations of the OSDs inside this zip file. The format is:
+ * <pre>
+ * {@code
+ * <zipContent>
+ * <object><id>1</id>...</object>
+ * <object><id>2</id>...</object>
+ * </zipContent>
+ * }
+ * </pre>
+ */
+ public Document generateContentListAsDocument(){
+ Document doc = ParamParser.parseXmlToDocument("<zipContent/>");
+ Element root = doc.getRootElement();
+ for(ObjectSystemData osd : osds){
+ osd.toXmlElement(root);
+ }
+ return doc;
+ }
+
+ /**
+ * Add the XML content list of this zip file to an OSD. This is added as a metaset with type "zipContent".
+ * If a metaset of this type already exists, it will be replaced. The metaset contains a list of XML-serialized
+ * OSDs (which represents the OSD's metadata, not the content).
+ * <pre>
+ * {@code
+ * <metaset type="zipContent">
+ * <object><id>1</id>...</object>
+ * ...
+ * <object><id>999</id>...</object>
+ * </metaset>
+ * }
+ * </pre>
+ * @param osd the osd which will store the new metaset.
+ */
+ public void addContentListToMetadata(ObjectSystemData osd){
+ Document meta = ParamParser.parseXmlToDocument(osd.getMetadata());
+ Node zipContentNode = meta.selectSingleNode("/metaset[@type='zipContent']");
+ Element zipMetaset;
+ if(zipContentNode != null){
+ zipContentNode.detach(); // remove old contentSet
+ }
+ zipMetaset = meta.getRootElement().addElement("metaset");
+ zipMetaset.addAttribute("type","zipContent");
+ for(ObjectSystemData contentOsd : osds){
+ contentOsd.toXmlElement(zipMetaset);
+ }
+ osd.setMetadata(meta.asXML());
+ }
+}
View
310 src/java/cinnamon/ContentStore.java
@@ -1,156 +1,154 @@
-package cinnamon;
-
-import cinnamon.global.Conf;
-import cinnamon.global.ConfThreadLocal;
-import cinnamon.utils.FileKeeper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.*;
-import java.util.UUID;
-
-public class ContentStore {
-
- static final String sep = File.separator;
-
- /**
- * Create a 3-tier folder hierarchy named after the first 6 letters of the file's UUID
- * in the repository's data folder,
- * and copy the file into it. See upload method for more details.
- *
- * @param sourcePath path to the file to copy
- * @param repository the repository to which the file belongs
- * @return String
- * @throws java.io.IOException if something IO-related goes wrong (like disk full)
- */
- // TODO: find a better method name
- public static String copyToContentStore(String sourcePath, String repository) throws IOException {
- Logger log = LoggerFactory.getLogger(ContentStore.class);
- Conf conf = ConfThreadLocal.getConf();
-
- File source = new File(sourcePath);
- String targetName = UUID.randomUUID().toString(); // GUID
- String subfolderName = getSubFolderName(targetName);
-
- // TODO: refactor identical code between upload&copy-tocontentstore.
-
- String subfolderPath = conf.getDataRoot() + repository + sep
- + subfolderName;
- File subfolder = new File(subfolderPath);
- boolean result = subfolder.mkdirs();
- log.debug("Result of mkdir: " + result);
- if (!result && !subfolder.isDirectory()) {
- throw new IOException("Could not create directory " + subfolderPath);
- }
-
- String targetPath = subfolderPath + sep + targetName;
- copyFile(source, new File(targetPath));
- return subfolderName + sep + targetName;
- }
-
- public static void copyFile(File src, File dst) throws IOException {
- InputStream inStream = new FileInputStream(src);
- copyStreamToFile(inStream, dst);
- inStream.close();
- }
-
- public static void copyStreamToFile(InputStream inStream, File target) throws IOException {
- OutputStream out = new FileOutputStream(target);
-
- // Transfer bytes from in to out
- byte[] buf = new byte[1024];
- int len;
- while ((len = inStream.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
- out.close();
- }
-
- private static String getSubFolderName(String f) {
- return f.substring(0, 2) + sep
- + f.substring(2, 4) + sep + f.substring(4, 6);
- }
-
- /**
- * Put a file into the content store folder. Folder structure is:
- * $cinnamon-data-dir / $repository name / 3 folders / UUID-filename.
- * The 3 folders consist of 2 hexadecimal characters each, taken from
- * the beginning of the filename of the stored file.
- * Example:
- * /home/cinnamon/cinnamon-data/cmn_test/c8/72/4d/c8724d35-997f-4cbd-9643-1721a4443956
- *
- * @param file the uploaded file
- * @param repository the repository to which the file belongs
- * @return String
- * @throws java.io.IOException if something unexpected and fatal happens (like missing write
- * permissions)
- */
- public static String upload(UploadedFile file,
- String repository) throws IOException {
- Logger log = LoggerFactory.getLogger(ContentStore.class);
- Conf conf = ConfThreadLocal.getConf();
-
- File f = new File(file.getFileBufferPath());
-
- String subfolderName = getSubFolderName(f.getName());
-
- String subfolderPath = conf.getDataRoot() + repository
- + sep + subfolderName;
- File subfolder = new File(subfolderPath);
-
- boolean result = subfolder.mkdirs();
- log.debug("Result of mkdir: " + result);
-
- String contentPath = subfolderPath + sep + f.getName();
-
- result = f.renameTo(new File(contentPath));
- log.debug("Result of renameTo(" + contentPath + ": "
- + result);
- if (!result && !subfolder.isDirectory()) {
- throw new IOException("Could not rename uploaded file " + contentPath);
- } else {
- return subfolderName + sep + f.getName();
- }
- }
-
- /**
- * Delete a file corresponding to an OSD object from the file system (and its
- * containing folder if it will empty after deletion)
- *
- * @param osd the OSD whose content is going to be deleted.
- */
- public static void deleteObjectFile(ObjectSystemData osd) {
- if (osd.getContentPath() == null) {
- return;
- }
- File contentFile = new File(osd.getContentPath());
- if (contentFile.exists()) { // cannot delete non-existent file
- FileKeeper.getInstance().addFileForDeletion(contentFile);
- }
- }
-
- /**
- * Create a byte array of the same size as the given OSD's content and set the
- * HttpServletResponse to binary/octed-stream for a binary attachment with name
- * = osd.getName().
- *
- * @param res HttpServletResponse
- * @param osd the object which is the source for the file content.
- * @return an empty byte-array of size=osd.content.size
- */
- public static byte[] getObjectFileContent(HttpServletResponse res,
- ObjectSystemData osd) {
- Long contentSize = osd.getContentSize();
- int fileSize = contentSize.intValue();
- // TODO: buffered streaming
- byte b[] = new byte[fileSize];
- res.setContentLength(fileSize);
- res.setContentType("binary/octet-stream"); // TODO: softcode with
- // detailed type info
- res.setHeader("Content-Disposition", "attachment; filename="
- + osd.getName());
- return b;
- }
-
-}
+package cinnamon;
+
+import cinnamon.global.Conf;
+import cinnamon.global.ConfThreadLocal;
+import cinnamon.utils.FileKeeper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.UUID;
+
+public class ContentStore {
+
+ static final String sep = File.separator;
+
+ /**
+ * Create a 3-tier folder hierarchy named after the first 6 letters of the file's UUID
+ * in the repository's data folder,
+ * and copy the file into it. See upload method for more details.
+ *
+ * @param sourcePath path to the file to copy
+ * @param repository the repository to which the file belongs
+ * @return String
+ * @throws java.io.IOException if something IO-related goes wrong (like disk full)
+ */
+ // TODO: find a better method name
+ public static String copyToContentStore(String sourcePath, String repository) throws IOException {
+ Logger log = LoggerFactory.getLogger(ContentStore.class);
+ Conf conf = ConfThreadLocal.getConf();
+
+ File source = new File(sourcePath);
+ String targetName = UUID.randomUUID().toString(); // GUID
+ String subfolderName = getSubFolderName(targetName);
+
+ // TODO: refactor identical code between upload&copy-tocontentstore.
+
+ String subfolderPath = conf.getDataRoot() + repository + sep
+ + subfolderName;
+ File subfolder = new File(subfolderPath);
+ boolean result = subfolder.mkdirs();
+ log.debug("Result of mkdir: " + result);
+ if (!result && !subfolder.isDirectory()) {
+ throw new IOException("Could not create directory " + subfolderPath);
+ }
+
+ String targetPath = subfolderPath + sep + targetName;
+ copyFile(source, new File(targetPath));
+ return subfolderName + sep + targetName;
+ }
+
+ public static void copyFile(File src, File dst) throws IOException {
+ InputStream inStream = new FileInputStream(src);
+ copyStreamToFile(inStream, dst);
+ inStream.close();
+ }
+
+ public static void copyStreamToFile(InputStream inStream, File target) throws IOException {
+ OutputStream out = new FileOutputStream(target);
+
+ // Transfer bytes from in to out
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = inStream.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ out.close();
+ }
+
+ private static String getSubFolderName(String f) {
+ return f.substring(0, 2) + sep
+ + f.substring(2, 4) + sep + f.substring(4, 6);
+ }
+
+ /**
+ * Put a file into the content store folder. Folder structure is:
+ * $cinnamon-data-dir / $repository name / 3 folders / UUID-filename.
+ * The 3 folders consist of 2 hexadecimal characters each, taken from
+ * the beginning of the filename of the stored file.
+ * Example:
+ * /home/cinnamon/cinnamon-data/cmn_test/c8/72/4d/c8724d35-997f-4cbd-9643-1721a4443956
+ *
+ * @param file the uploaded file
+ * @param repository the repository to which the file belongs
+ * @return String
+ * @throws java.io.IOException if something unexpected and fatal happens (like missing write
+ * permissions)
+ */
+ public static String upload(UploadedFile file,
+ String repository) throws IOException {
+ Logger log = LoggerFactory.getLogger(ContentStore.class);
+ Conf conf = ConfThreadLocal.getConf();
+
+ File f = new File(file.getFileBufferPath());
+ String subfolderName = getSubFolderName(file.getName());
+
+ String subfolderPath = conf.getDataRoot() + repository
+ + sep + subfolderName;
+ File subfolder = new File(subfolderPath);
+
+ boolean result = subfolder.mkdirs();
+ log.debug("Result of mkdir: " + result);
+
+ String contentPath = subfolderPath + sep + f.getName();
+
+ result = f.renameTo(new File(contentPath));
+ log.debug("Result of renameTo(" + contentPath + ": " + result);
+ if (!result && !subfolder.isDirectory()) {
+ throw new IOException("Could not rename uploaded file " + contentPath);
+ } else {
+ return subfolderName + sep + f.getName();
+ }
+ }
+
+ /**
+ * Delete a file corresponding to an OSD object from the file system (and its
+ * containing folder if it will empty after deletion)
+ *
+ * @param osd the OSD whose content is going to be deleted.
+ */
+ public static void deleteObjectFile(ObjectSystemData osd) {
+ if (osd.getContentPath() == null) {
+ return;
+ }
+ File contentFile = new File(osd.getContentPath());
+ if (contentFile.exists()) { // cannot delete non-existent file
+ FileKeeper.getInstance().addFileForDeletion(contentFile);
+ }
+ }
+
+ /**
+ * Create a byte array of the same size as the given OSD's content and set the
+ * HttpServletResponse to binary/octed-stream for a binary attachment with name
+ * = osd.getName().
+ *
+ * @param res HttpServletResponse
+ * @param osd the object which is the source for the file content.
+ * @return an empty byte-array of size=osd.content.size
+ */
+ public static byte[] getObjectFileContent(HttpServletResponse res,
+ ObjectSystemData osd) {
+ Long contentSize = osd.getContentSize();
+ int fileSize = contentSize.intValue();
+ // TODO: buffered streaming
+ byte b[] = new byte[fileSize];
+ res.setContentLength(fileSize);
+ res.setContentType("binary/octet-stream"); // TODO: softcode with
+ // detailed type info
+ res.setHeader("Content-Disposition", "attachment; filename="
+ + osd.getName());
+ return b;
+ }
+
+}

No commit comments for this range

Something went wrong with that request. Please try again.