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

Last change on this file since 3461 was 3461, checked in by bastiK, 14 years ago

added gui preference for autosave; fixed #5359 - Button to delete autosaved data

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