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