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.function.Consumer;
34  import java.util.function.Predicate;
35  
36  import static de.aikiit.fotorenamer.util.LocalizationHelper.getBundleString;
37  import static de.aikiit.fotorenamer.util.LocalizationHelper.getParameterizedBundleString;
38  
39  /**
40   * Abstract class that handles image renaming and file handling.
41   * <br>
42   * The onliest abstract method generates a filename from a given file
43   * and should be used to provide different strategies for image renaming.
44   *
45   * @author hirsch
46   * @version 2011-03-22, 11:43
47   */
48  @NoArgsConstructor(access = AccessLevel.PRIVATE)
49  abstract class AbstractImageRenamer implements Runnable {
50  
51      /**
52       * The logger of this class.
53       */
54      private static final Logger LOG =
55              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 int amountOfFiles = 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)
86              throws InvalidDirectoryException, NoFilesFoundException {
87  
88          if (directory == null) {
89              throw new InvalidDirectoryException("null is not a directory");
90          }
91  
92          this.currentDirectory = new File(directory);
93          if (!this.currentDirectory.isDirectory()) {
94              throw new InvalidDirectoryException(this.currentDirectory);
95          }
96  
97          // retrieve relevant images in directory
98          File[] files = this.currentDirectory.listFiles(
99                  new ImageFilenameFilter());
100         if (files == null || files.length == 0) {
101             throw new NoFilesFoundException(this.currentDirectory);
102         }
103         this.imageList = Lists.newArrayList(files);
104         this.amountOfFiles = this.imageList.size();
105     }
106 
107     /**
108      * Performs the actual/technical renaming.
109      */
110     private void renameFiles() {
111         LOG.info("Starting to rename " + this.amountOfFiles + " files.");
112 
113         Consumer<File> consumer = file -> {
114             // extract EXIF data and fetch target filename
115             String targetFilename = renameImage(file);
116 
117             // update progress bar (names have a different length)
118             progressBar.setProgress();
119             progressBar.setText(file.getName());
120             progressBar.updateUI();
121 
122             // TODO add second progressbar or counter for errors
123             try {
124                 Files.move(file.toPath(), new File(file.getParent(),
125                         targetFilename).toPath());
126             } catch (IOException e) {
127                 LOG.error("Unable to rename '"
128                         + file.getName() + "' to '"
129                         + targetFilename + "'");
130             }
131         };
132         Predicate<File> fileOnly = file -> file != null && file.isFile();
133 
134         this.imageList.parallelStream().filter(fileOnly).forEach(consumer);
135     }
136 
137     /**
138      * This method provides a strategy to rename image files when
139      * iterating over a list of files.
140      * It is called during image processing.
141      *
142      * @param imageFile Filename to renameFiles according to the subclass
143      *                  implementation.
144      * @return New filename for the given file.
145      */
146     protected abstract String renameImage(File imageFile);
147 
148     /**
149      * Performs the renaming and updates the UI. All error handling is done in
150      * other methods.
151      *
152      * @see #renameFiles()
153      */
154     public final void run() {
155         String notification;
156         this.progressBar = new ProgressBar(this.amountOfFiles);
157 
158         try {
159             renameFiles();
160         } catch (Exception e) {
161             JOptionPane.showMessageDialog(null,
162                     getParameterizedBundleString("fotorenamer.ui.rename.error", MoreObjects.firstNonNull(e.getMessage(), e.getClass().getSimpleName())),
163                     getBundleString("fotorenamer.ui.rename.error.title"),
164                     JOptionPane.ERROR_MESSAGE);
165 
166             this.amountOfFiles = 0;
167         } finally {
168             this.progressBar.dispose();
169         }
170 
171         // show UI-notification
172         switch (this.amountOfFiles) {
173             case 0:
174                 notification = getParameterizedBundleString("fotorenamer.ui.rename.success.message.none", this.currentDirectory.getName());
175                 break;
176             case 1:
177                 notification = getParameterizedBundleString("fotorenamer.ui.rename.success.message.one", this.currentDirectory.getName());
178                 break;
179             default:
180                 notification = getParameterizedBundleString("fotorenamer.ui.rename.success.message", this.amountOfFiles, this.currentDirectory.getName());
181                 break;
182         }
183 
184         notification += "\n\n";
185         JOptionPane.showMessageDialog(null, notification, getBundleString("fotorenamer.ui.rename.success.title"),
186                 JOptionPane.INFORMATION_MESSAGE);
187     }
188 }