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 
17 package com.android.documentsui.archives;
18 
19 import android.os.FileUtils;
20 import android.os.ProxyFileDescriptorCallback;
21 import android.system.ErrnoException;
22 import android.system.OsConstants;
23 
24 import java.io.IOException;
25 import java.io.InputStream;
26 
27 import org.apache.commons.compress.archivers.ArchiveEntry;
28 import org.apache.commons.compress.archivers.ArchiveException;
29 import org.apache.commons.compress.compressors.CompressorException;
30 
31 /**
32  * Provides a backend for a seekable file descriptors for files in archives.
33  */
34 public class Proxy extends ProxyFileDescriptorCallback {
35     private final ArchiveHandle mFile;
36     private final ArchiveEntry mEntry;
37     private InputStream mInputStream = null;
38     private long mOffset = 0;
39 
Proxy(ArchiveHandle file, ArchiveEntry entry)40     Proxy(ArchiveHandle file, ArchiveEntry entry)
41             throws IOException, CompressorException, ArchiveException {
42         mFile = file;
43         mEntry = entry;
44         recreateInputStream();
45     }
46 
47     @Override
onGetSize()48     public long onGetSize() throws ErrnoException {
49         return mEntry.getSize();
50     }
51 
52     @Override
onRead(long offset, int size, byte[] data)53     public int onRead(long offset, int size, byte[] data) throws ErrnoException {
54         // TODO: Add a ring buffer to prevent expensive seeks.
55         if (offset < mOffset) {
56             try {
57                 recreateInputStream();
58             } catch (IOException e) {
59                 throw new ErrnoException("onRead", OsConstants.EIO);
60             } catch (ArchiveException e) {
61                 throw new ErrnoException("onRead archive exception. " + e.getMessage(),
62                         OsConstants.EIO);
63             } catch (CompressorException e) {
64                 throw new ErrnoException("onRead uncompress exception. " + e.getMessage(),
65                         OsConstants.EIO);
66             }
67         }
68 
69         while (mOffset < offset) {
70             try {
71                 mOffset +=  mInputStream.skip(offset - mOffset);
72             } catch (IOException e) {
73                 throw new ErrnoException("onRead", OsConstants.EIO);
74             }
75         }
76 
77         int remainingSize = size;
78         while (remainingSize > 0) {
79             try {
80                 int bytes = mInputStream.read(data, size - remainingSize, remainingSize);
81                 if (bytes <= 0) {
82                     return size - remainingSize;
83                 }
84                 remainingSize -= bytes;
85                 mOffset += bytes;
86             } catch (IOException e) {
87                 throw new ErrnoException("onRead", OsConstants.EIO);
88             }
89         }
90 
91         return size - remainingSize;
92     }
93 
onRelease()94     @Override public void onRelease() {
95         FileUtils.closeQuietly(mInputStream);
96     }
97 
recreateInputStream()98     private void recreateInputStream()
99             throws IOException, CompressorException, ArchiveException {
100         FileUtils.closeQuietly(mInputStream);
101         mInputStream = mFile.getInputStream(mEntry);
102         mOffset = 0;
103     }
104 }
105