/**
* Copyright 2010 The University of North Carolina at Chapel Hill
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package unc.lib.cdr.workbench.capture;
import gov.loc.mets.DivType;
import gov.loc.mets.FileType;
import gov.loc.mets.FptrType;
import gov.loc.mets.MetsFactory;
import gov.loc.mets.MetsPackage;
import gov.loc.mets.MetsType;
import gov.loc.mets.util.METSConstants;
import gov.loc.mets.util.METSUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.edit.command.AddCommand;
import unc.lib.cdr.workbench.originals.OriginalFileStore;
import unc.lib.cdr.workbench.project.MetsProjectNature;
import unc.lib.cdr.workbench.stage.StagingJob;
/**
* @author Gregory Jansen
*
*/
public class CaptureJob extends Job {
private IProject project = null;
private List<OriginalFileStore> items;
private Set<String> includedFileExtensions = null;
public Set<String> getIncludedFileExtensions() {
return includedFileExtensions;
}
public void setIncludedFileExtensions(Set<String> includedFileExtensions) {
this.includedFileExtensions = includedFileExtensions;
}
private DivType topDestination = null;
private DivType insertBefore = null;
private MetsProjectNature mpn = null;
private MetsType m = null;
private DivType bag = null;
private IProgressMonitor monitor = null;
private CompoundCommand command = null;
private Map<OriginalFileStore, DivType> localParentDivs = new HashMap<OriginalFileStore, DivType>();
private static Comparator<OriginalFileStore> comparator = new Comparator<OriginalFileStore>() {
@Override
public int compare(OriginalFileStore arg0, OriginalFileStore arg1) {
return Collator.getInstance().compare(arg0.getName(), arg1.getName());
}
};
/**
* @param jobName
* @param items
* List of sibling resources items to capture
* @param destination
* The div within which to insert the list of siblings, if null original parents are found in arrangement
* or captured.
* @param insertBefore
* If not null, captured resources will be inserted before this div
*/
public CaptureJob(String jobName, List<OriginalFileStore> items, DivType destination, DivType insertBefore) {
super(jobName);
this.items = items;
if (items.size() > 0) {
this.project = items.iterator().next().getProject();
}
this.topDestination = destination;
this.insertBefore = insertBefore;
}
/**
* @param jobName
* @param items
* the items to capture or verify
*/
public CaptureJob(String jobName, List<OriginalFileStore> items) {
this(jobName, items, null, null);
}
public class CounterVisitor {
IProgressMonitor monitor = new NullProgressMonitor();
public int count = 0;
public void visit(IFileStore resource) throws CoreException {
count++;
if (resource.fetchInfo().isDirectory()) {
for (IFileStore f : resource.childStores(EFS.NONE, monitor)) {
visit(f);
}
}
}
}
/*
* if parent folders have been arranged, they will stay in their places, reset arrangement is separate! (non-Javadoc)
*
* @seeorg.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime. IProgressMonitor)
*/
@Override
protected IStatus run(IProgressMonitor monitor) {
this.monitor = monitor;
if (this.monitor == null) {
monitor = new NullProgressMonitor();
}
try {
this.items.remove(this.project);
if (this.items.size() == 0) {
return Status.OK_STATUS;
}
// recursively count the items
CounterVisitor v = new CounterVisitor();
for (OriginalFileStore r : items) {
v.visit(r);
}
String commandLabel = "Capturing " + v.count + " resources..";
monitor.beginTask(commandLabel, v.count);
command = new CompoundCommand(commandLabel);
// get METS object
mpn = MetsProjectNature.get(project);
m = mpn.getMets();
bag = METSUtils.findBagDiv(m);
Map<OriginalFileStore, DivType> map = captureSiblingList(items);
if (topDestination == null) {
// create and add relevant parents (EMF Command for top node)
for (OriginalFileStore r : items) {
if (map.containsKey(r)) {
makeOrLinkParent(r, map.get(r));
}
}
} else {
for (OriginalFileStore r : items) {
if (map.containsKey(r)) {
// use EMF command to add item to topDestination
emfAdd(topDestination, map.get(r));
}
}
}
mpn.getCommandStack().execute(command);
mpn.save();
project.getWorkspace().save(true, monitor);
boolean autostage = mpn.getAutomaticStaging();
if (autostage) {
System.out.println("triggering build b/c auto staging says " + autostage);
Job stagingJob = new StagingJob("Staging after capture", project);
stagingJob.setRule(StagingJob.mySchedulingRule);
stagingJob.schedule();
} else {
System.out.println("skipping build b/c auto staging says " + autostage);
}
monitor.done();
return Status.OK_STATUS;
} catch (CoreException e) {
e.printStackTrace();
return e.getStatus();
}
}
/**
* Recursives captures a list of originals, returning a map of the top nodes to their DivTypes. Originals
* already captured are left in place, but their children are captured under them.
*
* @param list
* the original stores to capture
* @param dest
* the parent to add these DivTypes to
* @return a map of captured original stores (at this level) to DivTypes
* @throws CoreException
*/
private Map<OriginalFileStore, DivType> captureSiblingList(List<OriginalFileStore> list) throws CoreException {
if (list.size() == 0)
return Collections.EMPTY_MAP;
Map<OriginalFileStore, DivType> result = new HashMap<OriginalFileStore, DivType>();
Collections.sort(list, comparator);
for (OriginalFileStore r : list) {
// first find or create this Div
DivType d = r.getMetsDivType();
boolean newDiv = false;
if (d == null) {
String ext = "";
String[] parts = r.getName().split(Pattern.quote("."));
if(parts.length > 1) {
ext = parts[parts.length-1];
}
if(r.fetchInfo().isDirectory() || this.includedFileExtensions == null || this.includedFileExtensions.contains(ext)) {
newDiv = true;
d = makeDiv(r);
result.put(r, d);
}
}
IProgressMonitor mon = new NullProgressMonitor();
if (r.fetchInfo().isDirectory()) {
// get children, and capture them
List<OriginalFileStore> children = Arrays.asList((OriginalFileStore[])r.childStores(EFS.NONE, mon));
Map<OriginalFileStore, DivType> map = captureSiblingList(children);
for (OriginalFileStore childRes : children) {
if (map.containsKey(childRes)) { // if we created a new Div
if (!newDiv) {
emfAdd(d, map.get(childRes));
} else {
d.getDiv().add(map.get(childRes));
}
}
}
}
}
return result;
}
private void emfAdd(DivType d, DivType child) {
Command add = AddCommand.create(mpn.getEditingDomain(), d, MetsPackage.eINSTANCE.getDivType_Div(), child);
command.append(add);
}
/**
* Finds and links to existing parent of resource or builds a parent and recurses.
*
* @param original
* @return
* @throws CoreException
*/
private void makeOrLinkParent(OriginalFileStore original, DivType div) throws CoreException {
DivType parent = null;
if (original.getOriginalStub().getStores().contains(original)) {
parent = this.bag;
} else {
parent = ((OriginalFileStore)original.getParent()).getMetsDivType();
}
if (parent != null) {
// attach resource div to found parent
emfAdd(parent, div);
} else { // parent is not in METS doc
if (localParentDivs.containsKey((OriginalFileStore)original.getParent())) {
// parents were already made
localParentDivs.get((OriginalFileStore)original.getParent()).getDiv().add(div);
} else {
// make a parent, attach the child and keep going
parent = makeDiv((OriginalFileStore)original.getParent());
localParentDivs.put((OriginalFileStore)original.getParent(), parent);
parent.getDiv().add(div);
makeOrLinkParent((OriginalFileStore)original.getParent(), parent);
}
}
}
/**
* @param original
*/
private DivType makeDiv(OriginalFileStore original) throws CoreException {
DivType result = MetsFactory.eINSTANCE.createDivType();
UUID uuid = UUID.randomUUID();
List<String> contentIds = new ArrayList<String>();
contentIds.add(original.getWrapped().toURI().toASCIIString());
contentIds.add("info:fedora/uuid:" + uuid.toString());
result.setCONTENTIDS(contentIds);
result.setID(original.getDivID());
String name = original.getWrapped().getName();
if(name == null || name.trim().isEmpty()) {
name = "(disc root)";
}
result.setLABEL1(name);
if (original.fetchInfo().isDirectory()) {
result.setTYPE(METSConstants.Div_Folder);
} else {
result.setTYPE(METSConstants.Div_File);
// calc size and checksum.
IFileInfo sourceFileInfo = original.fetchInfo();
long size = sourceFileInfo.getLength();
//long lastModifiedTimestamp = sourceFileInfo.getLastModified();
Long createTimestamp = null;
Path path = Paths.get(original.getWrapped().toURI());
try {
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
FileTime create = attr.creationTime();
if(create != null) createTimestamp = new Long(create.toMillis());
} catch(IOException ignored) {}
// find File section (for previously captured) or make one
FileType ft = original.getMetsFileType();
if (ft == null) {
ft = METSUtils.addFile(m, original.getWrapped().toURI(), original.getFileID(), size, createTimestamp, null);
}
FptrType fptr = MetsFactory.eINSTANCE.createFptrType();
fptr.setFILEID(ft.getID());
result.getFptr().add(fptr);
}
return result;
}
}