View Javadoc
1   /*
2    * Copyright 2011, Aiki IT, FotoRenamer
3    * <p/>
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * <p/>
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * <p/>
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.aikiit.fotorenamer.image;
17  
18  import com.google.common.base.MoreObjects;
19  import com.google.common.collect.Lists;
20  import de.aikiit.fotorenamer.exception.InvalidDirectoryException;
21  import de.aikiit.fotorenamer.exception.NoFilesFoundException;
22  import de.aikiit.fotorenamer.gui.ProgressBar;
23  import lombok.AccessLevel;
24  import lombok.NoArgsConstructor;
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  
28  import javax.swing.*;
29  import java.io.File;
30  import java.io.IOException;
31  import java.nio.file.Files;
32  import java.util.List;
33  import java.util.concurrent.atomic.AtomicInteger;
34  import java.util.function.Consumer;
35  import java.util.function.Predicate;
36  
37  import static de.aikiit.fotorenamer.util.LocalizationHelper.getBundleString;
38  import static de.aikiit.fotorenamer.util.LocalizationHelper.getParameterizedBundleString;
39  
40  /**
41   * Abstract class that handles image renaming and file handling.
42   * <br>
43   * The onliest abstract method generates a filename from a given file
44   * and should be used to provide different strategies for image renaming.
45   *
46   * @author hirsch
47   * @version 2011-03-22, 11:43
48   */
49  @NoArgsConstructor(access = AccessLevel.PRIVATE)
50  abstract class AbstractImageRenamer implements Runnable {
51  
52      /**
53       * The logger of this class.
54       */
55      private static final Logger LOG = LogManager.getLogger(AbstractImageRenamer.class);
56  
57      /**
58       * The currently selected directory to work on.
59       */
60      private File currentDirectory = null;
61      /**
62       * The list of all relevant files in the current directory.
63       */
64      private List<File> imageList = null;
65  
66      /**
67       * Progress bar for visual feedback of what's going on.
68       */
69      private ProgressBar progressBar = null;
70      /**
71       * Number of files that need processing.
72       */
73      private AtomicInteger amountOfFiles = new AtomicInteger(0);
74  
75      /**
76       * Starts image processing on the given directory if it contains
77       * relevant images. The strategy of renaming is defined by
78       * subclasses implementation of @see #renameImage(File).
79       *
80       * @param directory Name of directory to work on.
81       * @throws InvalidDirectoryException If there's a problem with
82       *                                   the selected directory.
83       * @throws NoFilesFoundException     If the selected directory is empty.
84       */
85      AbstractImageRenamer(final String directory) throws InvalidDirectoryException, NoFilesFoundException {
86  
87          if (directory == null) {
88              throw new InvalidDirectoryException("null is not a directory");
89          }
90  
91          this.currentDirectory = new File(directory);
92          if (!this.currentDirectory.isDirectory()) {
93              throw new InvalidDirectoryException(this.currentDirectory);
94          }
95  
96          // retrieve relevant images in directory
97          File[] files = this.currentDirectory.listFiles(new ImageFilenameFilter());
98          if (files == null || files.length == 0) {
99              throw new NoFilesFoundException(this.currentDirectory);
100         }
101         this.imageList = Lists.newArrayList(files);
102         this.amountOfFiles = new AtomicInteger(this.imageList.size());
103     }
104 
105     /**
106      * Performs the actual/technical renaming.
107      */
108     private void renameFiles() {
109         LOG.info("Starting to rename {} files.", this.amountOfFiles);
110 
111         Consumer<File> consumer = file -> {
112             // extract EXIF data and fetch target filename
113             String targetFilename = renameImage(file);
114 
115             // update progress bar (names have a different length)
116             progressBar.setProgress();
117             progressBar.setText(file.getName());
118             progressBar.updateUI();
119 
120             // TODO add second progressbar or counter for errors
121             try {
122                 Files.move(file.toPath(), new File(file.getParent(), targetFilename).toPath());
123             } catch (IOException e) {
124                 LOG.error("Unable to rename '{}' to '{}'", file.getName(), targetFilename);
125             }
126         };
127         Predicate<File> fileOnly = file -> file != null && file.isFile();
128 
129         this.imageList.parallelStream().filter(fileOnly).forEach(consumer);
130     }
131 
132     /**
133      * This method provides a strategy to rename image files when
134      * iterating over a list of files.
135      * It is called during image processing.
136      *
137      * @param imageFile Filename to renameFiles according to the subclass
138      *                  implementation.
139      * @return New filename for the given file.
140      */
141     protected abstract String renameImage(File imageFile);
142 
143     /**
144      * Performs the renaming and updates the UI. All error handling is done in
145      * other methods.
146      *
147      * @see #renameFiles()
148      */
149     public final void run() {
150         this.progressBar = new ProgressBar(this.amountOfFiles.get());
151 
152         try {
153             renameFiles();
154         } catch (Exception e) {
155             JOptionPane.showMessageDialog(null, getParameterizedBundleString("fotorenamer.ui.rename.error", MoreObjects.firstNonNull(e.getMessage(), e.getClass().getSimpleName())), getBundleString("fotorenamer.ui.rename.error.title"), JOptionPane.ERROR_MESSAGE);
156 
157             this.amountOfFiles = new AtomicInteger(0);
158         } finally {
159             this.progressBar.dispose();
160         }
161 
162         // show UI-notification
163         StringBuilder notification = new StringBuilder();
164         switch (this.amountOfFiles.get()) {
165             case 0:
166                 notification.append(getParameterizedBundleString("fotorenamer.ui.rename.success.message.none", this.currentDirectory.getName()));
167                 break;
168             case 1:
169                 notification.append(getParameterizedBundleString("fotorenamer.ui.rename.success.message.one", this.currentDirectory.getName()));
170                 break;
171             default:
172                 notification.append(getParameterizedBundleString("fotorenamer.ui.rename.success.message", this.amountOfFiles, this.currentDirectory.getName()));
173                 break;
174         }
175 
176         notification.append("\n\n");
177         JOptionPane.showMessageDialog(null, notification.toString(), getBundleString("fotorenamer.ui.rename.success.title"), JOptionPane.INFORMATION_MESSAGE);
178     }
179 }