manage remote and io exception in fanfix
[fanfix.git] / src / be / nikiroo / fanfix / reader / tui / TuiReaderApplication.java
1 package be.nikiroo.fanfix.reader.tui;
2
3 import java.awt.Toolkit;
4 import java.awt.datatransfer.DataFlavor;
5 import java.io.IOException;
6 import java.net.URL;
7 import java.net.UnknownHostException;
8
9 import jexer.TApplication;
10 import jexer.TCommand;
11 import jexer.TKeypress;
12 import jexer.TMessageBox;
13 import jexer.TMessageBox.Result;
14 import jexer.TMessageBox.Type;
15 import jexer.TStatusBar;
16 import jexer.TWidget;
17 import jexer.TWindow;
18 import jexer.event.TCommandEvent;
19 import jexer.event.TMenuEvent;
20 import jexer.menu.TMenu;
21 import be.nikiroo.fanfix.Instance;
22 import be.nikiroo.fanfix.data.MetaData;
23 import be.nikiroo.fanfix.data.Story;
24 import be.nikiroo.fanfix.library.BasicLibrary;
25 import be.nikiroo.fanfix.reader.BasicReader;
26 import be.nikiroo.fanfix.reader.Reader;
27 import be.nikiroo.fanfix.reader.tui.TuiReaderMainWindow.Mode;
28 import be.nikiroo.fanfix.supported.SupportType;
29 import be.nikiroo.utils.Progress;
30
31 /**
32 * Manages the TUI general mode and links and manages the {@link TWindow}s.
33 * <p>
34 * It will also enclose a {@link Reader} and simply handle the reading part
35 * differently (it will create the required sub-windows and display them).
36 *
37 * @author niki
38 */
39 class TuiReaderApplication extends TApplication implements Reader {
40 public static final int MENU_OPEN = 1025;
41 public static final int MENU_IMPORT_URL = 1026;
42 public static final int MENU_IMPORT_FILE = 1027;
43 public static final int MENU_EXPORT = 1028;
44 public static final int MENU_DELETE = 1029;
45 public static final int MENU_LIBRARY = 1030;
46 public static final int MENU_EXIT = 1031;
47
48 public static final TCommand CMD_EXIT = new TCommand(MENU_EXIT) {
49 };
50
51 private Reader reader;
52 private TuiReaderMainWindow main;
53
54 // start reading if meta present
55 public TuiReaderApplication(Reader reader, BackendType backend)
56 throws Exception {
57 super(backend);
58 init(reader);
59
60 if (getMeta() != null) {
61 read(false);
62 }
63 }
64
65 public TuiReaderApplication(Reader reader, String source,
66 TApplication.BackendType backend) throws Exception {
67 super(backend);
68 init(reader);
69
70 showMain();
71 main.setMode(Mode.SOURCE, source);
72 }
73
74 @Override
75 public void read(boolean sync) throws IOException {
76 read(getStory(null), sync);
77 }
78
79 @Override
80 public MetaData getMeta() {
81 return reader.getMeta();
82 }
83
84 @Override
85 public Story getStory(Progress pg) throws IOException {
86 return reader.getStory(pg);
87 }
88
89 @Override
90 public BasicLibrary getLibrary() {
91 return reader.getLibrary();
92 }
93
94 @Override
95 public void setLibrary(BasicLibrary lib) {
96 reader.setLibrary(lib);
97 }
98
99 @Override
100 public void setMeta(MetaData meta) throws IOException {
101 reader.setMeta(meta);
102 }
103
104 @Override
105 public void setMeta(String luid) throws IOException {
106 reader.setMeta(luid);
107 }
108
109 @Override
110 public void setMeta(URL source, Progress pg) throws IOException {
111 reader.setMeta(source, pg);
112 }
113
114 @Override
115 public void browse(String source) {
116 try {
117 reader.browse(source);
118 } catch (IOException e) {
119 Instance.getTraceHandler().error(e);
120 }
121 }
122
123 @Override
124 public int getChapter() {
125 return reader.getChapter();
126 }
127
128 @Override
129 public void setChapter(int chapter) {
130 reader.setChapter(chapter);
131 }
132
133 @Override
134 public void search(boolean sync) throws IOException {
135 reader.search(sync);
136 }
137
138 @Override
139 public void search(SupportType searchOn, String keywords, int page,
140 int item, boolean sync) throws IOException {
141 reader.search(searchOn, keywords, page, item, sync);
142 }
143
144 @Override
145 public void searchTag(SupportType searchOn, int page, int item,
146 boolean sync, Integer... tags) throws IOException {
147 reader.searchTag(searchOn, page, item, sync, tags);
148 }
149
150 /**
151 * Open the given {@link Story} for reading. This may or may not start an
152 * external program to read said {@link Story}.
153 *
154 * @param story
155 * the {@link Story} to read
156 * @param sync
157 * execute the process synchronously (wait until it is terminated
158 * before returning)
159 *
160 * @throws IOException
161 * in case of I/O errors
162 */
163 public void read(Story story, boolean sync) throws IOException {
164 if (story == null) {
165 throw new IOException("No story to read");
166 }
167
168 // TODO: open in editor + external option
169 if (!story.getMeta().isImageDocument()) {
170 TWindow window = new TuiReaderStoryWindow(this, story, getChapter());
171 window.maximize();
172 } else {
173 try {
174 openExternal(getLibrary(), story.getMeta().getLuid(), sync);
175 } catch (IOException e) {
176 messageBox("Error when trying to open the story",
177 e.getMessage(), TMessageBox.Type.OK);
178 }
179 }
180 }
181
182 /**
183 * Set the default status bar when this window appear.
184 * <p>
185 * Some shortcuts are always visible, and will be put here.
186 * <p>
187 * Note that shortcuts placed this way on menu won't work unless the menu
188 * also implement them.
189 *
190 * @param window
191 * the new window or menu on screen
192 * @param description
193 * the description to show on the status ba
194 */
195 public TStatusBar setStatusBar(TWindow window, String description) {
196 TStatusBar statusBar = window.newStatusBar(description);
197 statusBar.addShortcutKeypress(TKeypress.kbF10, CMD_EXIT, "Exit");
198 return statusBar;
199
200 }
201
202 private void showMain() {
203 if (main != null && main.isVisible()) {
204 main.activate();
205 } else {
206 if (main != null) {
207 main.close();
208 }
209 main = new TuiReaderMainWindow(this);
210 main.maximize();
211 }
212 }
213
214 private void init(Reader reader) {
215 this.reader = reader;
216
217 // TODO: traces/errors?
218 Instance.setTraceHandler(null);
219
220 // Add the menus TODO: i18n
221 TMenu fileMenu = addMenu("&File");
222 fileMenu.addItem(MENU_OPEN, "&Open...");
223 fileMenu.addItem(MENU_EXPORT, "&Save as...");
224 fileMenu.addItem(MENU_DELETE, "&Delete...");
225 // TODO: Move to...
226 fileMenu.addSeparator();
227 fileMenu.addItem(MENU_IMPORT_URL, "Import &URL...");
228 fileMenu.addItem(MENU_IMPORT_FILE, "Import &file...");
229 fileMenu.addSeparator();
230 fileMenu.addItem(MENU_LIBRARY, "Lib&rary");
231 fileMenu.addSeparator();
232 fileMenu.addItem(MENU_EXIT, "E&xit");
233
234 setStatusBar(fileMenu, "File-management "
235 + "commands (Open, Save, Print, etc.)");
236
237 // TODO: Edit: re-download, delete
238
239 //
240
241 addWindowMenu();
242
243 getBackend().setTitle("Fanfix");
244 }
245
246 @Override
247 protected boolean onCommand(TCommandEvent command) {
248 if (command.getCmd().equals(TuiReaderMainWindow.CMD_SEARCH)) {
249 messageBox("title", "caption");
250 return true;
251 }
252 return super.onCommand(command);
253 }
254
255 @Override
256 protected boolean onMenu(TMenuEvent menu) {
257 // TODO: i18n
258 switch (menu.getId()) {
259 case MENU_EXIT:
260 close(this);
261 return true;
262 case MENU_OPEN:
263 String openfile = null;
264 try {
265 openfile = fileOpenBox(".");
266 reader.setMeta(BasicReader.getUrl(openfile), null);
267 read(false);
268 } catch (IOException e) {
269 // TODO: i18n
270 error("Fail to open file"
271 + (openfile == null ? "" : ": " + openfile),
272 "Import error", e);
273 }
274
275 return true;
276 case MENU_DELETE:
277 String luid = null;
278 String story = null;
279 MetaData meta = null;
280 if (main != null) {
281 meta = main.getSelectedMeta();
282 }
283 if (meta != null) {
284 luid = meta.getLuid();
285 story = luid + ": " + meta.getTitle();
286 }
287
288 // TODO: i18n
289 TMessageBox mbox = messageBox("Delete story", "Delete story \""
290 + story + "\"", Type.OKCANCEL);
291 if (mbox.getResult() == Result.OK) {
292 try {
293 reader.getLibrary().delete(luid);
294 if (main != null) {
295 main.refreshStories();
296 }
297 } catch (IOException e) {
298 // TODO: i18n
299 error("Fail to delete the story: \"" + story + "\"",
300 "Error", e);
301 }
302 }
303
304 return true;
305 case MENU_IMPORT_URL:
306 String clipboard = "";
307 try {
308 clipboard = ("" + Toolkit.getDefaultToolkit()
309 .getSystemClipboard().getData(DataFlavor.stringFlavor))
310 .trim();
311 } catch (Exception e) {
312 // No data will be handled
313 }
314
315 if (clipboard == null || !clipboard.startsWith("http")) {
316 clipboard = "";
317 }
318
319 String url = inputBox("Import story", "URL to import", clipboard)
320 .getText();
321
322 try {
323 if (!imprt(url)) {
324 // TODO: i18n
325 error("URK not supported: " + url, "Import error");
326 }
327 } catch (IOException e) {
328 // TODO: i18n
329 error("Fail to import URL: " + url, "Import error", e);
330 }
331
332 return true;
333 case MENU_IMPORT_FILE:
334 String filename = null;
335 try {
336 filename = fileOpenBox(".");
337 if (!imprt(filename)) {
338 // TODO: i18n
339 error("File not supported: " + filename, "Import error");
340 }
341 } catch (IOException e) {
342 // TODO: i18n
343 error("Fail to import file"
344 + (filename == null ? "" : ": " + filename),
345 "Import error", e);
346 }
347 return true;
348 case MENU_LIBRARY:
349 showMain();
350 return true;
351 }
352
353 return super.onMenu(menu);
354 }
355
356 /**
357 * Import the given url.
358 * <p>
359 * Can fail if the host is not supported.
360 *
361 * @param url
362 *
363 * @return TRUE in case of success, FALSE if the host is not supported
364 *
365 * @throws IOException
366 * in case of I/O error
367 */
368 private boolean imprt(String url) throws IOException {
369 try {
370 reader.getLibrary().imprt(BasicReader.getUrl(url), null);
371 main.refreshStories();
372 return true;
373 } catch (UnknownHostException e) {
374 return false;
375 }
376 }
377
378 @Override
379 public void openExternal(BasicLibrary lib, String luid, boolean sync)
380 throws IOException {
381 reader.openExternal(lib, luid, sync);
382 }
383
384 /**
385 * Display an error message and log it.
386 *
387 * @param message
388 * the message
389 * @param title
390 * the title of the error message
391 */
392 private void error(String message, String title) {
393 error(message, title, null);
394 }
395
396 /**
397 * Display an error message and log it, including the linked
398 * {@link Exception}.
399 *
400 * @param message
401 * the message
402 * @param title
403 * the title of the error message
404 * @param e
405 * the exception to log if any (can be NULL)
406 */
407 private void error(String message, String title, Exception e) {
408 Instance.getTraceHandler().error(title + ": " + message);
409 if (e != null) {
410 Instance.getTraceHandler().error(e);
411 }
412
413 if (e != null) {
414 messageBox(title, message //
415 + "\n" + e.getMessage());
416 } else {
417 messageBox(title, message);
418 }
419 }
420
421 /**
422 * Ask the user and, if confirmed, close the {@link TApplication} this
423 * {@link TWidget} is running on.
424 * <p>
425 * This should result in the program terminating.
426 *
427 * @param widget
428 * the {@link TWidget}
429 */
430 static public void close(TWidget widget) {
431 close(widget.getApplication());
432 }
433
434 /**
435 * Ask the user and, if confirmed, close the {@link TApplication}.
436 * <p>
437 * This should result in the program terminating.
438 *
439 * @param app
440 * the {@link TApplication}
441 */
442 static void close(TApplication app) {
443 // TODO: i18n
444 if (app.messageBox("Confirmation", "(TODO: i18n) Exit application?",
445 TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
446 app.exit();
447 }
448 }
449 }