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 = LogManager.getLogger(AbstractImageRenamer.class);
55  
56      /**
57       * The currently selected directory to work on.
58       */
59      private File currentDirectory = null;
60      /**
61       * The list of all relevant files in the current directory.
62       */
63      private List<File> imageList = null;
64  
65      /**
66       * Progress bar for visual feedback of what's going on.
67       */
68      private ProgressBar progressBar = null;
69      /**
70       * Number of files that need processing.
71       */
72      private int amountOfFiles = 0;
73  
74      /**
75       * Starts image processing on the given directory if it contains
76       * relevant images. The strategy of renaming is defined by
77       * subclasses implementation of @see #renameImage(File).
78       *
79       * @param directory Name of directory to work on.
80       * @throws InvalidDirectoryException If there's a problem with
81       *                                   the selected directory.
82       * @throws NoFilesFoundException     If the selected directory is empty.
83       */
84      AbstractImageRenamer(final String directory) throws InvalidDirectoryException, NoFilesFoundException {
85  
86          if (directory == null) {
87              throw new InvalidDirectoryException("null is not a directory");
88          }
89  
90          this.currentDirectory = new File(directory);
91          if (!this.currentDirectory.isDirectory()) {
92              throw new InvalidDirectoryException(this.currentDirectory);
93          }
94  
95          // retrieve relevant images in directory
96          File[] files = this.currentDirectory.listFiles(new ImageFilenameFilter());
97          if (files == null || files.length == 0) {
98              throw new NoFilesFoundException(this.currentDirectory);
99          }
100         this.imageList = Lists.newArrayList(files);
101         this.amountOfFiles = this.imageList.size();
102     }
103 
104     /**
105      * Performs the actual/technical renaming.
106      */
107     private void renameFiles() {
108         LOG.info("Starting to rename {} files.", this.amountOfFiles);
109 
110         Consumer<File> consumer = file -> {
111             // extract EXIF data and fetch target filename
112             String targetFilename = renameImage(file);
113 
114             // update progress bar (names have a different length)
115             progressBar.setProgress();
116             progressBar.setText(file.getName());
117             progressBar.updateUI();
118 
119             // TODO add second progressbar or counter for errors
120             try {
121                 Files.move(file.toPath(), new File(file.getParent(), targetFilename).toPath());
122             } catch (IOException e) {
123                 LOG.error("Unable to rename '{}' to '{}'", file.getName(), targetFilename);
124             }
125         };
126         Predicate<File> fileOnly = file -> file != null && file.isFile();
127 
128         this.imageList.parallelStream().filter(fileOnly).forEach(consumer);
129     }
130 
131     /**
132      * This method provides a strategy to rename image files when
133      * iterating over a list of files.
134      * It is called during image processing.
135      *
136      * @param imageFile Filename to renameFiles according to the subclass
137      *                  implementation.
138      * @return New filename for the given file.
139      */
140     protected abstract String renameImage(File imageFile);
141 
142     /**
143      * Performs the renaming and updates the UI. All error handling is done in
144      * other methods.
145      *
146      * @see #renameFiles()
147      */
148     public final void run() {
149         this.progressBar = new ProgressBar(this.amountOfFiles);
150 
151         try {
152             renameFiles();
153         } catch (Exception e) {
154             JOptionPane.showMessageDialog(null, getParameterizedBundleString("fotorenamer.ui.rename.error", MoreObjects.firstNonNull(e.getMessage(), e.getClass().getSimpleName())), getBundleString("fotorenamer.ui.rename.error.title"), JOptionPane.ERROR_MESSAGE);
155 
156             this.amountOfFiles = 0;
157         } finally {
158             this.progressBar.dispose();
159         }
160 
161         // show UI-notification
162         StringBuilder notification = new StringBuilder();
163         switch (this.amountOfFiles) {
164             case 0:
165                 notification.append(getParameterizedBundleString("fotorenamer.ui.rename.success.message.none", this.currentDirectory.getName()));
166                 break;
167             case 1:
168                 notification.append(getParameterizedBundleString("fotorenamer.ui.rename.success.message.one", this.currentDirectory.getName()));
169                 break;
170             default:
171                 notification.append(getParameterizedBundleString("fotorenamer.ui.rename.success.message", this.amountOfFiles, this.currentDirectory.getName()));
172                 break;
173         }
174 
175         notification.append("\n\n");
176         JOptionPane.showMessageDialog(null, notification.toString(), getBundleString("fotorenamer.ui.rename.success.title"), JOptionPane.INFORMATION_MESSAGE);
177     }
178 }