1 package org.simantics.db.procore.ui.internal;
\r
4 import java.lang.reflect.InvocationTargetException;
\r
5 import java.nio.file.Files;
\r
6 import java.nio.file.Path;
\r
7 import java.util.ArrayList;
\r
9 import org.eclipse.core.runtime.IProgressMonitor;
\r
10 import org.eclipse.jface.dialogs.MessageDialog;
\r
11 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
\r
12 import org.eclipse.jface.operation.IRunnableWithProgress;
\r
13 import org.eclipse.swt.widgets.Display;
\r
14 import org.eclipse.swt.widgets.Shell;
\r
15 import org.simantics.db.common.utils.Logger;
\r
16 import org.simantics.db.server.Auxiliary;
\r
17 import org.simantics.db.server.ProCoreException;
\r
19 abstract class Handler {
\r
20 protected final String title = "Database Server";
\r
21 abstract boolean start(Shell shell, ProCoreException e) throws ProCoreException;
\r
22 protected void checkFolderGiven(Shell shell, ProCoreException e) throws ProCoreException {
\r
23 if (null == e.getDbFolder()) {
\r
24 String msg = "No database folder given.";
\r
25 MessageDialog.openWarning(shell, title, msg);
\r
26 throw new ProCoreException(msg);
\r
30 final class DefaultHandler extends Handler {
\r
32 boolean start(Shell shell, ProCoreException e) throws ProCoreException {
\r
33 String warning = Util.getMessage(e);
\r
34 MessageDialog.openError(shell, title, warning);
\r
38 final class GuardFileVersionHandler extends Handler {
\r
40 boolean start(Shell shell, ProCoreException e) throws ProCoreException {
\r
41 checkFolderGiven(shell, e);
\r
42 return HandlerUtil.recoverFromGuardFileVersion(shell, e.getDbFolder(), title, e.getMessage());
\r
45 final class DatabaseCorruptedHandler extends Handler {
\r
47 boolean start(Shell shell, ProCoreException e) throws ProCoreException {
\r
48 checkFolderGiven(shell, e);
\r
49 return HandlerUtil.recoverFromJournal(shell, e.getDbFolder(), title, e.getMessage());
\r
52 final class DatabaseLastExitHandler extends Handler {
\r
54 boolean start(Shell shell, ProCoreException e) throws ProCoreException {
\r
55 checkFolderGiven(shell, e);
\r
56 return HandlerUtil.recoverFromDatabaseLastExit(shell, e.getDbFolder(), title, e.getMessage());
\r
60 final class DatabaseProtocolHandler extends Handler {
\r
62 boolean start(Shell shell, ProCoreException e) throws ProCoreException {
\r
63 checkFolderGiven(shell, e);
\r
64 return HandlerUtil.recoverFromProtocol(shell, e.getDbFolder(), title, e.getMessage());
\r
68 final class DatabaseStartHandler extends Handler {
\r
70 boolean start(Shell shell, ProCoreException e) throws ProCoreException {
\r
71 checkFolderGiven(shell, e);
\r
72 String warning = e.getMessage();
\r
73 MessageDialog.openError(shell, title, warning);
\r
77 final class DatabaseVersionHandler extends Handler {
\r
79 boolean start(Shell shell, ProCoreException e) throws ProCoreException {
\r
80 checkFolderGiven(shell, e);
\r
81 return HandlerUtil.recoverFromDatabaseVersion(shell, e.getDbFolder(), title, e.getMessage());
\r
85 private static String NL = System.getProperty("line.separator");
\r
86 private static boolean isFolder(final Shell shell, final File dbFolder, String title) {
\r
87 if (dbFolder.isDirectory())
\r
89 MessageDialog.openWarning(shell, title, "Database folder does not exist. folder=" + dbFolder);
\r
92 public static boolean saveWithQuestion(final Shell shell, final File dbFolder, String title, String msg) {
\r
93 if (!isFolder(shell, dbFolder, title))
\r
94 return false; // Save not possible.
\r
95 String question = ((null != msg) ? (msg + NL) : "")
\r
96 + "Do you want to save database?" + NL + "folder=" + dbFolder;
\r
97 boolean yes = MessageDialog.openQuestion(shell, title, question);
\r
100 final class SaveDatabase extends ExecutorDatabase {
\r
102 SaveDatabase(File dbFolder) {
\r
104 beginMessage = "Saving database.";
\r
105 okMessage = "Database has been saved.";
\r
106 failMessage = "Failed to save database.";
\r
107 cancelMessage = "Save cancelled.";
\r
110 public void execute() throws Throwable {
\r
111 saveFolder = Auxiliary.saveDatabase(dbFolder);
\r
112 if (null == saveFolder || !Files.isDirectory(saveFolder))
\r
113 throw new ProCoreException("Save folder not ok.");
\r
116 public String getMessage() {
\r
117 return NL + "folder=" + saveFolder;
\r
120 SaveDatabase save = new SaveDatabase(dbFolder);
\r
121 execute(shell, save);
\r
122 save.showDone(shell);
\r
125 static boolean saveWithCheck(final Shell shell, final File dbFolder, String title, String msg) {
\r
126 boolean ok = saveWithQuestion(shell, dbFolder, title, msg);
\r
129 String question = "Save failed. Do you want me to contine?";
\r
130 return Util.confirm(shell, title, question);
\r
133 public static boolean delete(final Shell shell, final File dbFolder, String title, String msg) {
\r
134 if (!isFolder(shell, dbFolder, title))
\r
135 return false; // Delete not possible.
\r
136 String question = ((null != msg) ? (msg + NL) : "")
\r
137 + "Do you want to delete database?" + NL + "folder=" + dbFolder;
\r
138 boolean yes = MessageDialog.openQuestion(shell, title, question);
\r
141 saveWithQuestion(shell, dbFolder, title, null);
\r
142 final class DeleteDatabase extends ExecutorDatabase {
\r
143 DeleteDatabase(File dbFolder) {
\r
145 beginMessage = "Deleting database.";
\r
146 okMessage = "Database has been deleted.";
\r
147 failMessage = "Failed to delete database.";
\r
148 cancelMessage = "Delete cancelled.";
\r
151 public void execute() throws Throwable {
\r
152 Auxiliary.deleteDatabase(dbFolder);
\r
155 DeleteDatabase delete = new DeleteDatabase(dbFolder);
\r
156 execute(shell, delete);
\r
157 delete.showDone(shell);
\r
160 public static boolean purge(final Shell shell, final File dbFolder, String title, String msg) {
\r
161 if (!isFolder(shell, dbFolder, title))
\r
162 return false; // Purge not possible.
\r
164 if (Auxiliary.purgeDatabaseDone(dbFolder)) {
\r
165 MessageDialog.openInformation(shell, title, "Database already purged." + NL + "folder=" + dbFolder);
\r
166 return true; // Already clean.
\r
168 } catch (ProCoreException e) {
\r
169 Logger.defaultLogError("Failed to query database purge state.", e);
\r
171 String question = ((null != msg) ? (msg + NL) : "")
\r
172 + "Do you want to purge database?";
\r
173 boolean yes = MessageDialog.openQuestion(shell, title, question);
\r
176 return purgeWithSave(shell, dbFolder, title);
\r
178 private static boolean purgeWithSave(final Shell shell, final File dbFolder, String title) {
\r
179 boolean ok = saveWithCheck(shell, dbFolder, title, null);
\r
182 final class PurgeDatabase extends ExecutorDatabase {
\r
183 PurgeDatabase(File dbFolder) {
\r
185 beginMessage = "Purging database.";
\r
186 okMessage = "Database has been purged.";
\r
187 failMessage = "Failed to purge database.";
\r
188 cancelMessage = "Purge cancelled.";
\r
191 public void execute() throws Throwable {
\r
192 Auxiliary.purgeDatabase(dbFolder);
\r
195 PurgeDatabase purge = new PurgeDatabase(dbFolder);
\r
196 execute(shell, purge);
\r
197 purge.showDone(shell);
\r
200 public static boolean recoverFromGuardFileVersion(final Shell shell, final File dbFolder, String title, String msg)
\r
201 throws ProCoreException {
\r
202 String question = ((null != msg) ? msg : "")
\r
203 + NL + "Guard file version mismatch indicates that the database was made with different server version."
\r
204 + "It would be best to open the database with the same version it was made.";
\r
205 MessageDialog.openWarning(shell, title, question);
\r
208 public static boolean recoverFromDatabaseLastExit(final Shell shell, final File dbFolder, String title, String msg)
\r
209 throws ProCoreException {
\r
210 String message = ((null != msg) ? msg : "") + NL + "What should I try to do?";
\r
211 ArrayList<Util.Choice> choices = new ArrayList<Util.Choice>();
\r
212 choices.add(new Util.Choice("Cancel", "Cancel i.e. do nothing. Choose this if you want to manually analyze and correct the situation. This is the safest choice."));
\r
213 choices.add(new Util.Choice("Ignore", "Ignore the exit status. Choose this if you do not know what you are doing. This is fast way to recover and is the safest choice except for cancel."));
\r
214 choices.add(new Util.Choice("Remove", "Remove history. Choose this you know what you are doing. This is fast way to recover but can leave tricky semantic errors in the database. Furhermore, depending on the reason for the non clean exit status, this can fail and corrupt data. However, depending on how the client and/or server died, this could be the right choice."));
\r
215 choices.add(new Util.Choice("Recover", "Recover using journal. Choose this if you are willing to wait and know that the other choices won't work."));
\r
216 Util.Choice[] t = new Util.Choice[choices.size()];
\r
217 int choice = Util.select(shell, title, message, choices.toArray(t), 0);
\r
219 default: return false;
\r
220 case 1: return ignoreExitStatusWithSave(shell, dbFolder, title);
\r
221 case 2: return purgeWithSave(shell, dbFolder, title);
\r
222 case 3: return recoverFromJournalWithSave(shell, dbFolder, title);
\r
225 public static boolean ignoreExitStatusWithSave(final Shell shell, final File dbFolder, String title) {
\r
226 boolean ok = saveWithCheck(shell, dbFolder, title, null);
\r
229 final class IgnoreExitDatabase extends ExecutorDatabase {
\r
230 IgnoreExitDatabase(File dbFolder) {
\r
232 beginMessage = "Ignoring last exit status.";
\r
233 okMessage = "Ignore done.";
\r
234 failMessage = "Failed to start.";
\r
235 cancelMessage = "Ignore cancelled.";
\r
238 public void execute() throws Throwable {
\r
239 Auxiliary.ignoreExit(dbFolder);
\r
242 IgnoreExitDatabase recover = new IgnoreExitDatabase(dbFolder);
\r
243 execute(shell, recover);
\r
244 recover.showDone(shell);
\r
247 public static boolean ignoreProtocolVersionWithSave(final Shell shell, final File dbFolder, String title) {
\r
248 boolean ok = saveWithCheck(shell, dbFolder, title, null);
\r
251 final class IgnoreProtocolDatabase extends ExecutorDatabase {
\r
252 IgnoreProtocolDatabase(File dbFolder) {
\r
254 beginMessage = "Ignoring protocol version mismatch.";
\r
255 okMessage = "Ignore done.";
\r
256 failMessage = "Failed to start.";
\r
257 cancelMessage = "Ignore cancelled.";
\r
260 public void execute() throws Throwable {
\r
261 Auxiliary.ignoreProtocol(dbFolder);
\r
264 IgnoreProtocolDatabase ignore = new IgnoreProtocolDatabase(dbFolder);
\r
265 execute(shell, ignore);
\r
266 ignore.showDone(shell);
\r
269 public static boolean recoverFromProtocol(final Shell shell, final File dbFolder, String title, String msg)
\r
270 throws ProCoreException {
\r
271 String question = ((null != msg) ? msg : "")
\r
272 + NL + "Protocol version mismatch indicates that server and client versions differ."
\r
273 + " It would be best to open the database using the same server and client version."
\r
274 + " But if you insist I can ignore the mismatch and try to muddle along."
\r
275 + " If this works then you should export the data and get matching client and server versions."
\r
276 + " Otherwise there could later be strange errors caused by this version mismatch."
\r
278 boolean yes = Util.openDefaultNo(shell, title, question, MessageDialog.QUESTION);
\r
281 return ignoreProtocolVersionWithSave(shell, dbFolder, title);
\r
283 // public static boolean recoverFromProtocolWithSave(final Shell shell, final File dbFolder, String title) {
\r
284 // boolean ok = saveWithCheck(shell, dbFolder, title, null);
\r
287 // return ignoreProtocolVersionWithSave(shell, dbFolder, title);
\r
289 public static boolean recoverFromDatabaseVersion(final Shell shell, final File dbFolder, String title, String msg)
\r
290 throws ProCoreException {
\r
291 String question = ((null != msg) ? msg : "")
\r
292 + NL + "Database version mismatch indicates that the database was made with different server version."
\r
293 + " It would be best to open the database with the same version it was made."
\r
294 + " But if you insist I can try to recover database from journal.";
\r
295 boolean yes = Util.openDefaultNo(shell, title, question, MessageDialog.QUESTION);
\r
298 return recoverFromJournalWithSave(shell, dbFolder, title);
\r
300 public static boolean recoverFromJournal(final Shell shell, final File dbFolder, String title, String msg)
\r
301 throws ProCoreException {
\r
302 if (!isFolder(shell, dbFolder, title))
\r
303 return false; // Recovery not possible.
\r
304 if (!Auxiliary.canReadJournal(dbFolder)) {
\r
305 MessageDialog.openWarning(shell, title, "Journal file does not exist or isn't readable." + NL + "folder=" + dbFolder);
\r
306 return false; // Recovery not possible.
\r
308 String question = ((null != msg) ? msg : "")
\r
309 + NL + "Do you want me to try to recreate the database from journal?";
\r
310 boolean yes = MessageDialog.openQuestion(shell, title, question);
\r
313 return recoverFromJournalWithSave(shell, dbFolder, title);
\r
315 public static boolean recoverFromJournalWithSave(final Shell shell, final File dbFolder, String title) {
\r
316 boolean ok = saveWithCheck(shell, dbFolder, title, null);
\r
319 final class RecoverDatabase extends ExecutorDatabase {
\r
320 RecoverDatabase(File dbFolder) {
\r
322 beginMessage = "Recovering database.";
\r
323 okMessage = "Database has been recovered.";
\r
324 failMessage = "Failed to recover database.";
\r
325 cancelMessage = "Recovery cancelled.";
\r
328 public void execute() throws Throwable {
\r
329 Auxiliary.replaceFromJournal(dbFolder);
\r
332 RecoverDatabase recover = new RecoverDatabase(dbFolder);
\r
333 execute(shell, recover);
\r
334 recover.showDone(shell);
\r
337 private static void sleep(long millsec) throws InterruptedException {
\r
338 Display display = UI.getDisplay();
\r
339 boolean isUIThread = (null == display) ? false : (Thread.currentThread() == display.getThread());
\r
344 while (++count < 1000 && display.readAndDispatch())
\r
347 interface Executor extends Runnable {
\r
348 public String getMessageBegin();
\r
349 public String getMessageCancel();
\r
350 public String getMessageFail(Throwable throwable);
\r
351 public String getMessageFail();
\r
352 public String getMessageOk();
\r
353 public boolean isDone();
\r
354 public boolean isForkable();
\r
355 public boolean isCancelable();
\r
356 public void execute() throws Throwable;
\r
357 public void setCancelled();
\r
358 public void setDone();
\r
359 public void showDone(Shell shell);
\r
361 static abstract class ExecutorBase implements Executor {
\r
362 protected String beginMessage = "Task begin.";
\r
363 protected String okMessage = "Task ok.";
\r
364 protected String failMessage = "Task failed.";
\r
365 protected String cancelMessage = "Task cancelled.";
\r
366 protected boolean done = false;
\r
367 protected boolean ok = false;
\r
368 protected boolean cancelled = false;
\r
369 protected boolean forkable = true;
\r
370 protected boolean cancelable = false;
\r
371 protected Throwable throwable = null;
\r
372 public void run() {
\r
376 } catch (Throwable t) {
\r
383 public String getMessageBegin() {
\r
384 return beginMessage;
\r
387 public String getMessageCancel() {
\r
388 return cancelMessage;
\r
391 public String getMessageFail(Throwable throwable) {
\r
392 return failMessage + NL + throwable.getMessage();
\r
395 public String getMessageFail() {
\r
396 return failMessage;
\r
399 public String getMessageOk() {
\r
403 public boolean isDone() {
\r
407 public void setCancelled() {
\r
411 public void setDone() {
\r
415 public boolean isForkable() {
\r
419 public boolean isCancelable() {
\r
423 public void showDone(Shell shell) {
\r
424 if (null != throwable)
\r
425 Util.showError(shell, getMessageFail(throwable));
\r
427 Util.showInfo(shell, getMessageOk());
\r
428 else if (cancelled)
\r
429 Util.showInfo(shell, getMessageCancel());
\r
431 Util.showWarning(shell, getMessageFail());
\r
434 static abstract class ExecutorDatabase extends ExecutorBase {
\r
435 protected final File dbFolder;
\r
436 ExecutorDatabase(File dbFolder) {
\r
437 this.dbFolder = dbFolder;
\r
439 String getMessage() {
\r
440 return NL + "folder=" + dbFolder;
\r
443 public String getMessageBegin() {
\r
444 return super.getMessageBegin() + getMessage();
\r
447 public String getMessageCancel() {
\r
448 return super.getMessageCancel() + getMessage();
\r
451 public String getMessageFail(Throwable t) {
\r
452 return super.getMessageFail(t) + getMessage();
\r
455 public String getMessageOk() {
\r
456 return super.getMessageOk() + getMessage();
\r
459 private static void execute(final Shell shell, final Executor executor) {
\r
460 final Thread thread = new Thread(executor);
\r
462 IRunnableWithProgress progress = new IRunnableWithProgress() {
\r
464 public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
\r
466 monitor.beginTask(executor.getMessageBegin(), IProgressMonitor.UNKNOWN);
\r
467 while (!monitor.isCanceled() && !executor.isDone()) {
\r
471 if (executor.isDone())
\r
473 executor.setCancelled();
\r
474 thread.interrupt();
\r
475 monitor.subTask("Waiting for cancellation to finish.");
\r
476 while (!executor.isDone())
\r
483 boolean fork = executor.isForkable();
\r
484 boolean cancelable = executor.isCancelable();
\r
486 new ProgressMonitorDialog(shell).run(fork, cancelable, progress);
\r
487 } catch (InvocationTargetException e) {
\r
488 } catch (InterruptedException e) {
\r