1dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski/* 2dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * Copyright (C) 2017 The Android Open Source Project 3dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * 4dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * Licensed under the Apache License, Version 2.0 (the "License"); 5dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * you may not use this file except in compliance with the License. 6dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * You may obtain a copy of the License at 7dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * 8dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * http://www.apache.org/licenses/LICENSE-2.0 9dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * 10dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * Unless required by applicable law or agreed to in writing, software 11dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * distributed under the License is distributed on an "AS IS" BASIS, 12dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * See the License for the specific language governing permissions and 14dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * limitations under the License. 15dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski */ 16dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 17dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskipackage com.android.documentsui.archives; 18dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 19dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.content.Context; 20dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.net.Uri; 21dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.os.CancellationSignal; 22dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.os.OperationCanceledException; 23a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewskiimport android.os.ParcelFileDescriptor.AutoCloseOutputStream; 24dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.os.ParcelFileDescriptor; 25dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.provider.DocumentsContract.Document; 26dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.support.annotation.Nullable; 27dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.util.Log; 28dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 29dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport com.android.internal.annotations.GuardedBy; 30dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport android.support.annotation.VisibleForTesting; 31dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 32dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport libcore.io.IoUtils; 33dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 34dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.io.FileDescriptor; 35dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.io.FileNotFoundException; 36dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.io.FileOutputStream; 37dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.io.IOException; 38dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.io.InputStream; 39dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.ArrayList; 40dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.HashSet; 41dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.List; 42dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.Set; 43dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.concurrent.ExecutorService; 44dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.concurrent.Executors; 45dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.concurrent.RejectedExecutionException; 46dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.concurrent.TimeUnit; 47dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.zip.ZipEntry; 48dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskiimport java.util.zip.ZipOutputStream; 49dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 50dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski/** 51dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * Provides basic implementation for creating archives. 52dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * 53dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * <p>This class is thread safe. 54dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski */ 55dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewskipublic class WriteableArchive extends Archive { 56dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski private static final String TAG = "WriteableArchive"; 57dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 58dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @GuardedBy("mEntries") 59dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski private final Set<String> mPendingEntries = new HashSet<>(); 60dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); 61dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @GuardedBy("mEntries") 62dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski private final ZipOutputStream mZipOutputStream; 63a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski private final AutoCloseOutputStream mOutputStream; 64dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 65a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski /** 66a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski * Takes ownership of the passed file descriptor. 67a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski */ 68dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski private WriteableArchive( 69dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Context context, 70a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski ParcelFileDescriptor fd, 71dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Uri archiveUri, 72dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski int accessMode, 73dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @Nullable Uri notificationUri) 74dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throws IOException { 75dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski super(context, archiveUri, accessMode, notificationUri); 76dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (!supportsAccessMode(accessMode)) { 77dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException("Unsupported access mode."); 78dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 79dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 80dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski addEntry(null /* no parent */, new ZipEntry("/")); // Root entry. 81a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski mOutputStream = new AutoCloseOutputStream(fd); 82a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski mZipOutputStream = new ZipOutputStream(mOutputStream); 83dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 84dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 85dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski private void addEntry(@Nullable ZipEntry parentEntry, ZipEntry entry) { 86dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final String entryPath = getEntryPath(entry); 87dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski synchronized (mEntries) { 88dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (entry.isDirectory()) { 89dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (!mTree.containsKey(entryPath)) { 90dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mTree.put(entryPath, new ArrayList<ZipEntry>()); 91dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 92dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 93dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mEntries.put(entryPath, entry); 94dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (parentEntry != null) { 95dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mTree.get(getEntryPath(parentEntry)).add(entry); 96dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 97dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 98dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 99dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 100dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski /** 101dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * @see ParcelFileDescriptor 102dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski */ 103dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski public static boolean supportsAccessMode(int accessMode) { 104dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski return accessMode == ParcelFileDescriptor.MODE_WRITE_ONLY; 105dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 106dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 107dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski /** 108dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * Creates a DocumentsArchive instance for writing into an archive file passed 109dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * as a file descriptor. 110dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * 111dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * This method takes ownership for the passed descriptor. The caller must 112dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * not use it after passing. 113dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * 114dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * @param context Context of the provider. 115dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * @param descriptor File descriptor for the archive's contents. 116dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * @param archiveUri Uri of the archive document. 117dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * @param accessMode Access mode for the archive {@see ParcelFileDescriptor}. 118dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * @param Uri notificationUri Uri for notifying that the archive file has changed. 119dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski */ 120dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @VisibleForTesting 121dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski public static WriteableArchive createForParcelFileDescriptor( 122dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Context context, ParcelFileDescriptor descriptor, Uri archiveUri, int accessMode, 123dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @Nullable Uri notificationUri) 124dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throws IOException { 125dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 126a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski return new WriteableArchive(context, descriptor, archiveUri, accessMode, 127a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski notificationUri); 128dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (Exception e) { 129dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // Since the method takes ownership of the passed descriptor, close it 130dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // on exception. 131dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski IoUtils.closeQuietly(descriptor); 132dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw e; 133dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 134dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 135dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 136dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @Override 137dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @VisibleForTesting 138dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 139dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throws FileNotFoundException { 140dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final ArchiveId parsedParentId = ArchiveId.fromDocumentId(parentDocumentId); 141dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski MorePreconditions.checkArgumentEquals(mArchiveUri, parsedParentId.mArchiveUri, 142dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski "Mismatching archive Uri. Expected: %s, actual: %s."); 143dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 144dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final boolean isDirectory = Document.MIME_TYPE_DIR.equals(mimeType); 145dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski ZipEntry entry; 146dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski String entryPath; 147dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 148dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski synchronized (mEntries) { 149dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final ZipEntry parentEntry = mEntries.get(parsedParentId.mPath); 150dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 151dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (parentEntry == null) { 152dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new FileNotFoundException(); 153dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 154dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 155dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (displayName.indexOf("/") != -1 || ".".equals(displayName) || "..".equals(displayName)) { 156dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException("Display name contains invalid characters."); 157dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 158dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 159dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if ("".equals(displayName)) { 160dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException("Display name cannot be empty."); 161dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 162dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 163dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 164dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski assert(parentEntry.getName().endsWith("/")); 165dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final String parentName = "/".equals(parentEntry.getName()) ? "" : parentEntry.getName(); 166dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final String entryName = parentName + displayName + (isDirectory ? "/" : ""); 167dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski entry = new ZipEntry(entryName); 168dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski entryPath = getEntryPath(entry); 169dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski entry.setSize(0); 170dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 171dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (mEntries.get(entryPath) != null) { 172dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException("The document already exist: " + entryPath); 173dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 174dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski addEntry(parentEntry, entry); 175dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 176dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 177dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (!isDirectory) { 178dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // For files, the contents will be written via openDocument. Since the contents 179dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // must be immediately followed by the contents, defer adding the header until 180dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // openDocument. All pending entires which haven't been written will be added 181dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // to the ZIP file in close(). 182dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski synchronized (mEntries) { 183dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mPendingEntries.add(entryPath); 184dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 185dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } else { 186dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 187dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski synchronized (mEntries) { 188dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mZipOutputStream.putNextEntry(entry); 189dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 190dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (IOException e) { 191dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException( 192dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski "Failed to create a file in the archive: " + entryPath, e); 193dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 194dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 195dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 196dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski return createArchiveId(entryPath).toDocumentId(); 197dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 198dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 199dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @Override 200dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski public ParcelFileDescriptor openDocument( 201dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski String documentId, String mode, @Nullable final CancellationSignal signal) 202dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throws FileNotFoundException { 203dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski MorePreconditions.checkArgumentEquals("w", mode, 204dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski "Invalid mode. Only writing \"w\" supported, but got: \"%s\"."); 205dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final ArchiveId parsedId = ArchiveId.fromDocumentId(documentId); 206dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski MorePreconditions.checkArgumentEquals(mArchiveUri, parsedId.mArchiveUri, 207dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski "Mismatching archive Uri. Expected: %s, actual: %s."); 208dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 209dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final ZipEntry entry; 210dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski synchronized (mEntries) { 211dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski entry = mEntries.get(parsedId.mPath); 212dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (entry == null) { 213dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new FileNotFoundException(); 214dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 215dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 216dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (!mPendingEntries.contains(parsedId.mPath)) { 217dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException("Files can be written only once."); 218dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 219dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mPendingEntries.remove(parsedId.mPath); 220dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 221dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 222dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski ParcelFileDescriptor[] pipe; 223dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 224dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski pipe = ParcelFileDescriptor.createReliablePipe(); 225dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (IOException e) { 226dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // Ideally we'd simply throw IOException to the caller, but for consistency 227dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // with DocumentsProvider::openDocument, converting it to IllegalStateException. 228dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException("Failed to open the document.", e); 229dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 230dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final ParcelFileDescriptor inputPipe = pipe[0]; 231dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 232dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 233dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mExecutor.execute( 234dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski new Runnable() { 235dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @Override 236dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski public void run() { 237dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try (final ParcelFileDescriptor.AutoCloseInputStream inputStream = 238dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski new ParcelFileDescriptor.AutoCloseInputStream(inputPipe)) { 239dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 240dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski synchronized (mEntries) { 241dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mZipOutputStream.putNextEntry(entry); 242dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final byte buffer[] = new byte[32 * 1024]; 243dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski int bytes; 244dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski long size = 0; 245dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski while ((bytes = inputStream.read(buffer)) != -1) { 246dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski if (signal != null) { 247dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski signal.throwIfCanceled(); 248dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 249dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mZipOutputStream.write(buffer, 0, bytes); 250dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski size += bytes; 251dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 252dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski entry.setSize(size); 253dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mZipOutputStream.closeEntry(); 254dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 255dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (IOException e) { 256dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // Catch the exception before the outer try-with-resource closes 257dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // the pipe with close() instead of closeWithError(). 258dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 259dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Log.e(TAG, "Failed while writing to a file.", e); 260dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski inputPipe.closeWithError("Writing failure."); 261dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (IOException e2) { 262dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Log.e(TAG, "Failed to close the pipe after an error.", e2); 263dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 264dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 265dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (OperationCanceledException e) { 266dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // Cancelled gracefully. 267dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (IOException e) { 268dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // Input stream auto-close error. Close quietly. 269dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 270dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 271dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski }); 272dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (RejectedExecutionException e) { 273dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski IoUtils.closeQuietly(pipe[0]); 274dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski IoUtils.closeQuietly(pipe[1]); 275dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski throw new IllegalStateException("Failed to initialize pipe."); 276dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 277dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 278dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski return pipe[1]; 279dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 280dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 281dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski /** 282dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski * Closes the archive. Blocks until all enqueued pipes are completed. 283dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski */ 284dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski @Override 285dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski public void close() { 286dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // Waits until all enqueued pipe requests are completed. 287dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mExecutor.shutdown(); 288dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 289dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski final boolean result = mExecutor.awaitTermination( 290dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Long.MAX_VALUE, TimeUnit.MILLISECONDS); 291dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski assert(result); 292dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (InterruptedException e) { 293dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Log.e(TAG, "Opened files failed to be fullly written.", e); 294dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 295dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 296dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski // Flush all pending entries. They will all have empty size. 297dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski synchronized (mEntries) { 298dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski for (final String path : mPendingEntries) { 299dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 300dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mZipOutputStream.putNextEntry(mEntries.get(path)); 301dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mZipOutputStream.closeEntry(); 302dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (IOException e) { 303dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Log.e(TAG, "Failed to flush empty entries.", e); 304dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 305dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 306dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski 307dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski try { 308dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski mZipOutputStream.close(); 309dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } catch (IOException e) { 310dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski Log.e(TAG, "Failed while closing the ZIP file.", e); 311dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 312dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 313a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski 314a18ea7ecbc82adb7da88e77769895ed60264ea13Tomasz Mikolajewski IoUtils.closeQuietly(mOutputStream); 315dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski } 316dc235d2796593d0275843b7a5897c72c5603f6ccTomasz Mikolajewski}; 317