source: josm/trunk/src/org/openstreetmap/josm/data/AutosaveTask.java@ 3378

Last change on this file since 3378 was 3378, checked in by jttt, 14 years ago

Fix #2662 Auto-save

  • Property svn:mime-type set to text/plain
File size: 10.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.File;
7import java.io.FileInputStream;
8import java.io.FileNotFoundException;
9import java.io.IOException;
10import java.util.ArrayList;
11import java.util.Date;
12import java.util.Deque;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Set;
18import java.util.Timer;
19import java.util.TimerTask;
20import java.util.regex.Pattern;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.data.osm.DataSet;
24import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
25import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
26import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter.Listener;
27import org.openstreetmap.josm.data.preferences.IntegerProperty;
28import org.openstreetmap.josm.gui.MapView;
29import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
30import org.openstreetmap.josm.gui.layer.Layer;
31import org.openstreetmap.josm.gui.layer.OsmDataLayer;
32import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
33import org.openstreetmap.josm.io.IllegalDataException;
34import org.openstreetmap.josm.io.OsmExporter;
35import org.openstreetmap.josm.io.OsmReader;
36
37public class AutosaveTask extends TimerTask implements LayerChangeListener, Listener {
38
39 private static final char[] ILLEGAL_CHARACTERS = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' };
40 private static final String AUTOSAVE_DIR = "autosave";
41 private static final String DELETED_LAYERS_DIR = "autosave/deleted_layers";
42
43
44 public static final IntegerProperty PROP_FILES_PER_LAYER = new IntegerProperty("autosave.filesPerLayer", 1);
45 public static final IntegerProperty PROP_DELETED_LAYERS = new IntegerProperty("autosave.deletedLayersBackupCount", 5);
46 public static final IntegerProperty PROP_INTERVAL = new IntegerProperty("autosave.interval", 5 * 60);
47
48 private static class AutosaveLayerInfo {
49 OsmDataLayer layer;
50 String layerName;
51 String layerFileName;
52 final Deque<File> backupFiles = new LinkedList<File>();
53 }
54
55 //"data layer 1_20100711_1330"
56
57 private final DataSetListenerAdapter datasetAdapter = new DataSetListenerAdapter(this);
58 private Set<DataSet> changedDatasets = new HashSet<DataSet>();
59 private final List<AutosaveLayerInfo> layersInfo = new ArrayList<AutosaveLayerInfo>();
60 private Timer timer;
61 private Object layersLock = new Object();
62 private final Deque<File> deletedLayers = new LinkedList<File>();
63
64 private final File autosaveDir = new File(Main.pref.getPreferencesDir() + AUTOSAVE_DIR);
65 private final File deletedLayersDir = new File(Main.pref.getPreferencesDir() + DELETED_LAYERS_DIR);
66
67 public void schedule() {
68 if (PROP_INTERVAL.get() > 0) {
69
70 if (!autosaveDir.exists() && !autosaveDir.mkdirs()) {
71 System.out.println(tr("Unable to create directory {0}, autosave will be disabled", autosaveDir.getAbsolutePath()));
72 return;
73 }
74 if (!deletedLayersDir.exists() && !deletedLayersDir.mkdirs()) {
75 System.out.println(tr("Unable to create directory {0}, autosave will be disabled", deletedLayersDir.getAbsolutePath()));
76 return;
77 }
78
79 for (File f: deletedLayersDir.listFiles()) {
80 deletedLayers.add(f);
81 }
82
83
84 timer = new Timer(true);
85 timer.schedule(this, 1000, PROP_INTERVAL.get() * 1000);
86 MapView.addLayerChangeListener(this);
87 if (Main.isDisplayingMapView()) {
88 for (OsmDataLayer l: Main.map.mapView.getLayersOfType(OsmDataLayer.class)) {
89 registerNewlayer(l);
90 }
91 }
92 }
93 }
94
95 private String getFileName(String layerName, int index) {
96 String result = layerName;
97 for (int i=0; i<ILLEGAL_CHARACTERS.length; i++) {
98 result = result.replaceAll(Pattern.quote(String.valueOf(ILLEGAL_CHARACTERS[i])),
99 '&' + String.valueOf((int)ILLEGAL_CHARACTERS[i]) + ';');
100 }
101 if (index != 0) {
102 result = result + '_' + index;
103 }
104 return result;
105 }
106
107 private void setLayerFileName(AutosaveLayerInfo layer) {
108 int index = 0;
109 while (true) {
110 String filename = getFileName(layer.layer.getName(), index);
111 boolean foundTheSame = false;
112 for (AutosaveLayerInfo info: layersInfo) {
113 if (info != layer && info.layerFileName.equals(filename)) {
114 foundTheSame = true;
115 break;
116 }
117 }
118
119 if (!foundTheSame) {
120 layer.layerFileName = filename;
121 return;
122 }
123
124 index++;
125 }
126 }
127
128 private File getNewLayerFile(AutosaveLayerInfo layer) {
129 int index = 0;
130 Date now = new Date();
131 while (true) {
132 File result = new File(autosaveDir, String.format("%1$s_%2$tY%2$tm%2$td_%2$tH%2$tM%3$s.osm", layer.layerFileName, now, index == 0?"":"_" + index));
133 try {
134 if (result.createNewFile())
135 return result;
136 else {
137 System.out.println(tr("Unable to create file {0}, other filename will be used", result.getAbsolutePath()));
138 }
139 } catch (IOException e) {
140 System.err.println(tr("IOError while creating file, autosave will be skipped: {0}", e.getMessage()));
141 return null;
142 }
143 index++;
144 }
145 }
146
147 private void savelayer(AutosaveLayerInfo info) throws IOException {
148 if (!info.layer.getName().equals(info.layerName)) {
149 setLayerFileName(info);
150 info.layerName = info.layer.getName();
151 }
152 if (changedDatasets.contains(info.layer.data)) {
153 File file = getNewLayerFile(info);
154 if (file != null) {
155 info.backupFiles.add(file);
156 new OsmExporter().exportData(file, info.layer);
157 }
158 }
159 while (info.backupFiles.size() > PROP_FILES_PER_LAYER.get()) {
160 File oldFile = info.backupFiles.remove();
161 if (!oldFile.delete()) {
162 System.out.println(tr("Unable to delete old backup file {0}", oldFile.getAbsolutePath()));
163 }
164 }
165 }
166
167 @Override
168 public void run() {
169 synchronized (layersLock) {
170 try {
171 for (AutosaveLayerInfo info: layersInfo) {
172 savelayer(info);
173 }
174 changedDatasets.clear();
175 } catch (Throwable t) {
176 // Don't let exception stop time thread
177 System.err.println("Autosave failed: ");
178 t.printStackTrace();
179 }
180 }
181 }
182
183 @Override
184 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
185 // Do nothing
186 }
187
188 private void registerNewlayer(OsmDataLayer layer) {
189 synchronized (layersLock) {
190 layer.data.addDataSetListener(datasetAdapter);
191 AutosaveLayerInfo info = new AutosaveLayerInfo();
192 info.layer = layer;
193 layersInfo.add(info);
194 }
195 }
196
197 @Override
198 public void layerAdded(Layer newLayer) {
199 if (newLayer instanceof OsmDataLayer) {
200 registerNewlayer((OsmDataLayer) newLayer);
201 }
202 }
203
204 @Override
205 public void layerRemoved(Layer oldLayer) {
206 if (oldLayer instanceof OsmDataLayer) {
207 synchronized (layersLock) {
208 OsmDataLayer osmLayer = (OsmDataLayer) oldLayer;
209 osmLayer.data.removeDataSetListener(datasetAdapter);
210 Iterator<AutosaveLayerInfo> it = layersInfo.iterator();
211 while (it.hasNext()) {
212 AutosaveLayerInfo info = it.next();
213 if (info.layer == osmLayer) {
214
215 try {
216 savelayer(info);
217 File lastFile = info.backupFiles.pollLast();
218 if (lastFile != null) {
219 File backupFile = new File(deletedLayersDir, lastFile.getName());
220 lastFile.renameTo(backupFile);
221 deletedLayers.add(lastFile);
222 }
223 for (File file: info.backupFiles) {
224 file.delete();
225 }
226
227 while (deletedLayers.size() > PROP_DELETED_LAYERS.get()) {
228 deletedLayers.remove().delete();
229 }
230 } catch (IOException e) {
231 System.err.println(tr("Error while creating backup of removed layer: {0}", e.getMessage()));
232 }
233
234 it.remove();
235 }
236 }
237 }
238 }
239 }
240
241 @Override
242 public void processDatasetEvent(AbstractDatasetChangedEvent event) {
243 changedDatasets.add(event.getDataset());
244 }
245
246 public List<OsmDataLayer> getUnsavedLayers() {
247 List<OsmDataLayer> result = new ArrayList<OsmDataLayer>();
248
249 File[] files = autosaveDir.listFiles();
250 if (files == null)
251 return result;
252 for (File f: autosaveDir.listFiles()) {
253 if (f.isDirectory()) {
254 continue;
255 }
256 try {
257 DataSet ds = OsmReader.parseDataSet(new FileInputStream(f), NullProgressMonitor.INSTANCE);
258 String layerName = f.getName();
259 layerName = layerName.substring(0, layerName.lastIndexOf('.'));
260 result.add(new OsmDataLayer(ds, layerName, null));
261 f.renameTo(new File(deletedLayersDir, f.getName()));
262 } catch (FileNotFoundException e) {
263 // Should not happen
264 System.err.println("File " + f.getAbsolutePath() + " not found");
265 } catch (IllegalDataException e) {
266 System.err.println(tr("Unable to read autosaved osm data ({0}) - {1}", f.getAbsoluteFile(), e.getMessage()));
267 }
268 }
269
270 return result;
271 }
272
273
274}
Note: See TracBrowser for help on using the repository browser.