1 package org.coolreader.crengine; 2 3 import android.util.Log; 4 5 import org.coolreader.R; 6 import org.coolreader.plugins.OnlineStoreBook; 7 8 import java.io.File; 9 import java.util.ArrayList; 10 import java.util.Arrays; 11 import java.util.Collection; 12 import java.util.Collections; 13 import java.util.Comparator; 14 import java.util.zip.ZipEntry; 15 16 public class FileInfo { 17 18 public final static String RECENT_DIR_TAG = "@recent"; 19 public final static String SEARCH_RESULT_DIR_TAG = "@searchResults"; 20 public final static String ROOT_DIR_TAG = "@root"; 21 public final static String OPDS_LIST_TAG = "@opds"; 22 public final static String OPDS_DIR_PREFIX = "@opds:"; 23 public final static String ONLINE_CATALOG_PLUGIN_PREFIX = "@plugin:"; 24 public final static String GENRES_TAG = "@genresRoot"; 25 public final static String GENRES_GROUP_PREFIX = "@genresGroup:"; 26 public final static String GENRES_PREFIX = "@genre:"; 27 public final static String AUTHORS_TAG = "@authorsRoot"; 28 public final static String AUTHOR_GROUP_PREFIX = "@authorGroup:"; 29 public final static String AUTHOR_PREFIX = "@author:"; 30 public final static String SERIES_TAG = "@seriesRoot"; 31 public final static String SERIES_GROUP_PREFIX = "@seriesGroup:"; 32 public final static String SERIES_PREFIX = "@series:"; 33 public final static String RATING_TAG = "@ratingRoot"; 34 public final static String STATE_TO_READ_TAG = "@stateToReadRoot"; 35 public final static String STATE_READING_TAG = "@stateReadingRoot"; 36 public final static String STATE_FINISHED_TAG = "@stateFinishedRoot"; 37 public final static String TITLE_TAG = "@titlesRoot"; 38 public final static String TITLE_GROUP_PREFIX = "@titleGroup:"; 39 public final static String SEARCH_SHORTCUT_TAG = "@search"; 40 41 42 43 public Long id; // db id 44 public String title; // book title 45 public String authors; // authors, delimited with '|' 46 public String series; // series name w/o number 47 public int seriesNumber; // number of book inside series 48 public String genres; // genre codes, delimited with '|' 49 public String path; // path to directory where file or archive is located 50 public String filename; // file name w/o path for normal file, with optional path for file inside archive 51 public String pathname; // full path+arcname+filename 52 public String arcname; // archive file name w/o path 53 public String language; // document language 54 public String description; // book description 55 public String username; // username for online catalogs 56 public String password; // password for online catalogs 57 public DocumentFormat format; 58 public int size; // full file size 59 public int arcsize; // compressed size 60 public long createTime; 61 public long lastAccessTime; 62 public int flags; 63 public boolean isArchive; 64 public boolean isDirectory; 65 public boolean isListed; 66 public boolean isScanned; 67 public long crc32; 68 public int domVersion; 69 public int blockRenderingFlags; 70 public FileInfo parent; // parent item 71 public Object tag; // some additional information 72 73 private ArrayList<FileInfo> files;// files 74 private ArrayList<FileInfo> dirs; // directories 75 76 // 16 lower bits reserved for document flags 77 public static final int DONT_USE_DOCUMENT_STYLES_FLAG = 1; 78 public static final int DONT_REFLOW_TXT_FILES_FLAG = 2; 79 public static final int USE_DOCUMENT_FONTS_FLAG = 4; 80 81 // bits 16..19 - reading state (0..15 max) 82 public static final int READING_STATE_SHIFT = 16; 83 public static final int READING_STATE_MASK = 0x0F; 84 public static final int STATE_NEW = 0; 85 public static final int STATE_TO_READ = 1; 86 public static final int STATE_READING = 2; 87 public static final int STATE_FINISHED = 3; 88 89 // bits 20..23 - rate (0..15 max, 0..5 currently) 90 public static final int RATE_SHIFT = 20; 91 public static final int RATE_MASK = 0x0F; 92 public static final int RATE_VALUE_NOT_RATED = 0; 93 public static final int RATE_VALUE_1 = 1; 94 public static final int RATE_VALUE_2 = 2; 95 public static final int RATE_VALUE_3 = 3; 96 public static final int RATE_VALUE_4 = 4; 97 public static final int RATE_VALUE_5 = 5; 98 99 //bit 24,25 - info type 100 private static final int TYPE_SHIFT = 24; 101 private static final int TYPE_MASK = 0x03; 102 public static final int TYPE_NOT_SET = 0; 103 public static final int TYPE_FS_ROOT = 1; 104 public static final int TYPE_DOWNLOAD_DIR = 2; 105 106 // bits 26..29 - profile id (0..15 max) 107 public static final int PROFILE_ID_SHIFT = 26; 108 public static final int PROFILE_ID_MASK = 0x0F; 109 110 // bitmask for field 'tag' when obtained genres list as special folders 111 public static final int GENRE_DATA_INCCHILD_MASK = 0x80000000; 112 public static final int GENRE_DATA_BOOKCOUNT_MASK = 0x00FFFFFF; 113 114 /** 115 * Get book reading state. 116 * @return reading state (one of STATE_XXX constants) 117 */ getReadingState()118 public int getReadingState() { 119 return getBitValue(READING_STATE_SHIFT,READING_STATE_MASK); 120 } 121 122 /** 123 * Set new reading state. 124 * @param state is new reading state (one of STATE_XXX constants) 125 */ setReadingState(int state)126 public boolean setReadingState(int state) { 127 return setBitValue(state, READING_STATE_SHIFT, READING_STATE_MASK); 128 } 129 130 /** 131 * Get book reading state. 132 * @return reading state (one of STATE_XXX constants) 133 */ getRate()134 public int getRate() { 135 return getBitValue(RATE_SHIFT, RATE_MASK); 136 } 137 138 /** 139 * Set new rate. 140 * @param rate is new rate (one of RATE_XXX constants) 141 */ setRate(int rate)142 public boolean setRate(int rate) { 143 return setBitValue(rate, RATE_SHIFT, RATE_MASK); 144 } 145 146 /** 147 * Get FileInfo type. 148 * @return folder type (one of TYPE_XXX constants) 149 */ getType()150 public int getType() { 151 return getBitValue(TYPE_SHIFT, TYPE_MASK); 152 } 153 154 /** 155 * Set FileInfo type. 156 * @param type is new type 157 */ setType(int type)158 public boolean setType(int type) { 159 return setBitValue(type, TYPE_SHIFT, TYPE_MASK); 160 } 161 162 /** 163 * To separate archive name from file name inside archive. 164 */ 165 public static final String ARC_SEPARATOR = "@/"; 166 167 setFlag( int flag, boolean value )168 public void setFlag( int flag, boolean value ) { 169 flags = flags & (~flag) | (value? flag : 0); 170 } 171 getFlag( int flag )172 public boolean getFlag( int flag ) { 173 return (flags & flag)!=0; 174 } 175 getProfileId()176 public int getProfileId() { 177 return getBitValue(PROFILE_ID_SHIFT,PROFILE_ID_MASK); 178 } 179 setProfileId(int id)180 public void setProfileId(int id) { 181 setBitValue(id,PROFILE_ID_SHIFT,PROFILE_ID_MASK); 182 } 183 setBitValue(int value, int shift, int mask)184 private boolean setBitValue(int value, int shift, int mask) { 185 int oldFlags = flags; 186 flags = (flags & ~(mask << shift)) 187 | ((value & mask) << shift); 188 return flags != oldFlags; 189 } 190 getBitValue(int shift, int mask)191 private int getBitValue(int shift, int mask) { 192 return (flags >> shift) & mask; 193 } 194 getTitleOrFileName()195 public String getTitleOrFileName() { 196 if (title != null && title.length() > 0) 197 return title; 198 if (authors != null && authors.length() > 0) 199 return ""; 200 if (series != null && series.length() > 0) 201 return ""; 202 return filename; 203 } 204 205 /** 206 * Split archive + file path name by ARC_SEPARATOR 207 * @param pathName is pathname like /arc_file_path@/filepath_inside_arc or /file_path 208 * @return item[0] is pathname, item[1] is archive name (null if no archive) 209 */ splitArcName( String pathName )210 public static String[] splitArcName( String pathName ) 211 { 212 String[] res = new String[2]; 213 int arcSeparatorPos = pathName.indexOf(ARC_SEPARATOR); 214 if ( arcSeparatorPos>=0 ) { 215 // from archive 216 res[1] = pathName.substring(0, arcSeparatorPos); 217 res[0] = pathName.substring(arcSeparatorPos + ARC_SEPARATOR.length()); 218 } else { 219 res[0] = pathName; 220 } 221 return res; 222 } 223 FileInfo( String pathName )224 public FileInfo( String pathName ) 225 { 226 String[] parts = splitArcName( pathName ); 227 if ( parts[1]!=null ) { 228 // from archive 229 isArchive = true; 230 arcname = parts[1]; 231 pathname = parts[0]; 232 File f = new File(pathname); 233 filename = f.getName(); 234 path = f.getPath(); 235 File arc = new File(arcname); 236 if (arc.isFile() && arc.exists()) { 237 arcsize = (int)arc.length(); 238 isArchive = true; 239 try { 240 //ZipFile zip = new ZipFile(new File(arcname)); 241 ArrayList<ZipEntry> entries = Services.getEngine().getArchiveItems(arcname); 242 //for ( Enumeration<?> e = zip.entries(); e.hasMoreElements(); ) { 243 for (ZipEntry entry : entries) { 244 String name = entry.getName(); 245 246 if ( !entry.isDirectory() && pathname.equals(name) ) { 247 File itemf = new File(name); 248 filename = itemf.getName(); 249 path = itemf.getPath(); 250 format = DocumentFormat.byExtension(name); 251 size = (int)entry.getSize(); 252 arcsize = (int)entry.getCompressedSize(); 253 createTime = entry.getTime(); 254 domVersion = Engine.DOM_VERSION_CURRENT; 255 blockRenderingFlags = Engine.BLOCK_RENDERING_FLAGS_WEB; 256 break; 257 } 258 } 259 } catch ( Exception e ) { 260 Log.e("cr3", "error while reading contents of " + arcname); 261 } 262 } 263 } else { 264 fromFile(new File(pathName)); 265 } 266 } 267 getFileNameToDisplay()268 public String getFileNameToDisplay() { 269 boolean isSingleFileArchive = (isArchive && parent!=null && !parent.isArchive && arcname!=null); 270 return isSingleFileArchive 271 ? new File(arcname).getName() : filename; 272 } 273 fromFile( File f )274 private void fromFile( File f ) 275 { 276 if ( !f.isDirectory() ) { 277 DocumentFormat fmt = DocumentFormat.byExtension(f.getName()); 278 filename = f.getName(); 279 path = f.getParent(); 280 pathname = f.getAbsolutePath(); 281 format = fmt; 282 createTime = f.lastModified(); 283 size = (int)f.length(); 284 domVersion = Engine.DOM_VERSION_CURRENT; 285 blockRenderingFlags = Engine.BLOCK_RENDERING_FLAGS_WEB; 286 } else { 287 filename = f.getName(); 288 path = f.getParent(); 289 pathname = f.getAbsolutePath(); 290 isDirectory = true; 291 } 292 File parent_ = f.getParentFile(); 293 if (null != parent_) 294 parent = new FileInfo(parent_); 295 } 296 FileInfo( File f )297 public FileInfo( File f ) 298 { 299 fromFile(f); 300 } 301 FileInfo()302 public FileInfo() 303 { 304 domVersion = Engine.DOM_VERSION_CURRENT; 305 blockRenderingFlags = Engine.BLOCK_RENDERING_FLAGS_WEB; 306 } 307 308 /// doesn't copy parent and children FileInfo(FileInfo v)309 public FileInfo(FileInfo v) 310 { 311 assign(v); 312 } 313 assign(FileInfo v)314 public void assign(FileInfo v) 315 { 316 title = v.title; 317 authors = v.authors; 318 series = v.series; 319 seriesNumber = v.seriesNumber; 320 path = v.path; 321 filename = v.filename; 322 pathname = v.pathname; 323 arcname = v.arcname; 324 format = v.format; 325 flags = v.flags; 326 size = v.size; 327 arcsize = v.arcsize; 328 isArchive = v.isArchive; 329 isDirectory = v.isDirectory; 330 createTime = v.createTime; 331 lastAccessTime = v.lastAccessTime; 332 language = v.language; 333 genres = v.genres; 334 description = v.description; 335 username = v.username; 336 password = v.password; 337 crc32 = v.crc32; 338 domVersion = v.domVersion; 339 blockRenderingFlags = v.blockRenderingFlags; 340 id = v.id; 341 } 342 343 /** 344 * @return archive file path and name, null if this object is neither archive nor a file inside archive 345 */ getArchiveName()346 public String getArchiveName() 347 { 348 return arcname; 349 } 350 351 /** 352 * @return file name inside archive, null if this object is not a file inside archive 353 */ getArchiveItemName()354 public String getArchiveItemName() 355 { 356 if ( isArchive && !isDirectory && pathname!=null ) 357 return pathname; 358 return null; 359 } 360 isRecentDir()361 public boolean isRecentDir() 362 { 363 return RECENT_DIR_TAG.equals(pathname); 364 } 365 isSearchDir()366 public boolean isSearchDir() 367 { 368 return SEARCH_RESULT_DIR_TAG.equals(pathname); 369 } 370 isRootDir()371 public boolean isRootDir() 372 { 373 return ROOT_DIR_TAG.equals(pathname); 374 } 375 isSpecialDir()376 public boolean isSpecialDir() 377 { 378 return pathname!=null && pathname.startsWith("@"); 379 } 380 isOnlineCatalogPluginDir()381 public boolean isOnlineCatalogPluginDir() 382 { 383 return pathname!=null && pathname.startsWith(ONLINE_CATALOG_PLUGIN_PREFIX); 384 } 385 isOnlineCatalogPluginBook()386 public boolean isOnlineCatalogPluginBook() 387 { 388 return !isDirectory && pathname != null && pathname.startsWith(ONLINE_CATALOG_PLUGIN_PREFIX) && getOnlineStoreBookInfo() != null; 389 } 390 isOPDSDir()391 public boolean isOPDSDir() 392 { 393 return pathname!=null && pathname.startsWith(OPDS_DIR_PREFIX) && (getOPDSEntryInfo() == null || getOPDSEntryInfo().getBestAcquisitionLink() == null); 394 } 395 isOPDSBook()396 public boolean isOPDSBook() 397 { 398 return pathname!=null && pathname.startsWith(OPDS_DIR_PREFIX) && getOPDSEntryInfo() != null && getOPDSEntryInfo().getBestAcquisitionLink() != null; 399 } 400 getOPDSEntryInfo()401 private OPDSUtil.EntryInfo getOPDSEntryInfo() { 402 if (tag !=null && tag instanceof OPDSUtil.EntryInfo) 403 return (OPDSUtil.EntryInfo)tag; 404 return null; 405 } 406 getOnlineStoreBookInfo()407 public OnlineStoreBook getOnlineStoreBookInfo() { 408 if (tag !=null && tag instanceof OnlineStoreBook) 409 return (OnlineStoreBook)tag; 410 return null; 411 } 412 isOPDSRoot()413 public boolean isOPDSRoot() 414 { 415 return OPDS_LIST_TAG.equals(pathname); 416 } 417 isSearchShortcut()418 public boolean isSearchShortcut() 419 { 420 return SEARCH_SHORTCUT_TAG.equals(pathname); 421 } 422 isBooksByGenreRoot()423 public boolean isBooksByGenreRoot() 424 { 425 return GENRES_TAG.equals(pathname); 426 } 427 isBooksByAuthorRoot()428 public boolean isBooksByAuthorRoot() 429 { 430 return AUTHORS_TAG.equals(pathname); 431 } 432 isBooksBySeriesRoot()433 public boolean isBooksBySeriesRoot() 434 { 435 return SERIES_TAG.equals(pathname); 436 } 437 isBooksByRatingRoot()438 public boolean isBooksByRatingRoot() 439 { 440 return RATING_TAG.equals(pathname); 441 } 442 isBooksByStateToReadRoot()443 public boolean isBooksByStateToReadRoot() 444 { 445 return STATE_TO_READ_TAG.equals(pathname); 446 } 447 isBooksByStateReadingRoot()448 public boolean isBooksByStateReadingRoot() 449 { 450 return STATE_READING_TAG.equals(pathname); 451 } 452 isBooksByStateFinishedRoot()453 public boolean isBooksByStateFinishedRoot() 454 { 455 return STATE_FINISHED_TAG.equals(pathname); 456 } 457 isBooksByTitleRoot()458 public boolean isBooksByTitleRoot() 459 { 460 return TITLE_TAG.equals(pathname); 461 } 462 isBooksByGenreDir()463 public boolean isBooksByGenreDir() 464 { 465 return pathname!=null && pathname.startsWith(GENRES_PREFIX); 466 } 467 isBooksByAuthorDir()468 public boolean isBooksByAuthorDir() 469 { 470 return pathname!=null && pathname.startsWith(AUTHOR_PREFIX); 471 } 472 isBooksBySeriesDir()473 public boolean isBooksBySeriesDir() 474 { 475 return pathname!=null && pathname.startsWith(SERIES_PREFIX); 476 } 477 isOnSDCard()478 public boolean isOnSDCard() { 479 if (null == parent) 480 return false; 481 if ( ( ( "SD".equals(filename) && "SD".equals(title)) || 482 ("EXT SD".equals(filename) && "EXT SD".equals(title)) ) && 483 isDirectory && !isArchive && 0 == size && 0 == arcsize && 484 ROOT_DIR_TAG.equals(parent.pathname) ) 485 return true; 486 return parent.isOnSDCard(); 487 } 488 getGenreCode()489 public String getGenreCode() { 490 if (pathname.startsWith(GENRES_PREFIX)) { 491 return pathname.substring(GENRES_PREFIX.length()); 492 } 493 return ""; 494 } 495 getAuthorId()496 public long getAuthorId() 497 { 498 if (!isBooksByAuthorDir()) 499 return 0; 500 return id; 501 } 502 getSeriesId()503 public long getSeriesId() 504 { 505 if (!isBooksBySeriesDir()) 506 return 0; 507 return id; 508 } 509 isHidden()510 public boolean isHidden() 511 { 512 return pathname.startsWith("."); 513 } 514 getOPDSUrl()515 public String getOPDSUrl() 516 { 517 if ( !pathname.startsWith(OPDS_DIR_PREFIX) ) 518 return null; 519 return pathname.substring(OPDS_DIR_PREFIX.length()); 520 } 521 getOnlineCatalogPluginPackage()522 public String getOnlineCatalogPluginPackage() 523 { 524 if ( !pathname.startsWith(ONLINE_CATALOG_PLUGIN_PREFIX) ) 525 return null; 526 String s = pathname.substring(ONLINE_CATALOG_PLUGIN_PREFIX.length()); 527 int p = s.indexOf(":"); 528 if (p < 0) 529 return s; 530 else 531 return s.substring(0, p); 532 } 533 getOnlineCatalogPluginPath()534 public String getOnlineCatalogPluginPath() 535 { 536 if ( !pathname.startsWith(ONLINE_CATALOG_PLUGIN_PREFIX) ) 537 return null; 538 String s = pathname.substring(ONLINE_CATALOG_PLUGIN_PREFIX.length()); 539 int p = s.indexOf(":"); 540 if (p < 0) 541 return null; 542 else 543 return s.substring(p + 1); 544 } 545 getOnlineCatalogPluginId()546 public String getOnlineCatalogPluginId() 547 { 548 String s = getOnlineCatalogPluginPath(); 549 if (s == null) 550 return null; 551 int p = s.indexOf("="); 552 if (p < 0) 553 return null; 554 else 555 return s.substring(p + 1); 556 } 557 558 /** 559 * Get absolute path to file. 560 * For plain files, returns /abs_path_to_file/filename.ext 561 * For archives, returns /abs_path_to_archive/arc_file_name.zip@/filename_inside_archive.ext 562 * @return full path + filename 563 */ getPathName()564 public String getPathName() 565 { 566 if ( arcname!=null ) 567 return arcname + ARC_SEPARATOR + pathname; 568 return pathname; 569 } 570 getBasePath()571 public String getBasePath() 572 { 573 if ( arcname!=null ) 574 return arcname; 575 return pathname; 576 } 577 dirCount()578 public int dirCount() 579 { 580 return dirs!=null ? dirs.size() : 0; 581 } 582 fileCount()583 public int fileCount() 584 { 585 return files!=null ? files.size() : 0; 586 } 587 itemCount()588 public int itemCount() 589 { 590 return dirCount() + fileCount(); 591 } 592 addDir( FileInfo dir )593 public void addDir( FileInfo dir ) 594 { 595 if ( dirs==null ) 596 dirs = new ArrayList<FileInfo>(); 597 dirs.add(dir); 598 if (dir.parent == null) 599 dir.parent = this; 600 } addFile( FileInfo file )601 public void addFile( FileInfo file ) 602 { 603 if ( files==null ) 604 files = new ArrayList<FileInfo>(); 605 files.add(file); 606 } addItems( Collection<FileInfo> items )607 public void addItems( Collection<FileInfo> items ) 608 { 609 for ( FileInfo item : items ) { 610 if ( item.isDirectory ) 611 addDir(item); 612 else 613 addFile(item); 614 item.parent = this; 615 } 616 } replaceItems( Collection<FileInfo> items )617 public void replaceItems( Collection<FileInfo> items ) 618 { 619 files = null; 620 dirs = null; 621 addItems( items ); 622 } isEmpty()623 public boolean isEmpty() 624 { 625 return fileCount()==0 && dirCount()==0; 626 } getItem( int index )627 public FileInfo getItem( int index ) 628 { 629 if ( index<0 ) 630 throw new IndexOutOfBoundsException(); 631 if ( index<dirCount()) 632 return dirs.get(index); 633 index -= dirCount(); 634 if ( index<fileCount()) 635 return files.get(index); 636 Log.e("cr3", "Index out of bounds " + index + " at FileInfo.getItem() : returning 0"); 637 //throw new IndexOutOfBoundsException(); 638 return null; 639 } findItemByPathName( String pathName )640 public FileInfo findItemByPathName( String pathName ) 641 { 642 if ( dirs!=null ) 643 for ( FileInfo dir : dirs ) 644 if ( isOnSDCard() && pathName.compareToIgnoreCase(dir.getPathName()) == 0 || pathName.equals(dir.getPathName()) ) 645 return dir; 646 if ( files!=null ) 647 for ( FileInfo file : files ) { 648 if ( isOnSDCard() && pathName.compareToIgnoreCase(file.getPathName()) == 0 || pathName.equals(file.getPathName()) ) 649 return file; 650 if ( isOnSDCard() && file.getPathName().toLowerCase().startsWith(pathName.toLowerCase()+"@/") || file.getPathName().startsWith(pathName+"@/" )) 651 return file; 652 } 653 return null; 654 } 655 eq(String s1, String s2)656 public static boolean eq(String s1, String s2) { 657 if (s1 == null) 658 return s2 == null; 659 return s1.equals(s2); 660 } 661 pathNameEquals(FileInfo item)662 public boolean pathNameEquals(FileInfo item) { 663 return isDirectory == item.isDirectory && eq(arcname, item.arcname) && eq(pathname, item.pathname); 664 } 665 hasItem(FileInfo item)666 public boolean hasItem(FileInfo item) { 667 return getItemIndex(item) >= 0; 668 } 669 getItemIndex( FileInfo item )670 public int getItemIndex( FileInfo item ) 671 { 672 if ( item==null ) 673 return -1; 674 for ( int i=0; i<dirCount(); i++ ) { 675 if ( item.pathNameEquals(getDir(i)) ) 676 return i; 677 } 678 for ( int i=0; i<fileCount(); i++ ) { 679 if (item.pathNameEquals(getFile(i))) 680 return i + dirCount(); 681 } 682 return -1; 683 } 684 getFileIndex( FileInfo item )685 public int getFileIndex( FileInfo item ) 686 { 687 if ( item==null ) 688 return -1; 689 for ( int i=0; i<fileCount(); i++ ) { 690 if (item.pathNameEquals(getFile(i))) 691 return i; 692 } 693 return -1; 694 } 695 getDir( int index )696 public FileInfo getDir( int index ) 697 { 698 if ( index<0 ) 699 throw new IndexOutOfBoundsException(); 700 if ( index<dirCount()) 701 return dirs.get(index); 702 throw new IndexOutOfBoundsException(); 703 } 704 getFile( int index )705 public FileInfo getFile( int index ) 706 { 707 if ( index<0 ) 708 throw new IndexOutOfBoundsException(); 709 if ( index<fileCount()) 710 return files.get(index); 711 throw new IndexOutOfBoundsException(); 712 } 713 setFileProperties(FileInfo file)714 public boolean setFileProperties(FileInfo file) 715 { 716 boolean modified = false; 717 modified = setTitle(file.getTitle()) || modified; 718 modified = setAuthors(file.getAuthors()) || modified; 719 modified = setSeriesName(file.getSeriesName()) || modified; 720 modified = setSeriesNumber(file.getSeriesNumber()) || modified; 721 modified = setReadingState(file.getReadingState()) || modified; 722 modified = setRate(file.getRate()) || modified; 723 return modified; 724 } 725 setFile(int index, FileInfo file)726 public void setFile(int index, FileInfo file) 727 { 728 if ( index<0 ) 729 throw new IndexOutOfBoundsException(); 730 if (index < fileCount()) { 731 files.set(index, file); 732 file.parent = this; 733 return; 734 } 735 throw new IndexOutOfBoundsException(); 736 } 737 setFile(FileInfo file)738 public void setFile(FileInfo file) 739 { 740 int index = getFileIndex(file); 741 if ( index<0 ) 742 return; 743 setFile(index, file); 744 } 745 setItems(FileInfo copyFrom)746 public void setItems(FileInfo copyFrom) 747 { 748 clear(); 749 for (int i=0; i<copyFrom.fileCount(); i++) { 750 addFile(copyFrom.getFile(i)); 751 copyFrom.getFile(i).parent = this; 752 } 753 for (int i=0; i<copyFrom.dirCount(); i++) { 754 addDir(copyFrom.getDir(i)); 755 copyFrom.getDir(i).parent = this; 756 } 757 } 758 setItems(Collection<FileInfo> list)759 public void setItems(Collection<FileInfo> list) 760 { 761 clear(); 762 if (list == null) 763 return; 764 for (FileInfo item : list) { 765 if (item.isDirectory) 766 addDir(item); 767 else 768 addFile(item); 769 item.parent = this; 770 } 771 } 772 removeEmptyDirs()773 public void removeEmptyDirs() 774 { 775 if ( parent==null || pathname.startsWith("@") || !isListed || dirs==null ) 776 return; 777 for ( int i=dirCount()-1; i>=0; i-- ) { 778 FileInfo dir = getDir(i); 779 if ( dir.isListed && dir.dirCount() == 0 && dir.fileCount() == 0) 780 dirs.remove(i); 781 } 782 } 783 removeChild( FileInfo item )784 public void removeChild( FileInfo item ) 785 { 786 if ( item.isSpecialDir() ) 787 return; 788 if ( files!=null ) { 789 int n = files.indexOf(item); 790 if ( n>=0 && n<files.size() ) { 791 files.remove(n); 792 return; 793 } 794 } 795 if ( dirs!=null ) { 796 int n = dirs.indexOf(item); 797 if ( n>=0 && n<dirs.size() ) { 798 dirs.remove(n); 799 } 800 } 801 } 802 deleteFile()803 public boolean deleteFile() 804 { 805 if ( isArchive ) { 806 if ( isDirectory ) 807 return false; 808 File f = new File(arcname); 809 if ( f.exists() && !f.isDirectory() ) { 810 if ( !f.delete() ) 811 return false; 812 if ( parent!=null ) { 813 if ( parent.isArchive ) { 814 // remove all files belonging to this archive 815 } else { 816 parent.removeChild(this); 817 } 818 } 819 return true; 820 } 821 } 822 if ( isDirectory ) 823 return false; 824 if ( !fileExists() ) 825 return false; 826 File f = new File(pathname); 827 if ( f.delete() ) { 828 if ( parent!=null ) { 829 parent.removeChild(this); 830 } 831 return true; 832 } 833 return false; 834 } 835 fileExists()836 public boolean fileExists() 837 { 838 if (isDirectory) 839 return false; 840 if ( isArchive ) { 841 if ( arcname!=null ) 842 return new File(arcname).exists(); 843 return false; 844 } 845 return new File(pathname).exists(); 846 } 847 848 /** 849 * @return true if item (file, directory, or archive) exists 850 */ exists()851 public boolean exists() 852 { 853 if ( isArchive ) { 854 if ( arcname==null ) 855 return false; 856 File f = new File(arcname); 857 return f.exists(); 858 } 859 File f = new File(pathname); 860 return f.exists(); 861 } 862 863 /** 864 * @return true if item is a directory, which exists and can be written to 865 */ isWritableDirectory()866 public boolean isWritableDirectory() 867 { 868 if (!isDirectory || isArchive || isSpecialDir()) 869 return false; 870 File f = new File(pathname); 871 boolean isDir = f.isDirectory(); 872 boolean canWr = f.canWrite(); 873 // if (!canWr) { 874 // File testFile = new File(f, "cr3test.tmp"); 875 // try { 876 // OutputStream os = new FileOutputStream(testFile, false); 877 // os.close(); 878 // testFile.delete(); 879 // canWr = true; 880 // } catch (FileNotFoundException e) { 881 // L.e("cannot write " + testFile, e); 882 // } catch (IOException e) { 883 // L.e("cannot write " + testFile, e); 884 // } 885 // } 886 return isDir && canWr; 887 } 888 889 /** 890 * @return true if item is a directory, which exists and can be written to 891 */ isReadableDirectory()892 public boolean isReadableDirectory() 893 { 894 if (!isDirectory || isArchive || isSpecialDir()) 895 return false; 896 File f = new File(pathname); 897 boolean isDir = f.isDirectory(); 898 boolean canRd = f.canRead(); 899 return isDir && canRd; 900 } 901 getAuthors()902 public String getAuthors() { 903 return authors; 904 } 905 setAuthors(String authors)906 public boolean setAuthors(String authors) { 907 if (eq(this.authors, authors)) 908 return false; 909 this.authors = authors; 910 return true; 911 } 912 getTitle()913 public String getTitle() { 914 return title; 915 } 916 setTitle(String title)917 public boolean setTitle(String title) { 918 if (eq(this.title, title)) 919 return false; 920 this.title = title; 921 return true; 922 } 923 getSeriesName()924 public String getSeriesName() { 925 return series; 926 } 927 setSeriesName(String series)928 public boolean setSeriesName(String series) { 929 if (eq(this.series, series)) 930 return false; 931 this.series = series; 932 return true; 933 } 934 setSeriesNumber(int seriesNumber)935 public boolean setSeriesNumber(int seriesNumber) { 936 if (this.seriesNumber == seriesNumber) 937 return false; 938 this.seriesNumber = seriesNumber; 939 return true; 940 } 941 getSeriesNumber()942 public int getSeriesNumber() { 943 return series != null && series.length() > 0 ? seriesNumber : 0; 944 } 945 getLanguage()946 public String getLanguage() { 947 return language; 948 } 949 clear()950 public void clear() 951 { 952 dirs = null; 953 files = null; 954 } 955 956 public static enum SortOrder { 957 FILENAME(R.string.mi_book_sort_order_filename, new Comparator<FileInfo>() { 958 public int compare( FileInfo f1, FileInfo f2 ) 959 { 960 if ( f1==null || f2==null ) 961 return 0; 962 return Utils.cmp(f1.getFileNameToDisplay(), f2.getFileNameToDisplay()); 963 } 964 }), 965 FILENAME_DESC(R.string.mi_book_sort_order_filename_desc, new Comparator<FileInfo>() { 966 public int compare( FileInfo f1, FileInfo f2 ) 967 { 968 if ( f1==null || f2==null ) 969 return 0; 970 return Utils.cmp(f2.getFileNameToDisplay(), f1.getFileNameToDisplay()); 971 } 972 }), 973 TIMESTAMP(R.string.mi_book_sort_order_timestamp, new Comparator<FileInfo>() { 974 public int compare( FileInfo f1, FileInfo f2 ) 975 { 976 if ( f1==null || f2==null ) 977 return 0; 978 return firstNz( cmp(f1.createTime, f2.createTime), Utils.cmp(f1.filename, f2.filename) ); 979 } 980 }), 981 TIMESTAMP_DESC(R.string.mi_book_sort_order_timestamp_desc, new Comparator<FileInfo>() { 982 public int compare( FileInfo f1, FileInfo f2 ) 983 { 984 if ( f1==null || f2==null ) 985 return 0; 986 return firstNz( cmp(f2.createTime, f1.createTime), Utils.cmp(f2.filename, f1.filename) ); 987 } 988 }), 989 AUTHOR_TITLE(R.string.mi_book_sort_order_author, new Comparator<FileInfo>() { 990 public int compare( FileInfo f1, FileInfo f2 ) 991 { 992 if ( f1==null || f2==null ) 993 return 0; 994 return firstNz( 995 cmpNotNullFirst(Utils.formatAuthors(f1.authors), Utils.formatAuthors(f2.authors)) 996 ,cmpNotNullFirst(f1.series, f2.series) 997 ,cmp(f1.getSeriesNumber(), f2.getSeriesNumber()) 998 ,cmpNotNullFirst(f1.title, f2.title) 999 ,Utils.cmp(f1.filename, f2.filename) 1000 ); 1001 } 1002 }), 1003 AUTHOR_TITLE_DESC(R.string.mi_book_sort_order_author_desc, new Comparator<FileInfo>() { 1004 public int compare( FileInfo f1, FileInfo f2 ) 1005 { 1006 if ( f1==null || f2==null ) 1007 return 0; 1008 return firstNz( 1009 cmpNotNullFirst(Utils.formatAuthors(f2.authors), Utils.formatAuthors(f1.authors)) 1010 ,cmpNotNullFirst(f2.series, f1.series) 1011 ,cmp(f2.getSeriesNumber(), f1.getSeriesNumber()) 1012 ,cmpNotNullFirst(f2.title, f1.title) 1013 ,Utils.cmp(f2.filename, f1.filename) 1014 ); 1015 } 1016 }), 1017 TITLE_AUTHOR(R.string.mi_book_sort_order_title, new Comparator<FileInfo>() { 1018 public int compare( FileInfo f1, FileInfo f2 ) 1019 { 1020 if ( f1==null || f2==null ) 1021 return 0; 1022 return firstNz( 1023 cmpNotNullFirst(f1.series, f2.series) 1024 ,cmp(f1.getSeriesNumber(), f2.getSeriesNumber()) 1025 ,cmpNotNullFirst(f1.title, f2.title) 1026 ,cmpNotNullFirst(Utils.formatAuthors(f1.authors), Utils.formatAuthors(f2.authors)) 1027 ,Utils.cmp(f1.filename, f2.filename) 1028 ); 1029 } 1030 }), 1031 TITLE_AUTHOR_DESC(R.string.mi_book_sort_order_title_desc, new Comparator<FileInfo>() { 1032 public int compare( FileInfo f1, FileInfo f2 ) 1033 { 1034 if ( f1==null || f2==null ) 1035 return 0; 1036 return firstNz( 1037 cmpNotNullFirst(f2.series, f1.series) 1038 ,cmp(f2.getSeriesNumber(), f1.getSeriesNumber()) 1039 ,cmpNotNullFirst(f2.title, f1.title) 1040 ,cmpNotNullFirst(Utils.formatAuthors(f2.authors), Utils.formatAuthors(f1.authors)) 1041 ,Utils.cmp(f2.filename, f1.filename) 1042 ); 1043 } 1044 }); 1045 //================================================ 1046 private final Comparator<FileInfo> comparator; 1047 public final int resourceId; SortOrder( int resourceId, Comparator<FileInfo> comparator )1048 private SortOrder( int resourceId, Comparator<FileInfo> comparator ) 1049 { 1050 this.resourceId = resourceId; 1051 this.comparator = comparator; 1052 } 1053 getComparator()1054 public final Comparator<FileInfo> getComparator() 1055 { 1056 return comparator; 1057 } 1058 1059 /** 1060 * Same as cmp, but not-null comes first 1061 * @param str1 1062 * @param str2 1063 * @return 1064 */ cmpNotNullFirst( String str1, String str2 )1065 private static int cmpNotNullFirst( String str1, String str2 ) 1066 { 1067 if ( str1==null && str2==null ) 1068 return 0; 1069 if ( str1==null ) 1070 return 1; 1071 if ( str2==null ) 1072 return -1; 1073 return Utils.cmp(str1, str2); 1074 } 1075 cmp( long n1, long n2 )1076 static int cmp( long n1, long n2 ) 1077 { 1078 if ( n1<n2 ) 1079 return -1; 1080 if ( n1>n2 ) 1081 return 1; 1082 return 0; 1083 } 1084 firstNz( int... v)1085 private static int firstNz( int... v) 1086 { 1087 for ( int i=0; i<v.length; i++ ) { 1088 if ( v[i]!=0 ) 1089 return v[i]; 1090 } 1091 return 0; 1092 } fromName( String name )1093 public static SortOrder fromName( String name ) { 1094 if ( name!=null ) 1095 for ( SortOrder order : values() ) 1096 if ( order.name().equals(name) ) 1097 return order; 1098 return DEF_SORT_ORDER; 1099 } 1100 } 1101 public final static SortOrder DEF_SORT_ORDER = SortOrder.AUTHOR_TITLE; 1102 sort( SortOrder SortOrder )1103 public void sort( SortOrder SortOrder ) 1104 { 1105 if ( dirs!=null ) { 1106 ArrayList<FileInfo> newDirs = new ArrayList<FileInfo>(dirs); 1107 Collections.sort( newDirs, SortOrder.getComparator() ); 1108 dirs = newDirs; 1109 } 1110 if ( files!=null ) { 1111 ArrayList<FileInfo> newFiles = new ArrayList<FileInfo>(files); 1112 Collections.sort( newFiles, SortOrder.getComparator() ); 1113 files = newFiles; 1114 } 1115 } 1116 1117 1118 1119 @Override hashCode()1120 public int hashCode() { 1121 final int prime = 31; 1122 int result = 1; 1123 result = prime * result + ((arcname == null) ? 0 : arcname.hashCode()); 1124 result = prime * result + arcsize; 1125 result = prime * result + ((authors == null) ? 0 : authors.hashCode()); 1126 result = prime * result + (int) (createTime ^ (createTime >>> 32)); 1127 result = prime * result + ((dirs == null) ? 0 : dirs.hashCode()); 1128 result = prime * result 1129 + ((filename == null) ? 0 : filename.hashCode()); 1130 result = prime * result + ((files == null) ? 0 : files.hashCode()); 1131 result = prime * result + flags; 1132 result = prime * result + ((format == null) ? 0 : format.hashCode()); 1133 result = prime * result + (isArchive ? 1231 : 1237); 1134 result = prime * result + (isDirectory ? 1231 : 1237); 1135 result = prime * result + (isListed ? 1231 : 1237); 1136 result = prime * result + (isScanned ? 1231 : 1237); 1137 result = prime * result 1138 + ((language == null) ? 0 : language.hashCode()); 1139 result = prime * result 1140 + (int) (lastAccessTime ^ (lastAccessTime >>> 32)); 1141 result = prime * result + ((parent == null) ? 0 : parent.hashCode()); 1142 result = prime * result + ((path == null) ? 0 : path.hashCode()); 1143 result = prime * result 1144 + ((pathname == null) ? 0 : pathname.hashCode()); 1145 result = prime * result + ((series == null) ? 0 : series.hashCode()); 1146 result = prime * result + seriesNumber; 1147 result = prime * result + size; 1148 result = prime * result + ((title == null) ? 0 : title.hashCode()); 1149 return result; 1150 } 1151 1152 @Override equals(Object obj)1153 public boolean equals(Object obj) { 1154 if (this == obj) 1155 return true; 1156 if (obj == null) 1157 return false; 1158 if (getClass() != obj.getClass()) 1159 return false; 1160 FileInfo other = (FileInfo) obj; 1161 if (arcname == null) { 1162 if (other.arcname != null) 1163 return false; 1164 } else if (!arcname.equals(other.arcname)) 1165 return false; 1166 if (arcsize != other.arcsize) 1167 return false; 1168 if (authors == null) { 1169 if (other.authors != null) 1170 return false; 1171 } else if (!authors.equals(other.authors)) 1172 return false; 1173 if (createTime != other.createTime) 1174 return false; 1175 if (dirs == null) { 1176 if (other.dirs != null) 1177 return false; 1178 } else if (!dirs.equals(other.dirs)) 1179 return false; 1180 if (filename == null) { 1181 if (other.filename != null) 1182 return false; 1183 } else if (!filename.equals(other.filename)) 1184 return false; 1185 if (files == null) { 1186 if (other.files != null) 1187 return false; 1188 } else if (!files.equals(other.files)) 1189 return false; 1190 if (flags != other.flags) 1191 return false; 1192 if (format != other.format) 1193 return false; 1194 if (isArchive != other.isArchive) 1195 return false; 1196 if (isDirectory != other.isDirectory) 1197 return false; 1198 if (isListed != other.isListed) 1199 return false; 1200 if (isScanned != other.isScanned) 1201 return false; 1202 if (language == null) { 1203 if (other.language != null) 1204 return false; 1205 } else if (!language.equals(other.language)) 1206 return false; 1207 // do not compare genres of books, because in the absence of certain genres in the handbook, 1208 // the 'genres' field obtained from the database will not be equal to the field obtained when parsing the book file. 1209 /* 1210 if (!eqGenre(genres, other.genres)) 1211 return false; 1212 */ 1213 if (description == null) { 1214 if (other.description != null) 1215 return false; 1216 } else if (!description.equals(other.description)) 1217 return false; 1218 if (lastAccessTime != other.lastAccessTime) 1219 return false; 1220 if (parent == null) { 1221 if (other.parent != null) 1222 return false; 1223 } else if (!parent.equals(other.parent)) 1224 return false; 1225 if (path == null) { 1226 if (other.path != null) 1227 return false; 1228 } else if (!path.equals(other.path)) 1229 return false; 1230 if (pathname == null) { 1231 if (other.pathname != null) 1232 return false; 1233 } else if (!pathname.equals(other.pathname)) 1234 return false; 1235 if (series == null) { 1236 if (other.series != null && other.series.length() != 0) 1237 return false; 1238 } else if (!series.equals(other.series) && !(series.length() == 0 && other.series == null)) 1239 return false; 1240 if (seriesNumber != other.seriesNumber) 1241 return false; 1242 if (size != other.size) 1243 return false; 1244 if (title == null) { 1245 if (other.title != null) 1246 return false; 1247 } else if (!title.equals(other.title)) 1248 return false; 1249 if (crc32 != other.crc32) 1250 return false; 1251 if (domVersion != other.domVersion) 1252 return false; 1253 if (blockRenderingFlags != other.blockRenderingFlags) 1254 return false; 1255 return true; 1256 } 1257 baseEquals(FileInfo other)1258 public boolean baseEquals(FileInfo other) { 1259 if (this == other) 1260 return true; 1261 if (other == null) 1262 return false; 1263 if (arcname == null) { 1264 if (other.arcname != null) 1265 return false; 1266 } else if (!arcname.equals(other.arcname)) 1267 return false; 1268 if (arcsize != other.arcsize) 1269 return false; 1270 if (authors == null) { 1271 if (other.authors != null) 1272 return false; 1273 } else if (!authors.equals(other.authors)) 1274 return false; 1275 if (filename == null) { 1276 if (other.filename != null) 1277 return false; 1278 } else if (!filename.equals(other.filename)) 1279 return false; 1280 if (flags != other.flags) 1281 return false; 1282 if (format != other.format) 1283 return false; 1284 if (isArchive != other.isArchive) 1285 return false; 1286 if (isDirectory != other.isDirectory) 1287 return false; 1288 if (language == null) { 1289 if (other.language != null) 1290 return false; 1291 } else if (!language.equals(other.language)) 1292 return false; 1293 // do not compare genres of books, because in the absence of certain genres in the handbook, 1294 // the 'genres' field obtained from the database will not be equal to the field obtained when parsing the book file. 1295 /* 1296 if (!eqGenre(genres, other.genres)) 1297 return false; 1298 */ 1299 if (description == null) { 1300 if (other.description != null) 1301 return false; 1302 } else if (!description.equals(other.description)) 1303 return false; 1304 if (path == null) { 1305 if (other.path != null) 1306 return false; 1307 } else if (!path.equals(other.path)) 1308 return false; 1309 if (pathname == null) { 1310 if (other.pathname != null) 1311 return false; 1312 } else if (!pathname.equals(other.pathname)) 1313 return false; 1314 if (series == null) { 1315 if (other.series != null && other.series.length() != 0) 1316 return false; 1317 } else if (!series.equals(other.series) && !(series.length() == 0 && other.series == null)) 1318 return false; 1319 if (seriesNumber != other.seriesNumber) 1320 return false; 1321 if (size != other.size) 1322 return false; 1323 if (title == null) { 1324 if (other.title != null) 1325 return false; 1326 } else if (!title.equals(other.title)) 1327 return false; 1328 if (crc32 != other.crc32) 1329 return false; 1330 return true; 1331 } 1332 eqGenre(String g1, String g2)1333 private static boolean eqGenre(String g1, String g2) { 1334 if (g1 == null) { 1335 if (g2 != null && g2.length() != 0) 1336 return false; 1337 } 1338 if (g1.equals(g2)) 1339 return true; 1340 String[] g1_array = g1.split("\\|"); 1341 String[] g2_array = g2.split("\\|"); 1342 if (g1_array.length == g2_array.length) { 1343 Arrays.sort(g1_array); 1344 Arrays.sort(g2_array); 1345 return Arrays.equals(g1_array, g2_array); 1346 } 1347 return false; 1348 } 1349 1350 @Override toString()1351 public String toString() 1352 { 1353 return pathname; 1354 } 1355 allowSorting()1356 public boolean allowSorting() { 1357 return isDirectory && !isRootDir() && !isRecentDir() && !isOPDSDir() && !isBooksBySeriesDir(); 1358 } 1359 } 1360