/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community 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://opensource.org/licenses/ecl2.txt * * 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 org.opencastproject.assetmanager.storage.impl.fs; import static com.entwinemedia.fn.data.Opt.none; import static com.entwinemedia.fn.data.Opt.nul; import static com.entwinemedia.fn.data.Opt.some; import static org.apache.commons.io.FilenameUtils.EXTENSION_SEPARATOR; import static org.apache.commons.io.FilenameUtils.getExtension; import static org.apache.commons.lang3.exception.ExceptionUtils.getMessage; import static org.opencastproject.util.FileSupport.link; import static org.opencastproject.util.IoSupport.file; import static org.opencastproject.util.PathSupport.path; import static org.opencastproject.util.data.functions.Strings.trimToNone; import org.opencastproject.assetmanager.impl.VersionImpl; import org.opencastproject.assetmanager.impl.storage.AssetStore; import org.opencastproject.assetmanager.impl.storage.AssetStoreException; import org.opencastproject.assetmanager.impl.storage.DeletionSelector; import org.opencastproject.assetmanager.impl.storage.Source; import org.opencastproject.assetmanager.impl.storage.StoragePath; import org.opencastproject.util.FileSupport; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.data.Option; import org.opencastproject.workspace.api.Workspace; import com.entwinemedia.fn.Fn; import com.entwinemedia.fn.data.Opt; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; public abstract class AbstractFileSystemAssetStore implements AssetStore { /** Log facility */ private static final Logger logger = LoggerFactory.getLogger(AbstractFileSystemAssetStore.class); protected abstract Workspace getWorkspace(); protected abstract String getRootDirectory(); @Override public void put(StoragePath storagePath, Source source) throws AssetStoreException { // Retrieving the file from the workspace has the advantage that in most cases the file already exists in the local // working file repository. In the very few cases where the file is not in the working file repository, // this strategy leads to a minor overhead because the file not only gets downloaded and stored in the file system // but also a hard link needs to be created (or if that's not possible, a copy of the file. final File origin = getFileFromWorkspace(source); final File destination = createFile(storagePath, source); try { mkParent(destination); link(origin, destination); } catch (IOException e) { logger.error("Error while linking/copying file {} to {}: {}", new Object[] { origin, destination, getMessage(e) }); throw new AssetStoreException(e); } } private File getFileFromWorkspace(Source source) { try { return getWorkspace().get(source.getUri()); } catch (NotFoundException e) { logger.error("Source file '{}' does not exist", source.getUri()); throw new AssetStoreException(e); } catch (IOException e) { logger.error("Error while getting file '{}' from workspace: {}", source.getUri(), getMessage(e)); throw new AssetStoreException(e); } } @Override public boolean copy(final StoragePath from, final StoragePath to) throws AssetStoreException { return findStoragePathFile(from).map(new Fn<File, Boolean>() { @Override public Boolean apply(File f) { final File t = createFile(to, f); mkParent(t); logger.debug("Copying {} to {}", f.getAbsolutePath(), t.getAbsolutePath()); try { link(f, t, true); } catch (IOException e) { logger.error("Error copying archive file {} to {}", f, t); throw new AssetStoreException(e); } return true; } }).getOr(false); } @Override public Opt<InputStream> get(final StoragePath path) throws AssetStoreException { return findStoragePathFile(path).map(new Fn<File, InputStream>() { @Override public InputStream apply(File file) { try { return new FileInputStream(file); } catch (FileNotFoundException e) { logger.error("Error getting archive file {}", file); throw new AssetStoreException(e); } } }); } @Override public boolean contains(StoragePath path) throws AssetStoreException { return findStoragePathFile(path).isSome(); } @Override public boolean delete(DeletionSelector sel) throws AssetStoreException { File dir = getDeletionSelectorDir(sel); try { FileUtils.deleteDirectory(dir); // also delete the media package directory if all versions have been deleted FileSupport.deleteHierarchyIfEmpty(file(path(getRootDirectory(), sel.getOrganizationId())), dir.getParentFile()); return true; } catch (IOException e) { logger.error("Error deleting directory from archive {}", dir); throw new AssetStoreException(e); } } /** * Returns the directory file from a deletion selector * * @param sel * the deletion selector * @return the directory file */ private File getDeletionSelectorDir(DeletionSelector sel) { final String basePath = path(getRootDirectory(), sel.getOrganizationId(), sel.getMediaPackageId()); for (VersionImpl v : sel.getVersion()) return file(basePath, v.toString()); return file(basePath); } /** Create all parent directories of a file. */ private void mkParent(File f) { mkDirs(f.getParentFile()); } /** Create this directory and all of its parents. */ protected void mkDirs(File d) { if (d != null && !d.exists() && !d.mkdirs()) { final String msg = "Cannot create directory " + d; logger.error(msg); throw new AssetStoreException(msg); } } /** Return the extension of a file. */ private Opt<String> extension(File f) { return trimToNone(getExtension(f.getAbsolutePath())).toOpt(); } /** Return the extension of a URI, i.e. the extension of its path. */ private Opt<String> extension(URI uri) { try { return trimToNone(getExtension(uri.toURL().getPath())).toOpt(); } catch (MalformedURLException e) { throw new Error(e); } } /** Create a file from a storage path and the extension of file <code>f</code>. */ private File createFile(StoragePath p, File f) { return createFile(p, extension(f)); } /** Create a file from a storage path and the extension of the URI of <code>s</code>. */ private File createFile(StoragePath p, Source s) { return createFile(p, extension(s.getUri())); } /** Create a file from a storage path and an optional extension. */ private File createFile(StoragePath p, Opt<String> extension) { return file( getRootDirectory(), p.getOrganizationId(), p.getMediaPackageId(), p.getVersion().toString(), extension.isSome() ? p.getMediaPackageElementId() + EXTENSION_SEPARATOR + extension.get() : p .getMediaPackageElementId()); } /** * Returns a file {@link Option} from a storage path if one is found or an empty {@link Option} * * @param storagePath * the storage path * @return the file {@link Option} */ private Opt<File> findStoragePathFile(final StoragePath storagePath) { final FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return FilenameUtils.getBaseName(name).equals(storagePath.getMediaPackageElementId()); } }; final File containerDir = createFile(storagePath, Opt.none(String.class)).getParentFile(); return nul(containerDir.listFiles(filter)).bind(new Fn<File[], Opt<File>>() { @Override public Opt<File> apply(File[] files) { switch (files.length) { case 0: return none(); case 1: return some(files[0]); default: throw new AssetStoreException("Storage path " + files[0].getParent() + "contains multiple files with the same element id!: " + storagePath.getMediaPackageElementId()); } } }); } @Override public Option<Long> getUsedSpace() { return Option.some(FileUtils.sizeOfDirectory(new File(getRootDirectory()))); } @Override public Option<Long> getUsableSpace() { return Option.some(new File(getRootDirectory()).getUsableSpace()); } @Override public Option<Long> getTotalSpace() { return Option.some(new File(getRootDirectory()).getTotalSpace()); } }