1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui.archives;
18
19import android.os.ProxyFileDescriptorCallback;
20import android.system.ErrnoException;
21import android.system.OsConstants;
22import android.util.Log;
23import android.util.jar.StrictJarFile;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.util.zip.ZipEntry;
28
29import libcore.io.IoUtils;
30
31/**
32 * Provides a backend for a seekable file descriptors for files in archives.
33 */
34public class Proxy extends ProxyFileDescriptorCallback {
35    private final StrictJarFile mFile;
36    private final ZipEntry mEntry;
37    private InputStream mInputStream = null;
38    private long mOffset = 0;
39
40    Proxy(StrictJarFile file, ZipEntry entry) throws IOException {
41        mFile = file;
42        mEntry = entry;
43        recreateInputStream();
44    }
45
46    @Override
47    public long onGetSize() throws ErrnoException {
48        return mEntry.getSize();
49    }
50
51    @Override
52    public int onRead(long offset, int size, byte[] data) throws ErrnoException {
53        // TODO: Add a ring buffer to prevent expensive seeks.
54        if (offset < mOffset) {
55            try {
56                recreateInputStream();
57            } catch (IOException e) {
58                throw new ErrnoException("onRead", OsConstants.EIO);
59            }
60        }
61
62        while (mOffset < offset) {
63            try {
64                mOffset +=  mInputStream.skip(offset - mOffset);
65            } catch (IOException e) {
66                throw new ErrnoException("onRead", OsConstants.EIO);
67            }
68        }
69
70        int remainingSize = size;
71        while (remainingSize > 0) {
72            try {
73                int bytes = mInputStream.read(data, size - remainingSize, remainingSize);
74                if (bytes <= 0) {
75                    return size - remainingSize;
76                }
77                remainingSize -= bytes;
78                mOffset += bytes;
79            } catch (IOException e) {
80                throw new ErrnoException("onRead", OsConstants.EIO);
81            }
82        }
83
84        return size - remainingSize;
85   }
86
87    @Override public void onRelease() {
88        IoUtils.closeQuietly(mInputStream);
89    }
90
91    private void recreateInputStream() throws IOException {
92        IoUtils.closeQuietly(mInputStream);
93        mInputStream = mFile.getInputStream(mEntry);
94        mOffset = 0;
95    }
96}
97