1
22 package org.crosswire.jsword.book.install.sword;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.net.URI;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.zip.GZIPInputStream;
33
34 import org.crosswire.common.progress.JobManager;
35 import org.crosswire.common.progress.Progress;
36 import org.crosswire.common.util.CWProject;
37 import org.crosswire.common.util.CollectionUtil;
38 import org.crosswire.common.util.IOUtil;
39 import org.crosswire.common.util.Logger;
40 import org.crosswire.common.util.NetUtil;
41 import org.crosswire.common.util.Reporter;
42 import org.crosswire.jsword.JSMsg;
43 import org.crosswire.jsword.JSOtherMsg;
44 import org.crosswire.jsword.book.Book;
45 import org.crosswire.jsword.book.BookDriver;
46 import org.crosswire.jsword.book.BookException;
47 import org.crosswire.jsword.book.BookFilter;
48 import org.crosswire.jsword.book.BookFilterIterator;
49 import org.crosswire.jsword.book.BookMetaData;
50 import org.crosswire.jsword.book.BookSet;
51 import org.crosswire.jsword.book.basic.AbstractBookList;
52 import org.crosswire.jsword.book.install.InstallException;
53 import org.crosswire.jsword.book.install.Installer;
54 import org.crosswire.jsword.book.sword.ConfigEntry;
55 import org.crosswire.jsword.book.sword.SwordBook;
56 import org.crosswire.jsword.book.sword.SwordBookDriver;
57 import org.crosswire.jsword.book.sword.SwordBookMetaData;
58 import org.crosswire.jsword.book.sword.SwordBookPath;
59 import org.crosswire.jsword.book.sword.SwordConstants;
60
61 import com.ice.tar.TarEntry;
62 import com.ice.tar.TarInputStream;
63
64
72 public abstract class AbstractSwordInstaller extends AbstractBookList implements Installer, Comparable<AbstractSwordInstaller> {
73
84 protected abstract void download(Progress job, String dir, String file, URI dest) throws InstallException;
85
86
91 public String getInstallerDefinition() {
92 StringBuilder buf = new StringBuilder(host);
93 buf.append(',');
94 buf.append(packageDirectory);
95 buf.append(',');
96 buf.append(catalogDirectory);
97 buf.append(',');
98 buf.append(indexDirectory);
99 buf.append(',');
100 if (proxyHost != null) {
101 buf.append(proxyHost);
102 }
103 buf.append(',');
104 if (proxyPort != null) {
105 buf.append(proxyPort);
106 }
107 return buf.toString();
108 }
109
110
117 public boolean isNewer(Book book) {
118 File dldir = SwordBookPath.getSwordDownloadDir();
119
120 SwordBookMetaData sbmd = (SwordBookMetaData) book.getBookMetaData();
121 File conf = new File(dldir, sbmd.getConfPath());
122
123 if (!conf.exists()) {
126 return false;
127 }
128
129 URI configURI = NetUtil.getURI(conf);
130
131 URI remote = toRemoteURI(book);
132 return NetUtil.isNewer(remote, configURI, proxyHost, proxyPort);
133 }
134
135
140 public List<Book> getBooks() {
141 try {
142 if (!loaded) {
143 loadCachedIndex();
144 }
145
146 return new ArrayList<Book>(entries.values());
149 } catch (InstallException ex) {
150 log.error("Failed to reload cached index file", ex);
151 return new ArrayList<Book>();
152 }
153 }
154
155
160 public synchronized Book getBook(String name) {
161 for (Book book : getBooks()) {
164 if (name.equals(book.getName())) {
165 return book;
166 }
167 }
168
169 for (Book book : getBooks()) {
171 if (name.equalsIgnoreCase(book.getName())) {
172 return book;
173 }
174 }
175
176 for (Book book : getBooks()) {
179 BookMetaData bmd = book.getBookMetaData();
180 if (name.equals(bmd.getInitials())) {
181 return book;
182 }
183 }
184
185 for (Book book : getBooks()) {
187 if (name.equalsIgnoreCase(book.getInitials())) {
188 return book;
189 }
190 }
191 return null;
192 }
193
194
201 @Override
202 public synchronized List<Book> getBooks(BookFilter filter) {
203 List<Book> temp = CollectionUtil.createList(new BookFilterIterator(getBooks(), filter));
204 return new BookSet(temp);
205 }
206
207
214 public void install(Book book) {
215 final SwordBookMetaData sbmd = (SwordBookMetaData) book.getBookMetaData();
222
223 final Thread worker = new Thread("DisplayPreLoader")
227 {
228
233 @Override
234 public void run() {
235 String jobName = JSMsg.gettext("Installing book: {0}", sbmd.getName());
237 Progress job = JobManager.createJob(jobName, this);
238
239 job.beginJob(jobName);
241
242 yield();
243
244 URI temp = null;
245 try {
246 job.setSectionName(JSMsg.gettext("Initializing"));
248
249 temp = NetUtil.getTemporaryURI("swd", ZIP_SUFFIX);
250
251 download(job, packageDirectory, sbmd.getInitials() + ZIP_SUFFIX, temp);
252
253 job.setCancelable(false);
255 if (!job.isFinished()) {
256 File dldir = SwordBookPath.getSwordDownloadDir();
257 IOUtil.unpackZip(NetUtil.getAsFile(temp), dldir);
258 job.setSectionName(JSMsg.gettext("Copying config file"));
260 sbmd.setLibrary(NetUtil.getURI(dldir));
261 SwordBookDriver.registerNewBook(sbmd);
262 }
263
264 } catch (IOException e) {
265 Reporter.informUser(this, e);
266 job.cancel();
267 } catch (InstallException e) {
268 Reporter.informUser(this, e);
269 job.cancel();
270 } catch (BookException e) {
271 Reporter.informUser(this, e);
272 job.cancel();
273 } finally {
274 job.done();
275 if (temp != null) {
279 try {
280 NetUtil.delete(temp);
281 } catch (IOException e) {
282 log.warn("Error deleting temp download file:" + e.getMessage());
283 }
284 }
285 }
286 }
287 };
288
289 worker.setPriority(Thread.MIN_PRIORITY);
291 worker.start();
292 }
293
294
299 public void reloadBookList() throws InstallException {
300 String jobName = JSMsg.gettext("Downloading files");
302 Progress job = JobManager.createJob(jobName, Thread.currentThread());
303 job.beginJob(jobName);
304
305 try {
306 URI scratchfile = getCachedIndexFile();
307 download(job, catalogDirectory, FILE_LIST_GZ, scratchfile);
308 loaded = false;
309 } catch (InstallException ex) {
310 job.cancel();
311 throw ex;
312 } finally {
313 job.done();
314 }
315 }
316
317
324 public void downloadSearchIndex(Book book, URI localDest) throws InstallException {
325 String jobName = JSMsg.gettext("Downloading files");
327 Progress job = JobManager.createJob(jobName, Thread.currentThread());
328 job.beginJob(jobName);
329
330 try {
331 download(job, packageDirectory + '/' + SEARCH_DIR, book.getInitials() + ZIP_SUFFIX, localDest);
332 } catch (InstallException ex) {
333 job.cancel();
334 throw ex;
335 } finally {
336 job.done();
337 }
338 }
339
340
343 private void loadCachedIndex() throws InstallException {
344 BookDriver fake = SwordBookDriver.instance();
347
348 entries.clear();
349
350 URI cache = getCachedIndexFile();
351 if (!NetUtil.isFile(cache)) {
352 reloadBookList();
353 }
354
355 InputStream in = null;
356 GZIPInputStream gin = null;
357 TarInputStream tin = null;
358 try {
359 ConfigEntry.resetStatistics();
360
361 in = NetUtil.getInputStream(cache);
362 gin = new GZIPInputStream(in);
363 tin = new TarInputStream(gin);
364 while (true) {
365 TarEntry entry = tin.getNextEntry();
366 if (entry == null) {
367 break;
368 }
369
370 String internal = entry.getName();
371 if (!entry.isDirectory()) {
372 try {
373 int size = (int) entry.getSize();
374
375 if (size == 0) {
377 log.error("Empty entry: " + internal);
378 continue;
379 }
380
381 byte[] buffer = new byte[size];
382 if (tin.read(buffer) != size) {
383 log.error("Did not read all that was expected " + internal);
386 continue;
387 }
388
389 if (internal.endsWith(SwordConstants.EXTENSION_CONF)) {
390 internal = internal.substring(0, internal.length() - 5);
391 } else {
392 log.error("Not a SWORD config file: " + internal);
393 continue;
394 }
395
396 if (internal.startsWith(SwordConstants.DIR_CONF + '/')) {
397 internal = internal.substring(7);
398 }
399
400 SwordBookMetaData sbmd = new SwordBookMetaData(buffer, internal);
401 sbmd.setDriver(fake);
402 Book book = new SwordBook(sbmd, null);
403 entries.put(book.getName(), book);
404 } catch (IOException ex) {
405 log.error("Failed to load config for entry: " + internal, ex);
406 }
407 }
408 }
409
410 loaded = true;
411
412 ConfigEntry.dumpStatistics();
413 } catch (IOException ex) {
414 throw new InstallException(JSOtherMsg.lookupText("Error loading from cache"), ex);
415 } finally {
416 IOUtil.close(tin);
417 IOUtil.close(gin);
418 IOUtil.close(in);
419 }
420 }
421
422
425 public String getCatalogDirectory() {
426 return catalogDirectory;
427 }
428
429
433 public void setCatalogDirectory(String catologDirectory) {
434 this.catalogDirectory = catologDirectory;
435 }
436
437
440 public String getPackageDirectory() {
441 return packageDirectory;
442 }
443
444
448 public void setPackageDirectory(String newDirectory) {
449 if (packageDirectory == null || !packageDirectory.equals(newDirectory)) {
450 packageDirectory = newDirectory;
451 loaded = false;
452 }
453 }
454
455
458 public String getIndexDirectory() {
459 return indexDirectory;
460 }
461
462
466 public void setIndexDirectory(String indexDirectory) {
467 this.indexDirectory = indexDirectory;
468 }
469
470
473 public String getHost() {
474 return host;
475 }
476
477
481 public void setHost(String newHost) {
482 if (host == null || !host.equals(newHost)) {
483 host = newHost;
484 loaded = false;
485 }
486 }
487
488
491 public String getProxyHost() {
492 return proxyHost;
493 }
494
495
499 public void setProxyHost(String newProxyHost) {
500 String pHost = null;
501 if (newProxyHost != null && newProxyHost.length() > 0) {
502 pHost = newProxyHost;
503 }
504 if (proxyHost == null || !proxyHost.equals(pHost)) {
505 proxyHost = pHost;
506 loaded = false;
507 }
508 }
509
510
513 public Integer getProxyPort() {
514 return proxyPort;
515 }
516
517
521 public void setProxyPort(Integer newProxyPort) {
522 if (proxyPort == null || !proxyPort.equals(newProxyPort)) {
523 proxyPort = newProxyPort;
524 loaded = false;
525 }
526 }
527
528
531 protected URI getCachedIndexFile() throws InstallException {
532 try {
533 URI scratchdir = CWProject.instance().getWriteableProjectSubdir(getTempFileExtension(host, catalogDirectory), true);
534 return NetUtil.lengthenURI(scratchdir, FILE_LIST_GZ);
535 } catch (IOException ex) {
536 throw new InstallException(JSOtherMsg.lookupText("URL manipulation failed"), ex);
537 }
538 }
539
540
543 private static String getTempFileExtension(String host, String catalogDir) {
544 return DOWNLOAD_PREFIX + host + catalogDir.replace('/', '_');
545 }
546
547
552 @Override
553 public boolean equals(Object object) {
554 if (!(object instanceof AbstractSwordInstaller)) {
555 return false;
556 }
557 AbstractSwordInstaller that = (AbstractSwordInstaller) object;
558
559 if (!equals(this.host, that.host)) {
560 return false;
561 }
562
563 if (!equals(this.packageDirectory, that.packageDirectory)) {
564 return false;
565 }
566
567 return true;
568 }
569
570
575 public int compareTo(AbstractSwordInstaller myClass) {
576
577 int ret = host.compareTo(myClass.host);
578 if (ret != 0) {
579 ret = packageDirectory.compareTo(myClass.packageDirectory);
580 }
581 return ret;
582 }
583
584
589 @Override
590 public int hashCode() {
591 return host.hashCode() + packageDirectory.hashCode();
592 }
593
594
597 protected boolean equals(String string1, String string2) {
598 if (string1 == null) {
599 return string2 == null;
600 }
601 return string1.equals(string2);
602 }
603
604
607 protected Map<String, Book> entries = new HashMap<String, Book>();
608
609
612 protected String host;
613
614
617 protected String proxyHost;
618
619
622 protected Integer proxyPort;
623
624
627 protected String packageDirectory = "";
628
629
633 protected String catalogDirectory = "";
634
635
639 protected String indexDirectory = "";
640
641
644 protected boolean loaded;
645
646
649 protected static final String FILE_LIST_GZ = "mods.d.tar.gz";
650
651
654 protected static final String ZIP_SUFFIX = ".zip";
655
656
659 protected static final Logger log = Logger.getLogger(AbstractSwordInstaller.class);
660
661
664 protected static final String SEARCH_DIR = "search/jsword/L1";
665
666
669 protected static final String DOWNLOAD_PREFIX = "download-";
670
671 }
672