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 import java.util.Optional;
45
46 import org.apache.commons.io.IOUtils;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50
51
52
53
54
55
56
57 public class MediaStateImpl implements MediaState {
58 private static final Logger log = LoggerFactory.getLogger(MediaStateImpl.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 private boolean inTransaction = false;
75 private boolean valueChangePending;
76 private byte[] valueBeforeTransaction;
77 private byte[] value;
78
79 public MediaStateImpl(byte[] value, SimpleTranslator i18n) {
80 this.i18n = i18n;
81 startTransaction();
82 performMediaModification("");
83 setState(value);
84 valueBeforeTransaction = value;
85 }
86
87 public void startTransaction() {
88 inTransaction = true;
89 valueBeforeTransaction = getState();
90 }
91
92 @Override
93 public void setListener(Listener listener) {
94 this.listener = listener;
95 }
96
97 @Override
98 public void clearState() {
99 unDoneActions.clear();
100 doneActions.clear();
101 }
102
103 @Override
104 public void performMediaModification(String actionName) {
105 if (!currentActionInitialized && doneActions.size() > 1) {
106 doneActions.pop();
107 }
108 Record record = new Record();
109 record.actionName = actionName;
110 try {
111 record.file = File.createTempFile(TEMP_FILE_PREFIX + actionName, null, null);
112 record.file.deleteOnExit();
113 doneActions.push(record);
114 currentActionInitialized = false;
115 } catch (IOException e) {
116 logErrorAndNotify(i18n.translate("ui-mediaeditor.editHistoryTrackingProperty.tmpFileCreationFailure.message"), e);
117 }
118 }
119
120 @Override
121 public void undoLastState() {
122 if (doneActions.size() > 1) {
123 Record lastDone = doneActions.peek();
124 unDoneActions.push(lastDone);
125 doneActions.remove(lastDone);
126 Optional.ofNullable(doneActions.peek()).ifPresent(this::updateValue);
127 }
128 }
129
130 @Override
131 public boolean canUndoLastState() {
132 return doneActions.size() > 1;
133 }
134
135 @Override
136 public boolean canRedoLastState() {
137 return !unDoneActions.isEmpty();
138 }
139
140 @Override
141 public void redoLastState() {
142 if (!unDoneActions.isEmpty()) {
143 final Record toBeRedone = unDoneActions.peek();
144 updateValue(toBeRedone);
145 doneActions.push(toBeRedone);
146 unDoneActions.remove(toBeRedone);
147 }
148 }
149
150 @Override
151 public void revertState() {
152 rollback();
153 clearState();
154 }
155
156 public void rollback() {
157 try {
158 value = valueBeforeTransaction;
159 } finally {
160 valueChangePending = false;
161 endTransaction();
162 }
163 }
164
165 @Override
166 public byte[] getState() {
167 return value;
168 }
169
170 @Override
171 public void commitState() {
172 endTransaction();
173 clearState();
174 }
175
176 @Override
177 public void setState(byte[] bytes) {
178 if (!unDoneActions.isEmpty() &&
179 !unDoneActions.peek().actionName.equals(doneActions.peek().actionName)) {
180 unDoneActions.clear();
181 }
182 currentActionInitialized = true;
183 FileOutputStream fos = null;
184 try {
185 fos = new FileOutputStream(doneActions.peek().file);
186 IOUtils.write(bytes, fos);
187 this.value = bytes;
188 } catch (IOException e) {
189 logErrorAndNotify(i18n.translate("ui-mediaeditor.editHistoryTrackingProperty.ioException.message"), e);
190 } finally {
191 IOUtils.closeQuietly(fos);
192 }
193 }
194
195 protected void endTransaction() {
196 inTransaction = false;
197 valueBeforeTransaction = null;
198 }
199
200 private void logErrorAndNotify(String message, Exception e) {
201 log.error(message, e);
202 if (listener != null) {
203 listener.errorOccurred(message, e);
204 }
205 }
206
207 private void updateValue(Record newLastDone) {
208 FileInputStream fis = null;
209 try {
210 fis = new FileInputStream(newLastDone.file);
211 value = IOUtils.toByteArray(fis);
212 } catch (IOException e) {
213 logErrorAndNotify(i18n.translate("ui-mediaeditor.editHistoryTrackingProperty.ioException.message"), e);
214 } finally {
215 IOUtils.closeQuietly(fis);
216 }
217 }
218
219
220
221
222
223 private static class Record {
224
225 public File file;
226
227 public String actionName;
228 }
229
230
231
232
233
234 private static class TempFileStack extends LinkedList<Record> {
235
236 private int capacity;
237
238 public TempFileStack(int capacity) {
239 this.capacity = capacity;
240 }
241
242 @Override
243 public void push(Record record) {
244 while (size() > capacity) {
245 removeLast();
246 }
247 super.push(record);
248 }
249
250 @Override
251 public Record pop() {
252 Record result = super.pop();
253 if (result != null) {
254 result.file.delete();
255 }
256 return result;
257 }
258
259 @Override
260 public void clear() {
261 while (!isEmpty()) {
262 pop();
263 }
264 }
265
266 public void setCapacity(int capacity) {
267 this.capacity = capacity;
268 }
269 }
270 }