1/* 2 * Copyright (C) 2010 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.email.service; 18 19import com.android.email.FixedLengthInputStream; 20import com.android.email.mail.store.imap.ImapResponse; 21import com.android.email.mail.store.imap.ImapResponseParser; 22import com.android.email.mail.store.imap.ImapString; 23import com.android.emailcommon.Logging; 24import com.android.emailcommon.TempDirectory; 25import com.android.emailcommon.utility.Utility; 26import com.android.mail.utils.LogUtils; 27 28import org.apache.commons.io.IOUtils; 29 30import java.io.ByteArrayInputStream; 31import java.io.File; 32import java.io.FileInputStream; 33import java.io.FileNotFoundException; 34import java.io.FileOutputStream; 35import java.io.IOException; 36import java.io.InputStream; 37import java.io.OutputStream; 38 39/** 40 * Subclass of {@link ImapString} used for literals backed by a temp file. 41 */ 42public class ImapTempFileLiteral extends ImapString { 43 /* package for test */ final File mFile; 44 45 /** Size is purely for toString() */ 46 private final int mSize; 47 48 /* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException { 49 mSize = stream.getLength(); 50 mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory()); 51 52 // Unfortunately, we can't really use deleteOnExit(), because temp filenames are random 53 // so it'd simply cause a memory leak. 54 // deleteOnExit() simply adds filenames to a static list and the list will never shrink. 55 // mFile.deleteOnExit(); 56 OutputStream out = new FileOutputStream(mFile); 57 IOUtils.copy(stream, out); 58 out.close(); 59 } 60 61 /** 62 * Make sure we delete the temp file. 63 * 64 * We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort. 65 */ 66 @Override 67 protected void finalize() throws Throwable { 68 try { 69 destroy(); 70 } finally { 71 super.finalize(); 72 } 73 } 74 75 @Override 76 public InputStream getAsStream() { 77 checkNotDestroyed(); 78 try { 79 return new FileInputStream(mFile); 80 } catch (FileNotFoundException e) { 81 // It's probably possible if we're low on storage and the system clears the cache dir. 82 LogUtils.w(Logging.LOG_TAG, "ImapTempFileLiteral: Temp file not found"); 83 84 // Return 0 byte stream as a dummy... 85 return new ByteArrayInputStream(new byte[0]); 86 } 87 } 88 89 @Override 90 public String getString() { 91 checkNotDestroyed(); 92 try { 93 byte[] bytes = IOUtils.toByteArray(getAsStream()); 94 // Prevent crash from OOM; we've seen this, but only rarely and not reproducibly 95 if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) { 96 throw new IOException(); 97 } 98 return Utility.fromAscii(bytes); 99 } catch (IOException e) { 100 LogUtils.w(Logging.LOG_TAG, "ImapTempFileLiteral: Error while reading temp file", e); 101 return ""; 102 } 103 } 104 105 @Override 106 public void destroy() { 107 try { 108 if (!isDestroyed() && mFile.exists()) { 109 mFile.delete(); 110 } 111 } catch (RuntimeException re) { 112 // Just log and ignore. 113 LogUtils.w(Logging.LOG_TAG, "Failed to remove temp file: " + re.getMessage()); 114 } 115 super.destroy(); 116 } 117 118 @Override 119 public String toString() { 120 return String.format("{%d byte literal(file)}", mSize); 121 } 122 123 public boolean tempFileExistsForTest() { 124 return mFile.exists(); 125 } 126} 127