001package ch.gbrain.gwtstorage.manager; 002 003/* 004 * #%L 005 * GwtStorage 006 * %% 007 * Copyright (C) 2016 gbrain.ch 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import java.util.ArrayList; 024import java.util.Date; 025import java.util.List; 026import java.util.logging.Level; 027import java.util.logging.Logger; 028 029import org.fusesource.restygwt.client.JsonCallback; 030import org.fusesource.restygwt.client.Method; 031import org.fusesource.restygwt.client.Resource; 032 033import ch.gbrain.gwtstorage.model.StorageItem; 034import ch.gbrain.gwtstorage.model.StorageResource; 035 036import com.google.gwt.core.client.Callback; 037import com.google.gwt.core.client.GWT; 038import com.google.gwt.i18n.shared.DateTimeFormat; 039import com.google.gwt.i18n.shared.DateTimeFormat.PredefinedFormat; 040import com.google.gwt.json.client.JSONValue; 041import com.google.gwt.storage.client.Storage; 042import com.googlecode.gwtphonegap.client.PhoneGap; 043import com.googlecode.gwtphonegap.client.connection.Connection; 044import com.googlecode.gwtphonegap.client.file.DirectoryEntry; 045import com.googlecode.gwtphonegap.client.file.EntryBase; 046import com.googlecode.gwtphonegap.client.file.FileCallback; 047import com.googlecode.gwtphonegap.client.file.FileDownloadCallback; 048import com.googlecode.gwtphonegap.client.file.FileEntry; 049import com.googlecode.gwtphonegap.client.file.FileError; 050import com.googlecode.gwtphonegap.client.file.FileObject; 051import com.googlecode.gwtphonegap.client.file.FileReader; 052import com.googlecode.gwtphonegap.client.file.FileSystem; 053import com.googlecode.gwtphonegap.client.file.FileTransfer; 054import com.googlecode.gwtphonegap.client.file.FileTransferError; 055import com.googlecode.gwtphonegap.client.file.FileTransferProgressEvent; 056import com.googlecode.gwtphonegap.client.file.FileWriter; 057import com.googlecode.gwtphonegap.client.file.Flags; 058import com.googlecode.gwtphonegap.client.file.Metadata; 059import com.googlecode.gwtphonegap.client.file.ReaderCallback; 060import com.googlecode.gwtphonegap.client.file.WriterCallback; 061 062/** 063 * This class deals about writing and reading objects from Type StorageItem and 064 * as well loading application resource files (eg. video files) from the 065 * applications home base (server base). This is useful when running the App in 066 * the Web but as well in a Phonegap container as we could store the files 067 * locally (cache) when running in the phonegap container. 068 */ 069public class StorageManager 070{ 071 072 /** 073 * The remote base url of the application 074 */ 075 private String remoteAppBaseUrl = "http://www.exmaple.com/myapp/"; 076 077 public String getRemoteAppBaseUrl() 078 { 079 return remoteAppBaseUrl; 080 } 081 082 public void setRemoteAppBaseUrl(String baseUrl) 083 { 084 this.remoteAppBaseUrl = baseUrl; 085 logger.log(Level.INFO, "SetRemoteAppBaseUrl:" + baseUrl); 086 } 087 088 /** 089 * The default storage url in the application. This is the relative location 090 * from the applications base directory. 091 */ 092 private String storageUrl = "storage/v1/"; 093 094 public String getStorageUrl() 095 { 096 return storageUrl; 097 } 098 099 private String getLocalStorageUrl() 100 { 101 return storageUrl; 102 } 103 104 private String getRemoteStorageUrl() 105 { 106 return getRemoteAppBaseUrl() + storageUrl; 107 } 108 109 public void setStorageUrl(String storageUrl) 110 { 111 this.storageUrl = storageUrl; 112 logger.log(Level.INFO, "SetStorageUrl:" + storageUrl); 113 } 114 115 /** 116 * The directory we are going to create locally on the mobile device as cache 117 * directory when running in the phonegap container. 118 */ 119 private String cacheDirectory = "myApp"; 120 121 public String getCacheDirectory() 122 { 123 return cacheDirectory; 124 } 125 126 public void setCacheDirectory(String cacheDirectory) 127 { 128 if (this.cacheDirectory.equals(cacheDirectory)) return; // nothing to do it 129 // is the same as 130 // before 131 this.cacheDirectory = cacheDirectory; 132 this.cacheDirectoryEntry = null; 133 logger.log(Level.INFO, "SetCacheDirectory:" + cacheDirectory); 134 } 135 136 /** 137 * Needs to be enabled to perform any caching at all. 138 */ 139 private boolean cacheEnabled = true; 140 141 public void setCacheEnabled(boolean cacheEnabled) 142 { 143 this.cacheEnabled = cacheEnabled; 144 } 145 146 public boolean getCacheEnabled() 147 { 148 return this.cacheEnabled; 149 } 150 151 /** 152 * If enabled, the download of the big resource files for local caching (eg. 153 * videos) is only invoked if we are in a wlan network connected. Else it is 154 * taken from backend url always by need. 155 */ 156 private boolean wlanEnabled = true; 157 158 public void setWlanEnabled(boolean wlanEnabled) 159 { 160 this.wlanEnabled = wlanEnabled; 161 } 162 163 public boolean getWlanEnabled() 164 { 165 return this.wlanEnabled; 166 } 167 168 private Boolean lastCachingState = null; 169 170 private void logResourceCachingState(boolean state, String msg) 171 { 172 if (lastCachingState == null || lastCachingState != state) 173 { 174 logger.log(Level.INFO, msg); 175 } 176 lastCachingState = state; 177 } 178 179 /** 180 * Evaluates if resource caching is currently enabled at all. 181 * 182 * @return true if resouces shall be downloaded actually. 183 */ 184 private boolean isResourceCachingEnabled() 185 { 186 if (!phonegap.isPhoneGapDevice()) return false; 187 if (!this.getCacheEnabled()) 188 { 189 logResourceCachingState(false, "ResourceCaching is disabled"); 190 return false; 191 } 192 if (this.getWlanEnabled()) 193 { 194 // check connection state 195 if (phonegap.isPhoneGapDevice() && phonegap.getConnection().getType().equalsIgnoreCase(Connection.WIFI)) 196 { 197 logResourceCachingState(true, "Wlan requested and available, ResourceCaching enabled"); 198 return true; 199 } 200 logResourceCachingState(false, "Wlan requested but not available, ResourceCaching disabled"); 201 return false; 202 } 203 logResourceCachingState(true, "ResourceCaching enabled"); 204 return true; 205 } 206 207 /** 208 * Evaluates if Caching is possible at all on the given client device at the 209 * moment of evaluation. 210 * 211 * @return True if we have access to the local file system and therefore are able to provide a local resource caching. 212 */ 213 public boolean isResourceCachingPossible() 214 { 215 if (!phonegap.isPhoneGapDevice()) return false; 216 return true; 217 } 218 219 private PhoneGap phonegap; 220 221 private Logger logger; 222 223 private Storage localStorage = null; 224 225 /** 226 * Default constructor setting up the StorageManager all inclusive Logger and 227 * Phonegap references are setup locally 228 */ 229 public StorageManager() 230 { 231 this(null, null); 232 } 233 234 /** 235 * Constructor allowing to inject the Phonegap reference and a Logger 236 * 237 * @param phonegap Give the phonegap reference you have already. Give null if 238 * you want it to be treated here automatically. 239 * @param logger Give a logger reference or null if you want it to be treated 240 * here locally. 241 */ 242 public StorageManager(PhoneGap phonegap, Logger logger) 243 { 244 if (phonegap != null) 245 { 246 this.phonegap = phonegap; 247 } else 248 { 249 this.phonegap = GWT.create(PhoneGap.class); 250 } 251 if (logger != null) 252 { 253 this.logger = logger; 254 } else 255 { 256 this.logger = Logger.getLogger("StorageManager"); 257 } 258 getLocalStorage(); 259 } 260 261 /** 262 * Retrieve a reference to the local Storage (Browsers HTML5 key-value store) 263 * 264 * @return The local storage or null if not supported 265 */ 266 public Storage getLocalStorage() 267 { 268 if (localStorage == null) 269 { 270 localStorage = Storage.getLocalStorageIfSupported(); 271 if (localStorage == null) 272 { 273 logger.log(Level.SEVERE, "No LocalStorage available!!!!!!!!!!!!!"); 274 } 275 } 276 return localStorage; 277 } 278 279 /**************************************************************************************************************** 280 * Read / Write StorageItem to private local HTML5 storage 281 ****************************************************************************************************************/ 282 283 /** 284 * Write the given item to the local HTML5 storage. 285 * 286 * @param item The object to be serialized and stored under the ID within the 287 * local storage 288 * @return true if the write operation succeeded 289 */ 290 public boolean writeStorageItemToLocalStorage(StorageItem item) 291 { 292 if (item == null) return false; 293 try 294 { 295 JSONValue json = item.toJson(); 296 getLocalStorage().setItem(item.getStorageItemIdKey(), json.toString()); 297 writeStorageItemStorageTimeToLocalStorage(item); 298 logger.log(Level.INFO, "Local StorageItem written" + item.getLogId()); 299 return true; 300 } catch (Exception ex) 301 { 302 logger.log(Level.SEVERE, "Failure local write" + item.getLogId(), ex); 303 } 304 return false; 305 } 306 307 /** 308 * Read the item from the local html5 storage. 309 * 310 * @param item The item to be read from the local storage with the given ID as 311 * key. 312 * @return false if the read operation failed or nothing is found. 313 */ 314 public boolean readStorageItemFromLocalStorage(StorageItem item) 315 { 316 return readStorageItemFromLocalStorage(item, 0, 0); 317 } 318 319 /** 320 * Read the item from the local html5 storage and take the cacheTime in 321 * account 322 * 323 * @param item 324 * @param expectedVersion The minimum item version, not checked if <=0 325 * @param cacheTime If the item was stored longer than the cacheTime, it isn't 326 * accepted, not checked if <=0 327 * @return false if the read operation failed or nothing is found, the version 328 * wasn't ok or the cacheTime elapsed already 329 */ 330 private boolean readStorageItemFromLocalStorage(StorageItem item, int expectedVersion, int cacheTime) 331 { 332 if (item == null) return false; 333 try 334 { 335 String val = getLocalStorage().getItem(item.getStorageItemIdKey()); 336 if (val != null) 337 { 338 logger.log(Level.INFO, "Local StorageItem found" + item.getLogId()); 339 item.fromJson(val); 340 // check if the version is ok 341 if (expectedVersion > 0) 342 { 343 if (!checkStorageItemVersion(expectedVersion, item.getVersion())) 344 { 345 logger.log(Level.INFO, "Local StorageItem version mismatch" + item.getLogId()); 346 return false; 347 } 348 } 349 // check if cache is valid 350 if (cacheTime > 0) 351 { 352 Date storeTime = readStorageItemStorageTimeFromLocalStorage(item); 353 if (storeTime != null) 354 { // there was a time available, so compare it 355 Date nowTime = new Date(); 356 if (nowTime.getTime() - (cacheTime * 1000) > storeTime.getTime()) 357 { // elapsed 358 logger.log(Level.INFO, "Local StorageItem time elapsed" + item.getLogId()); 359 return false; 360 } 361 } 362 } 363 logger.log(Level.INFO, "Local readStorageItem complete" + item.getLogId()); 364 return true; 365 } else 366 { 367 logger.log(Level.INFO, "Local readStorageItem not found" + item.getLogId()); 368 } 369 } catch (Exception ex) 370 { 371 logger.log(Level.SEVERE, "Exception local readStorageItem" + item.getLogId(), ex); 372 } 373 return false; 374 } 375 376 /** 377 * Write the items Date/Time store value to html5 storage for later usage in 378 * relation to the cache time 379 * 380 * @param item The storage time for the given item (ID) is written to the 381 * key-value HTML5 storage. 382 * @return false if the read operation failed or nothing is found. 383 */ 384 private void writeStorageItemStorageTimeToLocalStorage(StorageItem item) 385 { 386 if (item == null || item.getStorageItemTimeKey() == null) return; 387 try 388 { 389 String saveTime = DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_FULL).format(new Date()); 390 getLocalStorage().setItem(item.getStorageItemTimeKey(), saveTime); 391 } catch (Exception ex) 392 { 393 logger.log(Level.SEVERE, "Exception local writeStorageItem time" + item.getLogId(), ex); 394 } 395 } 396 397 /** 398 * Read the items Date/Time store value from html5 storage. 399 * 400 * @param item The time when a certain StorageItem was written to the HTML5 401 * key-value storage is read. 402 * @return null if the read operation failed or nothing is found. 403 */ 404 private Date readStorageItemStorageTimeFromLocalStorage(StorageItem item) 405 { 406 if (item == null || item.getStorageItemTimeKey() == null) return null; 407 try 408 { 409 String val = getLocalStorage().getItem(item.getStorageItemTimeKey()); 410 if (val != null) 411 { 412 return DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_FULL).parse(val); 413 } 414 } catch (Exception ex) 415 { 416 logger.log(Level.SEVERE, "Exception local readStorageItem time" + item.getLogId(), ex); 417 } 418 return null; 419 } 420 421 /** 422 * Remove all StorageItem related keys from the LocalStorage, thus clear the 423 * cached json objects and references etc. 424 */ 425 public void clearStorageItems() 426 { 427 try 428 { 429 Storage storage = this.getLocalStorage(); 430 if (storage == null) return; 431 Integer len = storage.getLength(); 432 int index = 0; 433 for (int i = 0; i < len; i++) 434 { 435 String key = storage.key(index); 436 if (StorageItem.isStorageItemKey(key)) 437 { 438 logger.log(Level.INFO, "Remove cached StorageItem:" + key); 439 storage.removeItem(key); 440 } else 441 { 442 index++; 443 } 444 } 445 } catch (Exception ex) 446 { 447 logger.log(Level.SEVERE, "Execption clearing StorageItems", ex); 448 } 449 } 450 451 /**************************************************************************************************************** 452 * Read / Write StorageItems to local Files 453 ****************************************************************************************************************/ 454 455 /** 456 * Write the item to a local file 457 * 458 * @param item The StorageItem to be stored to the file system within the 459 * defined cache directory 460 * @param callback Is called once the asynch action completed or failed. 461 * @return false if the asynchronous action invocation failed. 462 */ 463 public boolean writeStorageItemToLocalFile(final StorageItem item, final Callback<StorageItem, StorageError> callback) 464 { 465 if (item == null) return false; 466 try 467 { 468 logger.log(Level.INFO, "local writeStorageItem invoked " + item.toString()); 469 return getLocalFileReference(getCacheDirectory(), item.getJsonFileName(), true, new FileCallback<FileEntry, StorageError>() 470 { 471 @Override 472 public void onSuccess(FileEntry entry) 473 { 474 logger.log(Level.INFO, "local writeStorageItem FileEntry successfully retrieved" + item.getLogId()); 475 // store the file content 476 writeStorageItemToLocalFile(entry, item, callback); 477 } 478 479 @Override 480 public void onFailure(StorageError error) 481 { 482 logger.log(Level.SEVERE, "Failure local writeStorageItem FileSystem creation" + item.getLogId() + " " + error.toString()); 483 } 484 }); 485 } catch (Exception ex) 486 { 487 logger.log(Level.SEVERE, "Exception local writeStorageItem " + item.getLogId(), ex); 488 if (callback != null) 489 { 490 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 491 } 492 } 493 return false; 494 } 495 496 /** 497 * Write the item to the local fileentry asynchronous 498 * 499 * @param fileEntry 500 * @param item 501 * @param callback Is called once the asynch action completed or failed 502 * @return false if the asynchronous action invocation failed. 503 */ 504 private boolean writeStorageItemToLocalFile(FileEntry fileEntry, final StorageItem item, final Callback<StorageItem, StorageError> callback) 505 { 506 if (item == null) return false; 507 try 508 { 509 logger.log(Level.INFO, "writeStorageItem to local file invoked" + item.getLogId()); 510 fileEntry.createWriter(new FileCallback<FileWriter, FileError>() 511 { 512 @Override 513 public void onSuccess(FileWriter writer) 514 { 515 writer.setOnWriteEndCallback(new WriterCallback<FileWriter>() 516 { 517 @Override 518 public void onCallback(FileWriter result) 519 { 520 // file written 521 logger.log(Level.INFO, "writeToLocalFile successfully written" + item.getLogId()); 522 if (callback != null) 523 { 524 callback.onSuccess(item); 525 } 526 } 527 }); 528 writer.setOnErrorCallback(new WriterCallback<FileWriter>() 529 { 530 @Override 531 public void onCallback(FileWriter result) 532 { 533 // Error while writing file 534 logger.log(Level.SEVERE, "Failure file write StorageItem" + item.getLogId() + " : " + result.toString()); 535 if (callback != null) 536 { 537 callback.onFailure(new StorageError(result.getError())); 538 } 539 } 540 }); 541 JSONValue json = item.toJson(); 542 writer.write(json.toString()); 543 } 544 545 @Override 546 public void onFailure(FileError error) 547 { 548 // can not create writer 549 logger.log(Level.SEVERE, "Failure file writer creation StorageItem" + item.getLogId() + " : " + error.toString()); 550 if (callback != null) 551 { 552 callback.onFailure(new StorageError(error)); 553 } 554 } 555 }); 556 return true; 557 } catch (Exception ex) 558 { 559 logger.log(Level.SEVERE, "Exception file write StorageItem" + item.toString(), ex); 560 if (callback != null) 561 { 562 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 563 } 564 } 565 return false; 566 } 567 568 /** 569 * Read the item from the Local File storage 570 * 571 * @param item The StorageItem (or inherited objects) to be read from the 572 * local cache file system location. 573 * @param callback Is called once the asynch action completed or failed 574 * @return false if the asynchronous action invocation failed. 575 */ 576 public boolean readStorageItemFromLocalFile(final StorageItem item, final Callback<StorageItem, StorageError> callback) 577 { 578 if (item == null) return false; 579 try 580 { 581 // get the file reference 582 return getLocalFileReference(getCacheDirectory(), item.getJsonFileName(), false, new FileCallback<FileEntry, StorageError>() 583 { 584 @Override 585 public void onSuccess(FileEntry entry) 586 { 587 logger.log(Level.INFO, "StorageItem File successfully retrieved" + item.getLogId()); 588 readStorageItemFromLocalFile(entry, item, callback); 589 } 590 591 @Override 592 public void onFailure(StorageError error) 593 { 594 logger.log(Level.SEVERE, "Failure LocalFileReference retrieval" + item.getLogId() + " : " + error.toString()); 595 if (callback != null) 596 { 597 callback.onFailure(error); 598 } 599 } 600 }); 601 } catch (Exception ex) 602 { 603 logger.log(Level.SEVERE, "Exception file write StorageItem" + item.getLogId(), ex); 604 if (callback != null) 605 { 606 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 607 } 608 } 609 return false; 610 } 611 612 /** 613 * Read the StorageItem from the given FileEntry and refresh the given 614 * CommonView 615 * 616 * @param fileEntry 617 * @param item 618 * @param callback Is called once the asynch action completed or failed 619 * @return false if the asynchronous action invocation failed. 620 */ 621 private boolean readStorageItemFromLocalFile(FileEntry fileEntry, final StorageItem item, final Callback<StorageItem, StorageError> callback) 622 { 623 if (item == null) return false; 624 try 625 { 626 // logger.log(Level.INFO,"readStorageItem from local file invoked" + 627 // item.getLogId()); 628 FileReader reader = phonegap.getFile().createReader(); 629 reader.setOnloadCallback(new ReaderCallback<FileReader>() 630 { 631 @Override 632 public void onCallback(FileReader result) 633 { 634 String json = result.getResult(); 635 // do something with the content 636 item.fromJson(json); 637 logger.log(Level.INFO, "readStorageItem from local file load completed for item" + item.getLogId()); 638 if (callback != null) 639 { 640 callback.onSuccess(item); 641 } 642 } 643 }); 644 reader.setOnErrorCallback(new ReaderCallback<FileReader>() 645 { 646 @Override 647 public void onCallback(FileReader result) 648 { 649 // error while reading file... 650 logger.log(Level.SEVERE, "Error StorageItem file writer reading" + item.getLogId() + " : " + result.toString()); 651 if (callback != null) 652 { 653 callback.onFailure(new StorageError(result.getError())); 654 } 655 } 656 }); 657 return true; 658 } catch (Exception ex) 659 { 660 logger.log(Level.SEVERE, "Exception file read StorageItem" + item.getLogId(), ex); 661 if (callback != null) 662 { 663 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 664 } 665 } 666 return false; 667 } 668 669 /** 670 * Our reference to the file system 671 */ 672 private FileSystem fileSystem = null; 673 674 /** 675 * Retrieve the FileEntry Reference on the local filesystem of the device if 676 * running in a local container eg. Phonegap 677 * 678 * @param directory 679 * @param filename 680 * @param callback is called once the asynch action completed or failed 681 * @return false if the asynchronous action invocation failed. 682 */ 683 private boolean getFileSystem(final FileCallback<FileSystem, StorageError> callback) 684 { 685 try 686 { 687 if (!phonegap.isPhoneGapDevice()) return false; 688 if (fileSystem != null) 689 { 690 if (callback != null) 691 { 692 callback.onSuccess(fileSystem); 693 } 694 return true; 695 } 696 logger.log(Level.INFO, "getFileReference - Request Local File System"); 697 phonegap.getFile().requestFileSystem(FileSystem.LocalFileSystem_PERSISTENT, 0, new FileCallback<FileSystem, FileError>() 698 { 699 @Override 700 public void onSuccess(FileSystem entry) 701 { 702 logger.log(Level.INFO, "FileSystem retrieved"); 703 fileSystem = entry; 704 if (callback != null) 705 { 706 callback.onSuccess(fileSystem); 707 } 708 } 709 710 @Override 711 public void onFailure(FileError error) 712 { 713 logger.log(Level.SEVERE, "Failure filesystem retrieval " + error.toString()); 714 if (callback != null) 715 { 716 callback.onFailure(new StorageError(error)); 717 } 718 } 719 }); 720 return true; 721 } catch (Exception ex) 722 { 723 logger.log(Level.SEVERE, "General failure FileSystem retrieval", ex); 724 if (callback != null) 725 { 726 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 727 } 728 } 729 return false; 730 } 731 732 /** 733 * Retrieve the FileEntry Reference on the local filesystem of the device if 734 * running in a local container eg. Phonegap 735 * 736 * @param directory 737 * @param filename 738 * @param callback is called once the asynch action completed or failed 739 * @return false if the asynchronous action invocation failed. 740 */ 741 private boolean getLocalFileReference(final String directory, final String filename, final boolean create, final FileCallback<FileEntry, StorageError> callback) 742 { 743 try 744 { 745 if (!phonegap.isPhoneGapDevice()) return false; 746 getFileSystem(new FileCallback<FileSystem, StorageError>() 747 { 748 @Override 749 public void onSuccess(FileSystem entry) 750 { 751 logger.log(Level.INFO, "getLocalFileReference - Request Local File System Directory for " + directory); 752 final DirectoryEntry root = entry.getRoot(); 753 root.getDirectory(directory, new Flags(create, false), new FileCallback<DirectoryEntry, FileError>() 754 { 755 @Override 756 public void onSuccess(DirectoryEntry entry) 757 { 758 logger.log(Level.INFO, "getLocalFileReference - Directory retrieved : " + directory); 759 entry.getFile(filename, new Flags(create, false), new FileCallback<FileEntry, FileError>() 760 { 761 @Override 762 public void onSuccess(FileEntry entry) 763 { 764 logger.log(Level.INFO, "getLocalFileReference - File retrieved : " + filename); 765 if (callback != null) 766 { 767 callback.onSuccess(entry); 768 } 769 } 770 771 @Override 772 public void onFailure(FileError error) 773 { 774 logger.log(Level.SEVERE, "Failure file retrieval " + filename + " " + error.toString()); 775 if (callback != null) 776 { 777 callback.onFailure(new StorageError(error)); 778 } 779 } 780 }); 781 } 782 783 @Override 784 public void onFailure(FileError error) 785 { 786 logger.log(Level.SEVERE, "Failure directory retrieval " + directory + " : " + error.toString()); 787 } 788 }); 789 } 790 791 @Override 792 public void onFailure(StorageError error) 793 { 794 logger.log(Level.SEVERE, "Failure filesystem retrieval " + error.toString()); 795 } 796 }); 797 return true; 798 } catch (Exception ex) 799 { 800 logger.log(Level.SEVERE, "General failure directory/file creator", ex); 801 if (callback != null) 802 { 803 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 804 } 805 } 806 return false; 807 } 808 809 /** 810 * Retrieve the FileEntry Reference on the local filesystem of the device if 811 * running in a local container eg. Phonegap 812 * 813 * @param directory The directory which we want to get a reference for. It will be created if it doesn't exist yet. 814 * It is based on the Filesystem reference. 815 * @param callback is called once the asynch action completed or failed 816 * @return false if the asynchronous action invocation failed. 817 */ 818 private boolean getLocalDirectoryEntry(final String directory, final FileCallback<DirectoryEntry, StorageError> callback) 819 { 820 try 821 { 822 if (!phonegap.isPhoneGapDevice()) return false; 823 getFileSystem(new FileCallback<FileSystem, StorageError>() 824 { 825 @Override 826 public void onSuccess(FileSystem entry) 827 { 828 logger.log(Level.INFO, "getLocalDirectoryEntry - FileSystem retrieved"); 829 final DirectoryEntry root = entry.getRoot(); 830 root.getDirectory(directory, new Flags(true, false), new FileCallback<DirectoryEntry, FileError>() 831 { 832 @Override 833 public void onSuccess(final DirectoryEntry dirEntry) 834 { 835 logger.log(Level.INFO, "getLocalDirectoryEntry - Directory retrieved : " + directory); 836 if (callback != null) 837 { 838 callback.onSuccess(dirEntry); 839 } 840 } 841 842 @Override 843 public void onFailure(FileError error) 844 { 845 logger.log(Level.SEVERE, "Failure directory retrieval " + directory + " : " + error.toString() + " : " + error.getErrorCode()); 846 if (callback != null) 847 { 848 callback.onFailure(new StorageError(error)); 849 } 850 } 851 }); 852 } 853 854 @Override 855 public void onFailure(StorageError error) 856 { 857 logger.log(Level.SEVERE, "Failure filesystem retrieval " + error.toString() + " : " + error.getErrorCode()); 858 if (callback != null) 859 { 860 callback.onFailure(error); 861 } 862 } 863 }); 864 return true; 865 } catch (Exception ex) 866 { 867 logger.log(Level.SEVERE, "Exception in getLocalDirectory : " + directory, ex); 868 if (callback != null) 869 { 870 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 871 } 872 } 873 return false; 874 } 875 876 /**************************************************************************************************************** 877 * Read StorageItem from URL path 878 ****************************************************************************************************************/ 879 880 /** 881 * Retrieve the item from the storage. First try local storage, if the version 882 * and the validTime are valid, it is returned. Else it tries to retrieve it 883 * from the remote backend. If found, it is cached locally for fast access. 884 * 885 * @param item The item to be read by ID from 1. the cache, 2. localAppPath 3. 886 * remoteAppPath in this priority order 887 * @param useCache If true, the system will first try to retrieve the value 888 * from the local cache before it reads the same from the 889 * applications path 890 * @param validTime The maximum age in seconds of the cache to be accepted as 891 * a valid item value, if elapsed it will try to read from the 892 * applications path / If <= 0 don't care 893 * @param expectedVersion The versionNumber which must be available in the 894 * cache to be a valid cache item. If <=0 don't care. 895 */ 896 public boolean readStorageItem(final StorageItem item, boolean useCache, int expectedVersion, int validTime, final Callback<StorageItem, StorageError> callback) 897 { 898 try 899 { 900 logger.log(Level.INFO, "readStorageItem" + item.getLogId()); 901 if (useCache && this.getCacheEnabled()) 902 { // retrieve the item first from local storage cache 903 if (this.readStorageItemFromLocalStorage(item, expectedVersion, validTime)) 904 { // found it valid in the cache 905 callback.onSuccess(item); 906 return true; 907 } 908 } 909 // didn't found a matching item in the cache yet or version mismatch or 910 // cache time elapsed 911 if (phonegap.isPhoneGapDevice()) 912 { 913 // we run in a locally installed app and want to retrieve now the value 914 // from the given backend 915 return this.readStorageItemFromRemoteApplication(item, callback); 916 } else 917 { // in the case of web app, load it from the applications relative base path 918 // this is automatically from the backend server where the app was 919 // loaded from 920 return this.readStorageItemFromLocalApplication(item, callback); 921 } 922 } catch (Exception ex) 923 { 924 logger.log(Level.SEVERE, "Exception readStorageItem" + item.getLogId(), ex); 925 if (callback != null) 926 { 927 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 928 } 929 } 930 return false; 931 } 932 933 /** 934 * Read the item asynch from the applications own source server with the given 935 * credentials and base path eg. 936 * /storage/v1/ch.gbrain.testapp.model.items-1.json Note: We just give the 937 * local relative url which means: - If running as local application (eg. 938 * started locally in Browser) it will load from local base - If running as 939 * web application it will load from the servers base - If running as Phonegap 940 * app it will load from the local installed base 941 * 942 * @param item 943 * @param callback is called once the asynch action completed or failed 944 * @return false if the asynchronous action invocation failed. 945 */ 946 public boolean readStorageItemFromLocalApplication(final StorageItem item, final Callback<StorageItem, StorageError> callback) 947 { 948 // we run in the web directly and therefore we read it directly from the 949 // application relative storage in the Webapp itself 950 logger.log(Level.INFO, "Read StorageItem from local applications base" + item.getLogId()); 951 return readStorageItemFromUrl(this.getLocalStorageUrl(), item, getReadStorageItemHandler(item, callback, null)); 952 // for testing in browser use this. But Chrome must run without security to 953 // work 954 // return readFromUrl(this.appRemoteStorageUrl,item,callback); 955 } 956 957 /** 958 * Compare the given versions. 959 * 960 * @param expectedVersion The version we do expect at least / if <=0, we don't 961 * care about versions, it is always ok 962 * @param realVersion The real version of the item 963 * @return true if the realversion >= expectedVersion 964 */ 965 private boolean checkStorageItemVersion(int expectedVersion, int realVersion) 966 { 967 if (expectedVersion <= 0) return true; // the version doesn't care 968 if (realVersion >= expectedVersion) return true; 969 return false; 970 } 971 972 /** 973 * Read the item asynch from the configured remote application base 974 * 975 * @param item 976 * @param callback is called once the asynch action completed or failed 977 * @return false if the asynchronous action invocation failed. 978 */ 979 public boolean readStorageItemFromRemoteApplication(final StorageItem item, final Callback<StorageItem, StorageError> callback) 980 { 981 logger.log(Level.INFO, "Read StorageItem from remote application base" + item.getLogId()); 982 return readStorageItemFromUrl(this.getRemoteStorageUrl(), item, getReadStorageItemHandler(item, callback, this.getLocalStorageUrl())); 983 } 984 985 /** 986 * Creates and returns a Callback which treats the result for a url Item 987 * retrieval 988 * 989 * @param item The StorageItem (or a inheriting object) which must be read 990 * (filled in with the retrieved data) 991 * @param callback The final resp. initial callback to be notified of the 992 * result 993 * @param fallBack A URL to which a further request must be done if the call 994 * fails 995 * @return The callback which deals with the asynch result of the remote item 996 * retrieval 997 */ 998 private Callback<StorageItem, StorageError> getReadStorageItemHandler(final StorageItem item, final Callback<StorageItem, StorageError> callback, final String fallbackUrl) 999 { 1000 return new Callback<StorageItem, StorageError>() 1001 { 1002 public void onSuccess(StorageItem newItem) 1003 { // loading succeeded 1004 // store it in the cache 1005 logger.log(Level.INFO, "Completed read item from url" + item.getLogId()); 1006 writeStorageItemToLocalStorage(newItem); 1007 callback.onSuccess(newItem); 1008 } 1009 1010 public void onFailure(StorageError error) 1011 { 1012 logger.log(Level.WARNING, "Failure url loading" + item.getLogId()); 1013 // nothing found, check if we must retrieve it from a remote location 1014 if (fallbackUrl != null && !fallbackUrl.isEmpty()) 1015 { 1016 readStorageItemFromUrl(fallbackUrl, item, getReadStorageItemHandler(item, callback, null)); 1017 } else 1018 { 1019 callback.onFailure(error); 1020 } 1021 } 1022 }; 1023 } 1024 1025 /** 1026 * Read the JSON item asynch from the given url and the 1027 * standard name of the item eg. ch.gbrain.testapp.model.items-1.json 1028 * 1029 * @param url The url to read from eg. for local application relative path 1030 * "storage/v1/" eg. for remote location 1031 * "http://host.domain.ch/testapp/storage/v1/" 1032 * @param item 1033 * @param callback is called once the asynch action completed or failed 1034 * @return false if the asynchronous action invocation failed. 1035 */ 1036 public boolean readStorageItemFromUrl(String url, final StorageItem item, final Callback<StorageItem, StorageError> callback) 1037 { 1038 if (item == null) return false; 1039 try 1040 { 1041 Resource resource = new Resource(url + item.getJsonFileName() + "?noCache=" + new Date().getTime()); 1042 Method method = resource.get(); 1043 /** 1044 * if (username.isEmpty()) { method = resource.get(); }else { method = 1045 * resource.get().user(username).password(password); } 1046 */ 1047 logger.log(Level.INFO, "Read from url:" + method.builder.getUrl()); 1048 method.send(new JsonCallback() 1049 { 1050 public void onSuccess(Method method, JSONValue response) 1051 { 1052 logger.log(Level.INFO, "Read from url success"); 1053 if (response != null) 1054 { 1055 try 1056 { 1057 logger.log(Level.INFO, "Successfully url read" + item.getLogId()); 1058 item.fromJson(response); 1059 if (callback != null) 1060 { 1061 callback.onSuccess(item); 1062 } 1063 } catch (Exception ex) 1064 { 1065 logger.log(Level.SEVERE, "Failure url read" + item.getLogId(), ex); 1066 } 1067 } 1068 } 1069 1070 public void onFailure(Method method, Throwable exception) 1071 { 1072 logger.log(Level.WARNING, "Failure url read" + item.getLogId(), exception); 1073 if (callback != null) 1074 { 1075 callback.onFailure(new StorageError(FileError.NOT_READABLE_ERR, exception.getMessage())); 1076 } 1077 } 1078 }); 1079 logger.log(Level.INFO, "Read from url call complete"); 1080 return true; 1081 } catch (Exception ex) 1082 { 1083 logger.log(Level.SEVERE, "Error url read" + item.getLogId(), ex); 1084 if (callback != null) 1085 { 1086 callback.onFailure(new StorageError(FileError.ABORT_ERR)); 1087 } 1088 } 1089 return false; 1090 } 1091 1092 /**************************************************************************************************************** 1093 * Read Resource from URL path 1094 ****************************************************************************************************************/ 1095 1096 /** 1097 * Retrieve the url of the resource on the remote server based on the current 1098 * runtime environement 1099 * 1100 * @param relativeUrl relative url of the resource according the app base 1101 * @return The url pointing to the resource dependent on if it is running in 1102 * Phonegap container or Webbrowser 1103 * 1104 */ 1105 public String getRemoteResourceUrl(String relativeUrl) 1106 { 1107 if (phonegap.isPhoneGapDevice()) 1108 { 1109 return getRemoteAppBaseUrl() + relativeUrl; 1110 } else 1111 { 1112 return relativeUrl; 1113 } 1114 } 1115 1116 /** 1117 * Evaluates in case of the runtime (browser/phonegap) the full url where a 1118 * resource must be retrieved from. In case of Phonegap, it will check if we 1119 * have the resource already locally stored in the cache and return a url 1120 * pointing to this one instead. 1121 * 1122 * @param relativeUrl of the resource as it is available in the application 1123 * itself. 1124 * @param version Check if the version of the stored resource equals. Not 1125 * checked if version=0 1126 * @return The full url where the resource must be retrieved from (either 1127 * points to the home server of the application as configured or it 1128 * points to the found local cached resource file. 1129 */ 1130 public boolean retrieveResourceUrl(final String relativeUrl, Integer version, final Callback<String, FileError> callback) 1131 { 1132 try 1133 { 1134 if (relativeUrl == null || relativeUrl.isEmpty()) 1135 { 1136 if (callback != null) 1137 { 1138 logger.log(Level.INFO, "Web ResourceCacheReference retrieval impossible with invalid URL : " + relativeUrl); 1139 callback.onFailure(new StorageError(FileError.SYNTAX_ERR, "Invalid Url given : " + relativeUrl)); 1140 } 1141 return false; 1142 } 1143 if (!phonegap.isPhoneGapDevice()) 1144 { 1145 if (callback != null) 1146 { 1147 logger.log(Level.INFO, "Web ResourceCacheReference retrieval : " + relativeUrl); 1148 callback.onSuccess(relativeUrl); 1149 } 1150 return true; 1151 } 1152 // check if we have a cached resource (eg. with a corresponding cache item 1153 // in the storage) 1154 StorageResource resource = new StorageResource(relativeUrl, version); 1155 Boolean checkVersion = checkResourceVersion(resource); 1156 if (checkVersion == null) 1157 { 1158 logger.log(Level.INFO, "No resource cache item found for : " + relativeUrl + " / version:" + version); 1159 if (callback != null) 1160 { 1161 callback.onFailure(new StorageError(FileError.NOT_FOUND_ERR, "No resource cache item found")); 1162 } 1163 } else if (checkVersion == true) 1164 { 1165 // it should be there already and version is ok 1166 logger.log(Level.INFO, "Successful ResourceCacheReference retrieval : " + relativeUrl + " / version=" + version); 1167 getCacheDirectoryEntry(new Callback<DirectoryEntry, StorageError>() 1168 { 1169 public void onSuccess(DirectoryEntry dirEntry) 1170 { 1171 if (callback != null) 1172 { 1173 String localResourceUrl = dirEntry.toURL() + "/" + convertFilePathToFileName(relativeUrl); 1174 logger.log(Level.INFO, "Successful ResourceCacheUrl evaluation : " + localResourceUrl); 1175 callback.onSuccess(localResourceUrl); 1176 } 1177 } 1178 1179 public void onFailure(StorageError error) 1180 { 1181 logger.log(Level.WARNING, "Failure in ResourceCacheUrl evaluation : " + relativeUrl + " error:" + error.getErrorCode()); 1182 if (callback != null) 1183 { 1184 callback.onFailure(error); 1185 } 1186 } 1187 }); 1188 return true; 1189 } else 1190 { 1191 logger.log(Level.INFO, "No matching resource cache item found for : " + relativeUrl + "version:" + version); 1192 } 1193 } catch (Exception ex) 1194 { 1195 logger.log(Level.SEVERE, "Exception resourceUrl evaluation for : " + relativeUrl, ex); 1196 } 1197 return false; 1198 } 1199 1200 /** 1201 * Create from a url a proper filename which could be stored in the filesystem 1202 * 1203 * @param filePath 1204 * @return The filename with all problematic characters replaced with working 1205 * ones. 1206 */ 1207 private String convertFilePathToFileName(String filePath) 1208 { 1209 return filePath.replace("/", "@@"); 1210 } 1211 1212 /** 1213 * Download the given resource url and store it in the local cache Directory. 1214 * 1215 * @param resource 1216 * @param destinationDir The URL of the destination Directoy 1217 */ 1218 private void downloadCacheResource(final StorageResource resource) 1219 { 1220 try 1221 { 1222 if (resource == null) return; 1223 logger.log(Level.INFO, "downloadCacheResource " + resource.getResourceUrl() + " Version:" + resource.getVersion()); 1224 getCacheDirectoryEntry(new Callback<DirectoryEntry, StorageError>() 1225 { 1226 public void onSuccess(DirectoryEntry cacheDir) 1227 { 1228 try 1229 { 1230 FileTransfer fileTransfer = phonegap.getFile().createFileTransfer(); 1231 String localFileName = convertFilePathToFileName(resource.getResourceUrl()); 1232 String sourceUrl = getRemoteAppBaseUrl() + resource.getResourceUrl(); 1233 String destUrl = cacheDir.toURL() + localFileName; 1234 // String destUrl = 1235 // "cdvfile://localhost/persistent/testapp/test.mp4"; 1236 logger.log(Level.INFO, "downloadResource invoked for : " + sourceUrl + " to : " + destUrl); 1237 fileTransfer.download(sourceUrl, destUrl, getResourceDownloadHandler(resource)); 1238 } catch (Exception lex) 1239 { 1240 logger.log(Level.SEVERE, "Exception in downloadCacheResource success handler", lex); 1241 } 1242 } 1243 1244 public void onFailure(StorageError error) 1245 { 1246 logger.log(Level.WARNING, "Failed to download CacheResource for : " + resource.getResourceUrl()); 1247 } 1248 }); 1249 } catch (Exception ex) 1250 { 1251 logger.log(Level.SEVERE, "Exception resourceDownload for : " + resource.getResourceUrl(), ex); 1252 } 1253 } 1254 1255 /** 1256 * Creates and returns a Callback which treats the result for a url resource 1257 * retrieval The just downloaded resource is registered in the local storage 1258 * with the version for future cache handling 1259 * 1260 * @return The callback which deals with the asynch result of the remote 1261 * resource retrieval 1262 */ 1263 private FileDownloadCallback getResourceDownloadHandler(final StorageResource resource) 1264 { 1265 return new FileDownloadCallback() 1266 { 1267 public void onSuccess(FileEntry fileEntry) 1268 { 1269 try 1270 { 1271 logger.log(Level.INFO, "FileDownload success " + fileEntry.getFullPath() + " for resource=" + resource.getResourceUrl() + " version=" + resource.getVersion()); 1272 // register now in the storage the version for the cache checks in the 1273 // future 1274 getLocalStorage().setItem(resource.getResourceIdKey(), fileEntry.toURL()); 1275 getLocalStorage().setItem(resource.getResourceVersionKey(), resource.getVersion().toString()); 1276 downloadInProgress = false; 1277 checkNextCacheResource(); 1278 } catch (Exception lex) 1279 { 1280 logger.log(Level.SEVERE, "Exception on cacheResource download success handler", lex); 1281 } 1282 } 1283 1284 public void onProgress(FileTransferProgressEvent progress) 1285 { 1286 // logger.log(Level.INFO,"FileDownload Progress " + 1287 // progress.getLoadedBytes()); 1288 } 1289 1290 public void onFailure(FileTransferError error) 1291 { 1292 logger.log(Level.SEVERE, "FileDownload Failure " + error.toString() + " : " + resource.getResourceUrl()); 1293 downloadInProgress = false; 1294 checkNextCacheResource(); 1295 } 1296 }; 1297 } 1298 1299 /** 1300 * Check if the version registered in the cache does match this resources 1301 * version 1302 * 1303 * @return true if the version matches the cache, false if not. If there was 1304 * no cache yet, returns null 1305 */ 1306 public Boolean checkResourceVersion(StorageResource resource) 1307 { 1308 try 1309 { 1310 // check if we have a cached resource (eg. with a corresponding cache item 1311 // in the storage) 1312 String cachedResourceVersion = getLocalStorage().getItem(resource.getResourceVersionKey()); 1313 if (cachedResourceVersion != null) 1314 { 1315 Integer cachedVersion = Integer.parseInt(cachedResourceVersion); 1316 Integer resourceVersion = resource.getVersion(); 1317 if (resourceVersion == null || resourceVersion == 0 || cachedVersion == resourceVersion) 1318 { 1319 return true; 1320 } 1321 logger.log(Level.WARNING, "Resource version mismatch:" + resource.getResourceUrl() + " version:" + resource.getVersion() + " cachedVersion:" + cachedResourceVersion); 1322 return false; 1323 } 1324 // there was obviously no cache 1325 return null; 1326 } catch (Exception ex) 1327 { 1328 logger.log(Level.SEVERE, "Exception checking resource version:" + resource.getResourceUrl() + " version:" + resource.getVersion(), ex); 1329 } 1330 // something went wrong, we have not found a compatible version therefore 1331 return false; 1332 } 1333 1334 /** 1335 * Remove all ResourceItem related keys from the LocalStorage and as well 1336 * related resource files, ClearCache 1337 */ 1338 public void clearResourceItems() 1339 { 1340 try 1341 { 1342 Storage storage = this.getLocalStorage(); 1343 if (storage == null) return; 1344 Integer len = storage.getLength(); 1345 Integer index = 0; 1346 for (int i = 0; i < len; i++) 1347 { 1348 String key = storage.key(index); 1349 if (StorageResource.isResourceIdKey(key)) 1350 { 1351 logger.log(Level.INFO, "Remove cached ResourceId : " + key); 1352 final String fullFileUrl = storage.getItem(key); 1353 storage.removeItem(key); 1354 // now remove the corresponding file asynch 1355 phonegap.getFile().resolveLocalFileSystemURI(fullFileUrl, new FileCallback<EntryBase, FileError>() 1356 { 1357 @Override 1358 public void onSuccess(EntryBase entry) 1359 { 1360 try 1361 { 1362 logger.log(Level.INFO, "Remove resource file:" + entry.getAsFileEntry().getFullPath()); 1363 entry.getAsFileEntry().remove(new FileCallback<Boolean, FileError>() 1364 { 1365 @Override 1366 public void onSuccess(Boolean entry) 1367 { 1368 logger.log(Level.INFO, "Successfully deleted file:" + fullFileUrl); 1369 } 1370 1371 @Override 1372 public void onFailure(FileError error) 1373 { 1374 logger.log(Level.WARNING, "Unable to delete File:" + fullFileUrl + " error:" + error.getErrorCode()); 1375 } 1376 }); 1377 } catch (Exception successEx) 1378 { 1379 logger.log(Level.WARNING, "Remove resource file failed:" + entry.getAsFileEntry().getFullPath(), successEx); 1380 } 1381 } 1382 1383 @Override 1384 public void onFailure(FileError error) 1385 { 1386 logger.log(Level.WARNING, "Unable to locate File for deletion:" + fullFileUrl + " error:" + error.getErrorCode()); 1387 } 1388 }); 1389 } else if (StorageResource.isResourceVersionKey(key)) 1390 { 1391 logger.log(Level.INFO, "Remove cached ResourceVersion : " + key); 1392 storage.removeItem(key); 1393 } else 1394 { 1395 index++; 1396 } 1397 } 1398 } catch (Exception ex) 1399 { 1400 logger.log(Level.SEVERE, "Execption clearing Resources", ex); 1401 } 1402 } 1403 1404 private List<StorageResource> cacheResourceQueue = new ArrayList<StorageResource>(); 1405 private boolean downloadInProgress = false; 1406 private DirectoryEntry cacheDirectoryEntry = null; 1407 1408 private boolean getCacheDirectoryEntry(final Callback<DirectoryEntry, StorageError> callback) 1409 { 1410 try 1411 { 1412 if (cacheDirectoryEntry != null && callback != null) 1413 { 1414 callback.onSuccess(cacheDirectoryEntry); 1415 return true; 1416 } 1417 if (!phonegap.isPhoneGapDevice()) return false; 1418 String cacheDir = getCacheDirectory(); 1419 getLocalDirectoryEntry(cacheDir, new FileCallback<DirectoryEntry, StorageError>() 1420 { 1421 @Override 1422 public void onSuccess(DirectoryEntry entry) 1423 { 1424 logger.log(Level.INFO, "CacheDirectory successfully retrieved with path:" + entry.getFullPath()); 1425 cacheDirectoryEntry = entry; 1426 if (callback != null) 1427 { 1428 callback.onSuccess(entry); 1429 } 1430 } 1431 1432 @Override 1433 public void onFailure(StorageError error) 1434 { 1435 logger.log(Level.SEVERE, "Failure Cache FileSystem Directory retrieval" + " : " + error.toString()); 1436 // stop the whole stuff, it doesn't work at all, we don't continue 1437 // here. Caching will not work therefore 1438 if (callback != null) 1439 { 1440 callback.onFailure(error); 1441 } 1442 } 1443 }); 1444 return true; 1445 } catch (Exception ex) 1446 { 1447 logger.log(Level.SEVERE, "Exception Cache FileSystem Directory retrieval", ex); 1448 } 1449 return false; 1450 } 1451 1452 /** 1453 * Check if there are further resources to be downloaded into the cache 1454 * 1455 * Note: GWT compiler will ignore the synchronized here, I leave it to show 1456 * that we take care about. As JS is single threaded, there won't be any 1457 * concurrency issues anyhow, so we don't have to take any more special 1458 * actions. 1459 */ 1460 private synchronized void checkNextCacheResource() 1461 { 1462 try 1463 { 1464 if (downloadInProgress) return; 1465 if (cacheResourceQueue.size() > 0) 1466 { 1467 downloadInProgress = true; 1468 StorageResource next = cacheResourceQueue.remove(0); 1469 downloadCacheResource(next); 1470 } 1471 } catch (Exception ex) 1472 { 1473 logger.log(Level.SEVERE, "Failure Downloading Cache Resource", ex); 1474 } finally 1475 { 1476 downloadInProgress = false; 1477 } 1478 } 1479 1480 /** 1481 * Check first if the resource with the given url and version is already 1482 * present, if not try to download the same in a sequential way asynchronously 1483 * 1484 * @param relativeUrl 1485 * @param version 1486 */ 1487 public void addResourceToCache(final String relativeUrl, final Integer version) 1488 { 1489 try 1490 { 1491 if (!this.isResourceCachingEnabled()) return; 1492 if (relativeUrl == null || relativeUrl.isEmpty()) return; 1493 // check if we have it already in the cache with the right version 1494 StorageResource resource = new StorageResource(relativeUrl, version); 1495 Boolean versionCheck = checkResourceVersion(resource); 1496 if (versionCheck == null) 1497 { 1498 logger.log(Level.WARNING, "ResourceCacheReference retrieval no chache entry found : " + relativeUrl + " requestedVersion:" + version + " -> invoke loading"); 1499 } else if (versionCheck == true) 1500 { 1501 // it should be there already and version is ok 1502 logger.log(Level.INFO, "Successful ResourceCacheReference retrieval : " + relativeUrl); 1503 // check if it really exists but asynch 1504 String fileName = convertFilePathToFileName(relativeUrl); 1505 this.getLocalFileReference(getCacheDirectory(), fileName, false, new FileCallback<FileEntry, StorageError>() 1506 { 1507 @Override 1508 public void onSuccess(FileEntry entry) 1509 { 1510 logger.log(Level.INFO, "Successful ResourceCacheFile retrieval : " + relativeUrl); 1511 // the cache is ok, file is there in right version, we don't have to 1512 // do something really. 1513 } 1514 1515 @Override 1516 public void onFailure(StorageError error) 1517 { 1518 logger.log(Level.SEVERE, "Failure ResourceCacheReference retrieval : " + relativeUrl + " : " + error.toString()); 1519 // nothing found, we must try to download again 1520 // add it to the list of downloadable cache entries 1521 cacheResourceQueue.add(new StorageResource(relativeUrl, version)); 1522 checkNextCacheResource(); 1523 } 1524 }); 1525 return; 1526 } else if (versionCheck == false) 1527 { 1528 // version doesn't match, invoke reload 1529 logger.log(Level.WARNING, "ResourceCacheReference retrieval version mismatch : " + relativeUrl + " requestedVersion:" + version + " -> invoke loading"); 1530 } 1531 // add it to the list of downloadable cache entries 1532 cacheResourceQueue.add(new StorageResource(relativeUrl, version)); 1533 checkNextCacheResource(); 1534 } catch (Exception ex) 1535 { 1536 logger.log(Level.SEVERE, "Exception addingResourceToCache", ex); 1537 } 1538 } 1539 1540 /** 1541 * Clear all cached items - key/value pairs in the LocalStorage - Related 1542 * files in the cache directory 1543 */ 1544 public void clearStorage() 1545 { 1546 try 1547 { 1548 this.clearResourceItems(); 1549 this.clearStorageItems(); 1550 } catch (Exception ex) 1551 { 1552 logger.log(Level.SEVERE, "Exception on Cache clearing", ex); 1553 } 1554 1555 } 1556 1557 /** 1558 * Retrieve all ResourceItems related keys from the LocalStorage 1559 * 1560 * @return The number of resources which will be evaluated and for which 1561 * callbacks have to be expected 1562 */ 1563 public int getAllCachedResourceItems(final Callback<StorageInfo, FileError> callback) 1564 { 1565 try 1566 { 1567 if (!this.isResourceCachingEnabled()) return 0; 1568 if (callback == null) return 0; 1569 logger.log(Level.INFO, "getAllCachedResourceItems"); 1570 Storage storage = this.getLocalStorage(); 1571 int len = storage.getLength(); 1572 int resCtr = 0; 1573 for (int i = 0; i < len; i++) 1574 { 1575 String key = storage.key(i); 1576 if (StorageResource.isResourceIdKey(key)) 1577 { 1578 logger.log(Level.INFO, "Read cached Resource : " + key); 1579 resCtr++; 1580 StorageInfoCollector collector = new StorageInfoCollector(key, callback); 1581 collector.collectInfo(); 1582 } 1583 } 1584 return resCtr; 1585 } catch (Exception ex) 1586 { 1587 logger.log(Level.SEVERE, "Execption reading all cached Resources", ex); 1588 } 1589 return 0; 1590 } 1591 1592 private class StorageInfoCollector 1593 { 1594 1595 private String storageKey; 1596 private String version; 1597 private String fileName; 1598 private String filePath; 1599 private String fileUrl; 1600 private Long fileSize; 1601 private Date lastModificationDate; 1602 private Callback<StorageInfo, FileError> callback; 1603 private FileEntry fileEntry; 1604 1605 public StorageInfoCollector(String storageKey, Callback<StorageInfo, FileError> callback) 1606 { 1607 this.storageKey = storageKey; 1608 this.callback = callback; 1609 } 1610 1611 private String logBaseInfo() 1612 { 1613 return "CollectInfo : " + storageKey + " / "; 1614 } 1615 1616 private void collectInfo() 1617 { 1618 Storage storage = getLocalStorage(); 1619 String versionKey = StorageResource.getResourceVersionKey(storageKey); 1620 version = storage.getItem(versionKey); 1621 fileUrl = storage.getItem(storageKey); 1622 // now resolve the file asynch 1623 phonegap.getFile().resolveLocalFileSystemURI(fileUrl, new FileCallback<EntryBase, FileError>() 1624 { 1625 @Override 1626 public void onSuccess(EntryBase entry) 1627 { 1628 logger.log(Level.INFO, logBaseInfo() + "ResolveLocalFileSystemUri success"); 1629 fileEntry = entry.getAsFileEntry(); 1630 fileEntry.getFile(new FileCallback<FileObject, FileError>() 1631 { 1632 @Override 1633 public void onSuccess(FileObject entry) 1634 { 1635 logger.log(Level.INFO, logBaseInfo() + "FileEntry located : " + entry.getFullPath() + " name:" + entry.getName()); 1636 fileName = entry.getName(); 1637 filePath = entry.getFullPath(); 1638 fileSize = entry.size(); 1639 try 1640 { // might throw an exception (unknown method in Phonegap ..... 1641 // lastModificationDate = entry.getLastModifiedDate(); -> take 1642 // it from Metadata instead 1643 } catch (Exception ex) 1644 { 1645 logger.log(Level.FINEST, logBaseInfo() + "Failure in File Modification Date evaluation", ex); 1646 } 1647 fileEntry.getMetadata(new FileCallback<Metadata, FileError>() 1648 { 1649 @Override 1650 public void onSuccess(Metadata metadata) 1651 { 1652 logger.log(Level.INFO, logBaseInfo() + "Successful FileMetadata located"); 1653 try 1654 { 1655 lastModificationDate = metadata.getModificationTime(); 1656 } catch (Exception ex) 1657 { 1658 logger.log(Level.FINEST, logBaseInfo() + "Failure in Metadata Modification Date evaluation", ex); 1659 } 1660 invokeSuccessCallback(); 1661 } 1662 1663 @Override 1664 public void onFailure(FileError error) 1665 { 1666 logger.log(Level.WARNING, logBaseInfo() + "Failure cache FileEntry Metadata retrieval with error : " + error.toString()); 1667 // anyhow signal success even if we don't have the Metadata 1668 invokeSuccessCallback(); 1669 } 1670 }); 1671 } 1672 1673 @Override 1674 public void onFailure(FileError error) 1675 { 1676 logger.log(Level.SEVERE, logBaseInfo() + "Failure cache FileEntry info retrieval with error : " + error.toString()); 1677 callback.onFailure(error); 1678 } 1679 }); 1680 } 1681 1682 @Override 1683 public void onFailure(FileError error) 1684 { 1685 logger.log(Level.WARNING, logBaseInfo() + "Unable to locate cache File information with error : " + error.getErrorCode()); 1686 if (callback != null) 1687 { 1688 callback.onFailure(error); 1689 } 1690 } 1691 }); 1692 } 1693 1694 private void invokeSuccessCallback() 1695 { 1696 StorageInfo info = new StorageInfo(); 1697 info.setFileName(this.fileName); 1698 info.setFilePath(filePath); 1699 info.setFileUrl(fileUrl); 1700 info.setFileSize(fileSize); 1701 info.setLastModificationDate(lastModificationDate); 1702 info.setStorageKey(storageKey); 1703 info.setVersion(version); 1704 callback.onSuccess(info); 1705 } 1706 1707 } 1708 1709}