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.mtp; 18 19 import android.content.Context; 20 import android.mtp.MtpObjectInfo; 21 import android.os.ParcelFileDescriptor; 22 import android.system.ErrnoException; 23 import android.system.Os; 24 import android.system.OsConstants; 25 26 import com.android.internal.util.Preconditions; 27 28 import java.io.File; 29 import java.io.IOException; 30 31 class MtpFileWriter implements AutoCloseable { 32 final ParcelFileDescriptor mCacheFd; 33 final String mDocumentId; 34 boolean mDirty; 35 MtpFileWriter(Context context, String documentId)36 MtpFileWriter(Context context, String documentId) throws IOException { 37 mDocumentId = documentId; 38 mDirty = false; 39 final File tempFile = File.createTempFile("mtp", "tmp", context.getCacheDir()); 40 mCacheFd = ParcelFileDescriptor.open( 41 tempFile, 42 ParcelFileDescriptor.MODE_READ_WRITE | 43 ParcelFileDescriptor.MODE_TRUNCATE | 44 ParcelFileDescriptor.MODE_CREATE); 45 tempFile.delete(); 46 } 47 getDocumentId()48 String getDocumentId() { 49 return mDocumentId; 50 } 51 write(long offset, int size, byte[] bytes)52 int write(long offset, int size, byte[] bytes) throws IOException, ErrnoException { 53 Preconditions.checkArgumentNonnegative(offset, "offset"); 54 Preconditions.checkArgumentNonnegative(size, "size"); 55 Preconditions.checkArgument(size <= bytes.length); 56 if (size == 0) { 57 return 0; 58 } 59 mDirty = true; 60 Os.lseek(mCacheFd.getFileDescriptor(), offset, OsConstants.SEEK_SET); 61 return Os.write(mCacheFd.getFileDescriptor(), bytes, 0, size); 62 } 63 flush(MtpManager manager, MtpDatabase database, int[] operationsSupported)64 void flush(MtpManager manager, MtpDatabase database, int[] operationsSupported) 65 throws IOException, ErrnoException { 66 // Skip unnecessary flush. 67 if (!mDirty) { 68 return; 69 } 70 71 // Get the placeholder object info. 72 final Identifier identifier = database.createIdentifier(mDocumentId); 73 final MtpObjectInfo placeholderObjectInfo = 74 manager.getObjectInfo(identifier.mDeviceId, identifier.mObjectHandle); 75 76 // Delete the target object info if it already exists (as a placeholder). 77 manager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 78 79 // Create the target object info with a correct file size and upload the file. 80 final long size = Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_END); 81 final MtpObjectInfo targetObjectInfo = new MtpObjectInfo.Builder(placeholderObjectInfo) 82 .setCompressedSize(size) 83 .build(); 84 85 Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_SET); 86 final int newObjectHandle = manager.createDocument( 87 identifier.mDeviceId, targetObjectInfo, mCacheFd); 88 89 final MtpObjectInfo newObjectInfo = manager.getObjectInfo( 90 identifier.mDeviceId, newObjectHandle); 91 final Identifier parentIdentifier = 92 database.getParentIdentifier(identifier.mDocumentId); 93 database.updateObject( 94 identifier.mDocumentId, 95 identifier.mDeviceId, 96 parentIdentifier.mDocumentId, 97 operationsSupported, 98 newObjectInfo, 99 size); 100 101 mDirty = false; 102 } 103 104 @Override close()105 public void close() throws IOException { 106 mCacheFd.close(); 107 } 108 } 109