Java tutorial
/* * Copyright 2018 Patrik Karlstrm. * * 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 se.trixon.filebydate; import com.drew.imaging.ImageMetadataReader; import com.drew.imaging.ImageProcessingException; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; import com.drew.metadata.exif.ExifSubIFDDirectory; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.PathMatcher; import java.nio.file.attribute.BasicFileAttributes; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.ResourceBundle; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import se.trixon.almond.util.Dict; import se.trixon.almond.util.SystemHelper; import se.trixon.almond.util.Xlog; /** * * @author Patrik Karlstrm */ public class Operation { private static final Logger LOGGER = Logger.getLogger(Operation.class.getName()); private final ResourceBundle mBundle; private final List<Exception> mExceptions = new ArrayList<>(); private final List<File> mFiles = new ArrayList<>(); private boolean mInterrupted; private final OperationListener mListener; private final Profile mProfile; public Operation(OperationListener operationListener, Profile profile) { mListener = operationListener; mProfile = profile; mBundle = SystemHelper.getBundle(Operation.class, "Bundle"); } public void start() { long startTime = System.currentTimeMillis(); Date date = new Date(startTime); SimpleDateFormat dateFormat = new SimpleDateFormat(); mListener.onOperationStarted(); mListener.onOperationProcessingStarted(); mListener.onOperationLog(dateFormat.format(date)); mInterrupted = !generateFileList(); String status; if (!mInterrupted && !mFiles.isEmpty()) { mListener.onOperationLog(String.format(mBundle.getString("found_count"), mFiles.size())); mListener.onOperationLog(""); status = Dict.PROCESSING.toString(); mListener.onOperationLog(status); int progress = 0; SimpleDateFormat simpleDateFormat = mProfile.getDateFormat(); for (File sourceFile : mFiles) { try { try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException ex) { mInterrupted = true; break; } String fileDate = simpleDateFormat.format(getDate(sourceFile)); File destDir = new File(mProfile.getDestDir(), fileDate); if (destDir.isFile()) { mListener.onOperationLog(String.format(Dict.Dialog.ERROR_DEST_DIR_IS_FILE.toString(), destDir.getAbsolutePath())); break; } else if (!destDir.exists() && !mProfile.isDryRun()) { FileUtils.forceMkdir(destDir); } String destFilename = sourceFile.getName(); String base = FilenameUtils.getBaseName(destFilename); String ext = FilenameUtils.getExtension(destFilename); NameCase caseBase = mProfile.getCaseBase(); NameCase caseExt = mProfile.getCaseExt(); if (caseBase != NameCase.UNCHANGED || caseExt != NameCase.UNCHANGED) { if (caseBase == NameCase.LOWER) { base = base.toLowerCase(); } else if (caseBase == NameCase.UPPER) { base = base.toUpperCase(); } if (caseExt == NameCase.LOWER) { ext = ext.toLowerCase(); } else if (caseBase == NameCase.UPPER) { ext = ext.toUpperCase(); } if (base.length() == 0) { destFilename = String.format(".%s", ext); } else if (ext.length() == 0) { destFilename = base; } else { destFilename = String.format("%s.%s", base, ext); } } File destFile = new File(destDir, destFilename); String log; if (destFile.exists() && !mProfile.isReplaceExisting()) { log = String.format(Dict.Dialog.ERROR_DEST_FILE_EXISTS.toString(), destFile.getAbsolutePath()); } else { Command command = mProfile.getCommand(); String cmd = command == Command.COPY ? "cp" : "mv"; log = String.format("%s %s %s", cmd, sourceFile.getAbsolutePath(), destFile.toString()); if (destDir.canWrite()) { if (!mProfile.isDryRun()) { if (command == Command.COPY) { FileUtils.copyFile(sourceFile, destFile); } else if (command == Command.MOVE) { if (File.listRoots().length > 1 || destFile.exists()) { FileUtils.copyFile(sourceFile, destFile); FileUtils.deleteQuietly(sourceFile); } else { FileUtils.moveFile(sourceFile, destFile); } } } } else if (!mProfile.isDryRun()) { log = Dict.Dialog.ERROR_DEST_CANT_WRITE.toString(); } } mListener.onOperationLog(getMessage(log)); } catch (IOException | ImageProcessingException | NullPointerException ex) { mListener.onOperationLog(getMessage(ex.getLocalizedMessage())); } mListener.onOperationProgress(++progress, mFiles.size()); } } if (mInterrupted) { status = Dict.TASK_ABORTED.toString(); mListener.onOperationLog("\n" + status); mListener.onOperationInterrupted(); } else { mExceptions.stream().forEach((exception) -> { mListener.onOperationLog(String.format("#%s", exception.getLocalizedMessage())); }); long millis = System.currentTimeMillis() - startTime; long min = TimeUnit.MILLISECONDS.toMinutes(millis); long sec = TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)); status = String.format("%s (%d %s, %d %s)", Dict.TASK_COMPLETED.toString(), min, Dict.TIME_MIN.toString(), sec, Dict.TIME_SEC.toString()); mListener.onOperationFinished(status, mFiles.size()); if (!mProfile.isDryRun()) { mProfile.setLastRun(System.currentTimeMillis()); try { ProfileManager.getInstance().save(); } catch (IOException ex) { LOGGER.log(Level.SEVERE, null, ex); } } } } private boolean generateFileList() { mListener.onOperationLog(""); mListener.onOperationLog(Dict.GENERATING_FILELIST.toString()); PathMatcher pathMatcher = mProfile.getPathMatcher(); EnumSet<FileVisitOption> fileVisitOptions = EnumSet.noneOf(FileVisitOption.class); if (mProfile.isFollowLinks()) { fileVisitOptions = EnumSet.of(FileVisitOption.FOLLOW_LINKS); } File file = mProfile.getSourceDir(); if (file.isDirectory()) { FileVisitor fileVisitor = new FileVisitor(pathMatcher, mFiles, this); try { if (mProfile.isRecursive()) { Files.walkFileTree(file.toPath(), fileVisitOptions, Integer.MAX_VALUE, fileVisitor); } else { Files.walkFileTree(file.toPath(), fileVisitOptions, 1, fileVisitor); } if (fileVisitor.isInterrupted()) { return false; } } catch (IOException ex) { Xlog.e(getClass(), ex.getLocalizedMessage()); } } else if (file.isFile() && pathMatcher.matches(file.toPath().getFileName())) { mFiles.add(file); } if (mFiles.isEmpty()) { mListener.onOperationLog(Dict.FILELIST_EMPTY.toString()); } else { Collections.sort(mFiles); } return true; } private Date getDate(File sourceFile) throws IOException, ImageProcessingException { Date date = new Date(System.currentTimeMillis()); DateSource dateSource = mProfile.getDateSource(); if (dateSource == DateSource.FILE_CREATED) { BasicFileAttributes attr = Files.readAttributes(sourceFile.toPath(), BasicFileAttributes.class); date = new Date(attr.creationTime().toMillis()); } else if (dateSource == DateSource.FILE_MODIFIED) { BasicFileAttributes attr = Files.readAttributes(sourceFile.toPath(), BasicFileAttributes.class); date = new Date(attr.lastModifiedTime().toMillis()); } else if (dateSource == DateSource.EXIF_ORIGINAL) { Metadata metadata; Directory directory = null; try { metadata = ImageMetadataReader.readMetadata(sourceFile); directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); date = directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL); } catch (NullPointerException | ImageProcessingException ex) { String message; if (directory == null) { message = String.format(Dict.Dialog.ERROR_EXIF_NOT_FOUND.toString(), sourceFile.getAbsolutePath()); } else { message = String.format(Dict.Dialog.ERROR_FILE_FORMAT_NOT_SUPPORTED.toString(), sourceFile.getAbsolutePath()); } throw new ImageProcessingException(message); } } return date; } private String getMessage(String message) { if (mProfile.isDryRun()) { message = String.format("dry-run: %s", message); } return StringUtils.defaultString(message, ""); } OperationListener getListener() { return mListener; } public enum Command { COPY, MOVE; @Override public String toString() { return Dict.valueOf(name()).toString(); } } }