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.mtp;
18
19import android.content.Context;
20import android.mtp.MtpObjectInfo;
21import android.os.ParcelFileDescriptor;
22import android.system.ErrnoException;
23import android.system.Os;
24import android.system.OsConstants;
25
26import com.android.internal.util.Preconditions;
27
28import java.io.File;
29import java.io.IOException;
30
31class MtpFileWriter implements AutoCloseable {
32    final ParcelFileDescriptor mCacheFd;
33    final String mDocumentId;
34    boolean mDirty;
35
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
48    String getDocumentId() {
49        return mDocumentId;
50    }
51
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
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
105    public void close() throws IOException {
106        mCacheFd.close();
107    }
108}
109