1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.mediaeditor.data;
35
36
37 import info.magnolia.i18nsystem.SimpleTranslator;
38
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.util.LinkedList;
44
45 import org.apache.commons.io.IOUtils;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import com.vaadin.v7.data.util.ObjectProperty;
50 import com.vaadin.v7.data.util.TransactionalPropertyWrapper;
51
52
53
54
55
56
57 public class EditHistoryTrackingPropertyImpl extends TransactionalPropertyWrapper<byte[]> implements EditHistoryTrackingProperty {
58 private static final Logger log = LoggerFactory.getLogger(EditHistoryTrackingPropertyImpl.class);
59
60 private static final int DEFAULT_DEPTH = 5;
61
62 private static final String TEMP_FILE_PREFIX = "MEDIA_EDITOR_";
63
64 private TempFileStack doneActions = new TempFileStack(DEFAULT_DEPTH);
65
66 private TempFileStack unDoneActions = new TempFileStack(DEFAULT_DEPTH);
67
68 private boolean currentActionInitialized;
69
70 private Listener listener;
71
72 private final SimpleTranslator i18n;
73
74 public EditHistoryTrackingPropertyImpl(byte[] bytes, SimpleTranslator i18n) {
75 super(new ObjectProperty<>(bytes));
76
77 this.i18n = i18n;
78 startTransaction();
79 startAction("");
80 setValue(bytes);
81 }
82
83 @Override
84 public void setListener(Listener listener) {
85 this.listener = listener;
86 }
87
88 @Override
89 public String getLastDoneActionName() {
90 return doneActions.size() < 2 ? null : doneActions.peek().actionName;
91 }
92
93 @Override
94 public String getLastUnDoneActionName() {
95 return unDoneActions.isEmpty() ? null : unDoneActions.peek().actionName;
96 }
97
98 @Override
99 public void purgeHistory() {
100 unDoneActions.clear();
101 doneActions.clear();
102 }
103
104 @Override
105 public void setCapacity(int depth) {
106 doneActions.setCapacity(depth);
107 unDoneActions.setCapacity(depth);
108 }
109
110 @Override
111 public void startAction(String actionName) {
112 if (!currentActionInitialized && doneActions.size() > 1) {
113 doneActions.pop();
114 }
115 Record record = new Record();
116 record.actionName = actionName;
117 try {
118 record.file = File.createTempFile(TEMP_FILE_PREFIX + actionName, null, null);
119 record.file.deleteOnExit();
120 doneActions.push(record);
121 currentActionInitialized = false;
122 } catch (IOException e) {
123 logErrorAndNotify(i18n.translate("ui-mediaeditor.editHistoryTrackingProperty.tmpFileCreationFailure.message"), e);
124 }
125 }
126
127 @Override
128 public void undo() {
129 if (doneActions.size() > 1) {
130 Record lastDone = doneActions.peek();
131 unDoneActions.push(lastDone);
132 doneActions.remove(lastDone);
133 updateValue(doneActions.peek());
134 }
135 }
136
137 @Override
138 public void redo() {
139 if (!unDoneActions.isEmpty()) {
140 final Record toBeRedone = unDoneActions.peek();
141 updateValue(toBeRedone);
142 doneActions.push(toBeRedone);
143 unDoneActions.remove(toBeRedone);
144 }
145 }
146
147 @Override
148 public void revert() {
149 rollback();
150 purgeHistory();
151 }
152
153 @Override
154 public void commit() {
155 super.commit();
156 purgeHistory();
157 }
158
159 @Override
160 public void setValue(byte[] bytes) throws ReadOnlyException {
161 if (!unDoneActions.isEmpty() &&
162 !unDoneActions.peek().actionName.equals(doneActions.peek().actionName)) {
163 unDoneActions.clear();
164 }
165 currentActionInitialized = true;
166 FileOutputStream fos = null;
167 try {
168 fos = new FileOutputStream(doneActions.peek().file);
169 IOUtils.write(bytes, fos);
170 super.setValue(bytes);
171 } catch (IOException e) {
172 logErrorAndNotify(i18n.translate("ui-mediaeditor.editHistoryTrackingProperty.ioException.message"), e);
173 } finally {
174 IOUtils.closeQuietly(fos);
175 }
176 }
177
178 private void logErrorAndNotify(String message, Exception e) {
179 log.error(message, e);
180 if (listener != null) {
181 listener.errorOccurred(message, e);
182 }
183 }
184
185 private void updateValue(Record newLastDone) {
186 FileInputStream fis = null;
187 try {
188 fis = new FileInputStream(newLastDone.file);
189 super.setValue(IOUtils.toByteArray(fis));
190 } catch (IOException e) {
191 logErrorAndNotify(i18n.translate("ui-mediaeditor.editHistoryTrackingProperty.ioException.message"), e);
192 } finally {
193 IOUtils.closeQuietly(fis);
194 }
195 }
196
197
198
199
200
201 private static class Record {
202
203 public File file;
204
205 public String actionName;
206 }
207
208
209
210
211
212 private static class TempFileStack extends LinkedList<Record> {
213
214 private int capacity;
215
216 public TempFileStack(int capacity) {
217 this.capacity = capacity;
218 }
219
220 @Override
221 public void push(Record record) {
222 while (size() > capacity) {
223 removeLast();
224 }
225 super.push(record);
226 }
227
228 @Override
229 public Record pop() {
230 Record result = super.pop();
231 if (result != null) {
232 result.file.delete();
233 }
234 return result;
235 }
236
237 @Override
238 public void clear() {
239 while (!isEmpty()) {
240 pop();
241 }
242 }
243
244 public void setCapacity(int capacity) {
245 this.capacity = capacity;
246 }
247 }
248 }