public String getLine(int lineNo) throws Exception {
    if (lineNo <= 0 || lineNo > mNumLines) {
      return null;
    }

    String line = null;
    Jedis jedis = pool_.getResource();
    try {
      String lineNoStr = Integer.toString(lineNo);
      line = jedis.hget(lineNoStr, LINE_KEY);
      if (line == null) {
        RecordId lineId = getRecordId(lineNo);

        byte[] buffer = new byte[BLOCK_SIZE];
        synchronized (mDataFile) {
          mDataFile.seek(lineId.pageNo() * BLOCK_SIZE);
          mDataFile.read(buffer);
        }
        ByteBuffer bb = ByteBuffer.wrap(buffer);

        int numRecords = bb.getInt(BLOCK_SIZE - INT_SIZE);
        int indexPos = BLOCK_SIZE - 3 * INT_SIZE;
        int curLineNo = lineNo - lineId.slotNo() + 1;
        String[] recordKeys = {PAGE_NO_KEY, SLOT_NO_KEY};
        for (int i = 1; i < numRecords; i++, curLineNo++) {
          int offset = bb.getInt(indexPos);
          int len = bb.getInt(indexPos + INT_SIZE);

          ByteBuffer linebb = ByteBuffer.wrap(buffer, offset, len);
          byte[] lineBuf = new byte[linebb.remaining()];
          linebb.get(lineBuf);
          String lineStr = new String(lineBuf);

          if (i == lineId.slotNo()) {
            line = lineStr;
          }

          // Store in cache
          lineNoStr = Integer.toString(curLineNo);
          jedis.hdel(lineNoStr, recordKeys);
          jedis.hset(lineNoStr, LINE_KEY, lineStr);
          indexPos -= 2 * INT_SIZE;
        }
      }

      if (line == null) {
        throw new Exception("LineServer: Could not find line on page");
      }
    } catch (IOException ioe) {
      System.err.println("LineServer: IOException on line retrieval");
      throw ioe;
    } finally {
      pool_.returnResource(jedis);
    }

    return line;
  }
 /**
  * Assuming the file has an id3v2 tag, returns true if the tag can be overwritten. We cannot
  * overwrite id3v2 tags not supported
  *
  * @param raf
  * @return
  * @throws IOException
  */
 private boolean canOverwrite(RandomAccessFile raf) throws IOException {
   raf.seek(3);
   // Version du tag ID3v2.xx.xx
   String versionHigh = raf.read() + "";
   if (!(versionHigh.equals("4") || versionHigh.equals("3") || versionHigh.equals("2")))
     return false; // only version 2.3.xx
   // raf.read();
   // int flag = raf.read() & 128;
   // if (flag == 128)
   // return false; //unsynchronised tags not supported
   return true;
 }
  private RecordId getRecordId(int lineNo) throws IOException {
    RecordId id = null;
    Jedis jedis = pool_.getResource();
    try {
      int pageNo, slotNo;
      String lineNoStr = Integer.toString(lineNo);
      String pageNoStr = jedis.hget(lineNoStr, PAGE_NO_KEY);
      String slotNoStr = jedis.hget(lineNoStr, SLOT_NO_KEY);
      if (pageNoStr != null && slotNoStr != null) {
        id = new RecordId(Integer.parseInt(pageNoStr), Integer.parseInt(slotNoStr));
      } else { // Was not cached
        // Read directory page containing line in question
        byte[] buffer = new byte[BLOCK_SIZE];
        int absIndex = (lineNo - 1) * 2 * INT_SIZE;
        int relativeIndex = absIndex % BLOCK_SIZE;
        int bytesRead = 0;
        synchronized (mDirectoryFile) {
          mDirectoryFile.seek(absIndex - relativeIndex);
          bytesRead = mDirectoryFile.read(buffer);
        }
        ByteBuffer bb = ByteBuffer.wrap(buffer);

        // Cache all entries in the page
        int curLineNo = lineNo - relativeIndex / (2 * INT_SIZE);
        for (int i = 0; i < bytesRead / (2 * INT_SIZE); i++, curLineNo++) {
          pageNo = bb.getInt();
          slotNo = bb.getInt();
          if (curLineNo == lineNo) {
            id = new RecordId(pageNo, slotNo);
          }
          lineNoStr = Integer.toString(curLineNo);
          jedis.hset(lineNoStr, PAGE_NO_KEY, Integer.toString(pageNo));
          jedis.hset(lineNoStr, SLOT_NO_KEY, Integer.toString(slotNo));
        }
      }

      if (id == null) {
        throw new IOException("LineServer: Could not find record id in directory");
      }
    } finally {
      pool_.returnResource(jedis);
    }

    return id;
  }
  /**
   * Read block from file.
   *
   * @param file - File to read.
   * @param off - Marker position in file to start read from if {@code -1} read last blockSz bytes.
   * @param blockSz - Maximum number of chars to read.
   * @param lastModified - File last modification time.
   * @return Read file block.
   * @throws IOException In case of error.
   */
  public static VisorFileBlock readBlock(File file, long off, int blockSz, long lastModified)
      throws IOException {
    RandomAccessFile raf = null;

    try {
      long fSz = file.length();
      long fLastModified = file.lastModified();

      long pos = off >= 0 ? off : Math.max(fSz - blockSz, 0);

      // Try read more that file length.
      if (fLastModified == lastModified && fSz != 0 && pos >= fSz)
        throw new IOException(
            "Trying to read file block with wrong offset: " + pos + " while file size: " + fSz);

      if (fSz == 0)
        return new VisorFileBlock(file.getPath(), pos, fLastModified, 0, false, EMPTY_FILE_BUF);
      else {
        int toRead = Math.min(blockSz, (int) (fSz - pos));

        byte[] buf = new byte[toRead];

        raf = new RandomAccessFile(file, "r");

        raf.seek(pos);

        int cntRead = raf.read(buf, 0, toRead);

        if (cntRead != toRead)
          throw new IOException(
              "Count of requested and actually read bytes does not match [cntRead="
                  + cntRead
                  + ", toRead="
                  + toRead
                  + ']');

        boolean zipped = buf.length > 512;

        return new VisorFileBlock(
            file.getPath(), pos, fSz, fLastModified, zipped, zipped ? zipBytes(buf) : buf);
      }
    } finally {
      U.close(raf, null);
    }
  }