Major cleanup of Utils class.
[trakem2.git] / ini / trakem2 / persistence / FilePathRepair.java
blob298776312cf4753f95b6aad33133cd99efc8bb1b
1 package ini.trakem2.persistence;
3 import java.io.File;
4 import java.util.Hashtable;
5 import java.util.HashSet;
6 import java.util.Vector;
8 import java.awt.Color;
9 import java.awt.Dimension;
10 import java.awt.event.MouseAdapter;
11 import java.awt.event.MouseEvent;
13 import javax.swing.BoxLayout;
14 import javax.swing.table.AbstractTableModel;
15 import javax.swing.JFrame;
16 import javax.swing.JLabel;
17 import javax.swing.JPanel;
18 import javax.swing.border.LineBorder;
19 import javax.swing.JScrollPane;
20 import javax.swing.JTable;
21 import javax.swing.SwingUtilities;
23 import ij.ImagePlus;
24 import ij.gui.GenericDialog;
25 import ij.io.OpenDialog;
27 import ini.trakem2.ControlWindow;
28 import ini.trakem2.Project;
29 import ini.trakem2.display.Displayable;
30 import ini.trakem2.display.Patch;
31 import ini.trakem2.persistence.Loader;
32 import ini.trakem2.utils.Dispatcher;
33 import ini.trakem2.utils.IJError;
34 import ini.trakem2.utils.Utils;
36 /** A class to manage "file not found" problems. */
37 public class FilePathRepair {
39 private final Project project;
40 private final PathTableModel data = new PathTableModel();
41 private final JTable table = new JTable(data);
42 private final JFrame frame;
44 private FilePathRepair(final Project project) {
45 this.project = project;
46 this.frame = ControlWindow.createJFrame("Repair: " + project);
49 private final Runnable makeGUI() {
50 return new Runnable() { public void run() {
51 JScrollPane jsp = new JScrollPane(table);
52 jsp.setPreferredSize(new Dimension(500, 500));
53 table.addMouseListener(listener);
54 JLabel label = new JLabel("Double-click any to repair file path:");
55 JLabel label2 = new JLabel("(Any listed with identical parent folder will be fixed as well.)");
56 JPanel plabel = new JPanel();
57 BoxLayout pbl = new BoxLayout(plabel, BoxLayout.Y_AXIS);
58 plabel.setLayout(pbl);
59 //plabel.setBorder(new LineBorder(Color.black, 1, true));
60 plabel.setMinimumSize(new Dimension(400, 40));
61 plabel.add(label);
62 plabel.add(label2);
63 JPanel all = new JPanel();
64 BoxLayout bl = new BoxLayout(all, BoxLayout.Y_AXIS);
65 all.setLayout(bl);
66 all.add(plabel);
67 all.add(jsp);
68 frame.getContentPane().add(all);
69 frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
70 frame.pack();
71 ij.gui.GUI.center(frame);
72 frame.setVisible(true);
73 }};
76 private static class PathTableModel extends AbstractTableModel {
77 final Vector<Patch> vp = new Vector<Patch>();
78 final Vector<String> vpath = new Vector<String>();
79 final HashSet<Patch> set = new HashSet<Patch>();
80 PathTableModel() {}
81 public final String getColumnName(final int col) {
82 switch (col) {
83 case 0: return "Image";
84 case 1: return "Nonexistent file path";
85 default: return "";
88 public final int getRowCount() { return vp.size(); }
89 public final int getColumnCount() { return 2; }
90 public final Object getValueAt(final int row, final int col) {
91 switch (col) {
92 case 0: return vp.get(row);
93 case 1: return vpath.get(row);
94 default: return null;
97 public final boolean isCellEditable(final int row, final int col) {
98 return false;
100 public final void setValueAt(final Object value, final int row, final int col) {} // ignore
102 synchronized public final void add(final Patch patch) {
103 if (set.contains(patch)) return; // already here
104 vp.add(patch);
105 vpath.add(patch.getImageFilePath());
106 set.add(patch);
109 synchronized public final String remove(final int row) {
110 set.remove(vp.remove(row));
111 return vpath.remove(row);
114 synchronized public final String remove(final Patch p) {
115 final int i = vp.indexOf(p);
116 if (-1 == i) return null;
117 set.remove(vp.remove(i));
118 return vpath.remove(i);
122 // Static part
124 static private final Hashtable<Project,FilePathRepair> projects = new Hashtable<Project,FilePathRepair>();
126 static public void add(final Patch patch) {
127 dispatcher.exec(new Runnable() { public void run() {
128 final Project project = patch.getProject();
129 FilePathRepair fpr = null;
130 synchronized (projects) {
131 fpr = projects.get(project);
132 if (null == fpr) {
133 fpr = new FilePathRepair(project);
134 projects.put(project, fpr);
135 SwingUtilities.invokeLater(fpr.makeGUI());
137 fpr.data.add(patch);
138 if (!fpr.frame.isVisible()) {
139 fpr.frame.setVisible(true);
140 } else {
141 SwingUtilities.invokeLater(new Repainter(fpr));
144 }});
147 static private class Repainter implements Runnable {
148 FilePathRepair fpr;
149 Repainter(final FilePathRepair fpr) { this.fpr = fpr; }
150 public void run() {
151 try {
152 fpr.table.updateUI();
153 fpr.table.repaint();
154 fpr.frame.pack();
155 } catch (Exception e) { IJError.print(e); }
159 static private final Dispatcher dispatcher = new Dispatcher("File path fixer");
161 static private MouseAdapter listener = new MouseAdapter() {
162 public void mousePressed(final MouseEvent me) {
163 final JTable table = (JTable) me.getSource();
164 final PathTableModel data = (PathTableModel) table.getModel();
165 final int row = table.rowAtPoint(me.getPoint());
166 if (-1 == row) return;
168 if (2 == me.getClickCount()) {
169 dispatcher.exec(new Runnable() { public void run() {
170 try {
171 table.setEnabled(false);
172 GenericDialog gd = new GenericDialog("Fix paths");
173 gd.addCheckbox("Fix other listed image files with identical parent directory", true);
174 gd.addCheckbox("Fix all image files in the project with identical parent directory", true);
175 gd.addCheckbox("Update mipmaps for each fixed path", false);
176 gd.showDialog();
177 if (!gd.wasCanceled()) {
178 fixPath(table, data, row, gd.getNextBoolean(), gd.getNextBoolean(), gd.getNextBoolean());
180 } catch (Exception e) {
181 IJError.print(e);
182 } finally {
183 table.setEnabled(true);
185 }});
190 static private void fixPath(final JTable table, final PathTableModel data, final int row, final boolean fix_similar, final boolean fix_all, final boolean update_mipmaps) throws Exception {
191 synchronized (projects) {
192 final Patch patch = data.vp.get(row);
193 if (null == patch) return;
194 final String old_path = patch.getImageFilePath();
195 final File f = new File(old_path);
196 if (f.exists()) {
197 Utils.log("File exists for " + patch + " at " + f.getAbsolutePath() + "\n --> not updating.");
198 data.remove(row);
199 return;
201 // Else, pop up file dialog
202 OpenDialog od = new OpenDialog("Select image file", OpenDialog.getDefaultDirectory(), null);
203 final String dir = od.getDirectory();
204 final String filename = od.getFileName();
205 if (null == dir) return; // dialog was canceled
206 final String path = new StringBuffer(dir).append(filename).toString(); // save concat, no backslash altered
207 if (!fixPatchPath(patch, path, update_mipmaps)) {
208 return;
210 // Remove from table
211 String wrong_parent_path = new File(data.remove(row)).getParent();
212 if (!wrong_parent_path.endsWith(File.separator)) wrong_parent_path = new StringBuffer(wrong_parent_path).append(File.separatorChar).toString(); // backslash-safe
213 String good_parent_path = dir;
214 if (!dir.endsWith(File.separator)) good_parent_path = new StringBuffer(good_parent_path).append(File.separatorChar).toString(); // backslash-safe
216 int n_fixed = 1;
218 // Check for similar parent paths and see if they can be fixed
219 if (fix_similar) {
220 for (int i=data.vp.size() -1; i>-1; i--) {
221 final String wrong_path = data.vpath.get(i);
222 final Patch p = data.vp.get(i);
223 if (wrong_path.startsWith(wrong_parent_path)) {
224 // try to fix as well
225 File file = new File(new StringBuffer(good_parent_path).append(wrong_path.substring(wrong_parent_path.length())).toString());
226 if (file.exists()) {
227 if (fixPatchPath(p, file.getAbsolutePath(), update_mipmaps)) {
228 data.remove(p); // not by 'i' but by Patch, since if some fail the order is not the same
229 n_fixed++;
235 if (fix_all) {
236 // traverse all Patch from the entire project
237 for (final Displayable d : patch.getLayerSet().getDisplayables(Patch.class)) {
238 final Patch p = (Patch) d;
239 final String wrong_path = p.getImageFilePath();
240 if (wrong_path.startsWith(wrong_parent_path)) {
241 File file = new File(new StringBuffer(good_parent_path).append(wrong_path.substring(wrong_parent_path.length())).toString());
242 if (file.exists()) {
243 fixPatchPath(p, file.getAbsolutePath(), update_mipmaps);
244 n_fixed++;
250 // if table is empty, close
251 if (0 == data.vp.size()) {
252 FilePathRepair fpr = projects.remove(patch.getProject());
253 fpr.frame.dispose();
256 Utils.logAll("Fixed " + n_fixed + " image file path" + (n_fixed > 1 ? "s" : ""));
260 static private boolean fixPatchPath(final Patch patch, final String new_path, final boolean update_mipmaps) {
261 try {
262 // Open the image header to check that dimensions match
263 final Loader loader = patch.getProject().getLoader();
264 loader.releaseToFit(Math.max(Loader.MIN_FREE_BYTES, patch.getOWidth() * patch.getOHeight() * 10));
265 final Dimension dim = loader.getDimensions(new_path);
266 if (null == dim) {
267 Utils.log(new StringBuffer("ERROR: could not open image at ").append(new_path).toString()); // preserving backslashes
268 return false;
270 // Check and set dimensions
271 if (dim.width != patch.getOWidth() || dim.height != patch.getOHeight()) {
272 Utils.log("ERROR different o_width,o_height for patch " + patch + "\n at new path " + new_path);
273 return false;
275 // flag as good
276 loader.removeFromUnloadable(patch);
277 // Assign new image path
278 loader.addedPatchFrom(new_path, patch);
279 // submit job to regenerate mipmaps in the background
280 if (update_mipmaps) loader.regenerateMipMaps(patch);
281 return true;
282 } catch (Exception e) {
283 IJError.print(e);
284 return false;