@Override
 public void detectLanguageAndEncoding(Book book) throws BookReadingException {
   InputStream stream = null;
   try {
     stream = book.File.getInputStream();
     final PdbHeader header = new PdbHeader(stream);
     PdbUtil.skip(stream, header.Offsets[0] + 16 - header.length());
     if (PdbUtil.readInt(stream) != 0x4D4F4249) /* "MOBI" */ {
       throw new BookReadingException("unsupportedFileFormat", book.File);
     }
     final int length = (int) PdbUtil.readInt(stream);
     PdbUtil.skip(stream, 4);
     final int encodingCode = (int) PdbUtil.readInt(stream);
     final Encoding encoding = supportedEncodings().getEncoding(encodingCode);
     final String encodingName = encoding != null ? encoding.Name : "utf-8";
     book.setEncoding(encodingName);
     PdbUtil.skip(stream, 52);
     final int fullNameOffset = (int) PdbUtil.readInt(stream);
     final int fullNameLength = (int) PdbUtil.readInt(stream);
     final int languageCode = (int) PdbUtil.readInt(stream);
     book.setLanguage(
         ZLLanguageUtil.languageByIntCode(languageCode & 0xFF, (languageCode >> 8) & 0xFF));
   } catch (IOException e) {
     throw new BookReadingException(e, book.File);
   } finally {
     if (stream != null) {
       try {
         stream.close();
       } catch (IOException e) {
       }
     }
   }
 }
 @Override
 public synchronized void readUids(Book book) throws BookReadingException {
   readUidsNative(book);
   if (book.uids().isEmpty()) {
     book.addUid(BookUtil.createSHA256Uid(book.File));
   }
 }
  public Map<String, Object> data(IBookCollection<Book> collection) {
    final Map<String, Object> map = new HashMap<String, Object>();
    map.put("generation", myGeneration.getValue());
    map.put("timestamp", System.currentTimeMillis());

    final Book currentBook = collection.getRecentBook(0);
    if (currentBook != null) {
      final String oldHash = myCurrentBookHash.getValue();
      final String newHash = collection.getHash(currentBook, true);
      if (newHash != null && !newHash.equals(oldHash)) {
        myCurrentBookHash.setValue(newHash);
        if (oldHash.length() != 0) {
          myCurrentBookTimestamp.setValue(String.valueOf(System.currentTimeMillis()));
          myServerBook.reset();
        }
      }
      final String currentBookHash = newHash != null ? newHash : oldHash;

      final Map<String, Object> currentBookMap = new HashMap<String, Object>();
      currentBookMap.put("hash", currentBookHash);
      currentBookMap.put("title", currentBook.getTitle());
      try {
        currentBookMap.put("timestamp", Long.parseLong(myCurrentBookTimestamp.getValue()));
      } catch (Exception e) {
      }
      map.put("currentbook", currentBookMap);

      final List<Map<String, Object>> lst = new ArrayList<Map<String, Object>>();
      if (positionOption(currentBookHash).getValue().length() == 0) {
        final Map<String, Object> posMap = positionMap(collection, currentBook);
        if (posMap != null) {
          posMap.put("hash", currentBookHash);
          lst.add(posMap);
        }
      }
      if (!currentBookHash.equals(oldHash) && positionOption(oldHash).getValue().length() == 0) {
        final Map<String, Object> posMap =
            positionMap(collection, collection.getBookByHash(oldHash));
        if (posMap != null) {
          posMap.put("hash", oldHash);
          lst.add(posMap);
        }
      }
      if (lst.size() > 0) {
        map.put("positions", lst);
      }
    }

    System.err.println("DATA = " + map);
    return map;
  }
 private Map<String, Object> positionMap(IBookCollection<Book> collection, Book book) {
   if (book == null) {
     return null;
   }
   final ZLTextFixedPosition.WithTimestamp pos = collection.getStoredPosition(book.getId());
   return pos != null ? position2Map(pos) : null;
 }
 @Override
 public String getSummary() {
   StringBuilder builder = new StringBuilder();
   int count = 0;
   for (Author author : Book.authors()) {
     if (count++ > 0) {
       builder.append(",  ");
     }
     builder.append(author.DisplayName);
     if (count == 5) {
       break;
     }
   }
   return builder.toString();
 }
 public static void shareBook(Activity activity, Book book) {
   try {
     final ZLPhysicalFile file = book.File.getPhysicalFile();
     if (file == null) {
       // That should be impossible
       return;
     }
     final CharSequence sharedFrom =
         Html.fromHtml(ZLResource.resource("sharing").getResource("sharedFrom").getValue());
     activity.startActivity(
         new Intent(Intent.ACTION_SEND)
             .setType(FileTypeCollection.Instance.rawMimeType(file).Name)
             .putExtra(Intent.EXTRA_SUBJECT, book.getTitle())
             .putExtra(Intent.EXTRA_TEXT, sharedFrom)
             .putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file.javaFile())));
   } catch (ActivityNotFoundException e) {
     // TODO: show toast
   }
 }
 @Override
 public void readUids(Book book) {
   if (book.uids().isEmpty()) {
     book.addUid(BookUtil.createUid(book.File, "SHA-256"));
   }
 }
 @Override
 public void readMetainfo(Book book) throws BookReadingException {
   InputStream stream = null;
   try {
     stream = book.File.getInputStream();
     final PdbHeader header = new PdbHeader(stream);
     PdbUtil.skip(stream, header.Offsets[0] + 16 - header.length());
     if (PdbUtil.readInt(stream) != 0x4D4F4249) /* "MOBI" */ {
       throw new BookReadingException("unsupportedFileFormat", book.File);
     }
     final int length = (int) PdbUtil.readInt(stream);
     PdbUtil.skip(stream, 4);
     final int encodingCode = (int) PdbUtil.readInt(stream);
     final Encoding encoding = supportedEncodings().getEncoding(encodingCode);
     final String encodingName = encoding != null ? encoding.Name : "utf-8";
     book.setEncoding(encodingName);
     PdbUtil.skip(stream, 52);
     final int fullNameOffset = (int) PdbUtil.readInt(stream);
     final int fullNameLength = (int) PdbUtil.readInt(stream);
     final int languageCode = (int) PdbUtil.readInt(stream);
     book.setLanguage(
         ZLLanguageUtil.languageByIntCode(languageCode & 0xFF, (languageCode >> 8) & 0xFF));
     PdbUtil.skip(stream, 32);
     int offset = 132;
     if ((PdbUtil.readInt(stream) & 0x40) != 0) {
       PdbUtil.skip(stream, length - 116);
       offset = length + 20;
       if (PdbUtil.readInt(stream) == 0x45585448) /* "EXTH" */ {
         PdbUtil.skip(stream, 4);
         final int recordsNumber = (int) PdbUtil.readInt(stream);
         offset += 8;
         for (int i = 0; i < recordsNumber; ++i) {
           final int type = (int) PdbUtil.readInt(stream);
           final int size = (int) PdbUtil.readInt(stream);
           offset += size;
           if (size <= 8) {
             continue;
           }
           switch (type) {
             default:
               PdbUtil.skip(stream, size - 8);
               break;
             case 100:
               {
                 final byte[] buffer = new byte[size - 8];
                 stream.read(buffer);
                 String author = new String(buffer, encodingName);
                 final int index = author.indexOf(',');
                 if (index != -1) {
                   author =
                       author.substring(index + 1).trim()
                           + ' '
                           + author.substring(0, index).trim();
                 } else {
                   author = author.trim();
                 }
                 book.addAuthor(author);
                 break;
               }
             case 105:
               {
                 final byte[] buffer = new byte[size - 8];
                 stream.read(buffer);
                 book.addTag(new String(buffer, encodingName));
                 break;
               }
           }
         }
       }
     }
     PdbUtil.skip(stream, fullNameOffset - offset);
     final byte[] titleBuffer = new byte[fullNameLength];
     stream.read(titleBuffer);
     book.setTitle(new String(titleBuffer, encodingName));
   } catch (IOException e) {
     throw new BookReadingException(e, book.File);
   } finally {
     if (stream != null) {
       try {
         stream.close();
       } catch (IOException e) {
       }
     }
   }
 }
 @Override
 public void readUids(Book book) throws BookReadingException {
   if (book.uids().isEmpty()) {
     book.addUid(BookUtil.createUid(book.File, "SHA-256"));
   }
 }