/* * Copyright (c) 2013, Psiphon Inc. * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package ca.psiphon.ploggy; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; import android.util.Pair; /** * Helpers for managing local resource files * * Supports two publishing modes: * 1. Picture, in which metadata is omitted from the shared copy, which is also automatically scaled down in data size * 2. Raw, in which the exact local file is shared (no metadata stripped, no scaling) * */ public class Resources { private static final String LOG_TAG = "Resources"; private static final String LOCAL_RESOURCE_TEMPORARY_COPY_FILENAME_FORMAT_STRING = "%s.ploggyLocalResource"; public static class MessageWithAttachments { public final Data.Message mMessage; public final List<Data.LocalResource> mLocalResources; public MessageWithAttachments( Data.Message message, List<Data.LocalResource> localResources) { mMessage = message; mLocalResources = localResources; } } public static MessageWithAttachments createMessageWithAttachment( Date messageTimestamp, String messageContent, Data.LocalResource.Type localResourceType, String attachmentMimeType, String attachmentFilePath) throws Utils.ApplicationError { // Create a resource with a random ID and add it to the message // Friends only see the random ID, not the local resource file name // Note: never reusing resource IDs, even if same local e.g., file, has been published previously String id = Utils.formatFingerprint(Utils.getRandomBytes(Protocol.RESOURCE_ID_LENGTH)); List<Data.Resource> messageAttachments = new ArrayList<Data.Resource>(); List<Data.LocalResource> localResources = new ArrayList<Data.LocalResource>(); if (attachmentMimeType != null || attachmentFilePath != null) { File file = new File(attachmentFilePath); if (localResourceType == Data.LocalResource.Type.PICTURE) { // If the resource is transformed (e.g., picture is auto-scaled-down and has no metadata) // then we need to do that now, to get the correct size file = makeScaledDownPictureFileCopy(attachmentFilePath, id); } messageAttachments.add(new Data.Resource(id, attachmentMimeType, file.length())); localResources.add(new Data.LocalResource(localResourceType, id, attachmentMimeType, attachmentFilePath, null)); } return new MessageWithAttachments(new Data.Message(messageTimestamp, messageContent, messageAttachments), localResources); } public static InputStream openLocalResourceForReading( Data.LocalResource localResource, Pair<Long, Long> range) throws Utils.ApplicationError { InputStream inputStream = null; try { File file = new File(localResource.mFilePath); if (localResource.mType == Data.LocalResource.Type.PICTURE) { file = makeScaledDownPictureFileCopy(localResource.mFilePath, localResource.mResourceId); } inputStream = new FileInputStream(file); // TODO: ignoring endAt (range.second)! if (range != null && range.first != inputStream.skip(range.first)) { throw new Utils.ApplicationError(LOG_TAG, "failed to seek to requested offset"); } return inputStream; } catch (IOException e) { if (inputStream != null) { try { inputStream.close(); } catch (IOException e1) { } } throw new Utils.ApplicationError(LOG_TAG, e); } } private static File getTemporaryCopyFile(String resourceId) { File directory = Utils.getApplicationContext().getCacheDir(); directory.mkdirs(); return new File(directory, String.format(LOCAL_RESOURCE_TEMPORARY_COPY_FILENAME_FORMAT_STRING, resourceId)); } private static File makeScaledDownPictureFileCopy(String sourceFilePath, String resourceId) throws Utils.ApplicationError { File file = new File(sourceFilePath); File temporaryCopyFile = getTemporaryCopyFile(resourceId); // TODO: date check sufficient? if (!temporaryCopyFile.exists() || new Date(file.lastModified()).after(new Date(temporaryCopyFile.lastModified()))) { Pictures.copyScaledBitmapWithoutMetadata(file, temporaryCopyFile); } return temporaryCopyFile; } }