/*
* Copyright 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.acra.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.android.internal.util.Predicate;
import org.acra.ACRA;
import org.acra.ACRAConstants;
import org.acra.collections.BoundedLinkedList;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.LinkedList;
import java.util.List;
import static org.acra.ACRA.LOG_TAG;
/**
* @author William Ferguson, F43nd1r
* @since 4.6.0
*/
public final class IOUtils {
private static final Predicate<String> DEFAULT_FILTER = new Predicate<String>() {
@Override
public boolean apply(String s) {
return true;
}
};
private static final int NO_LIMIT = -1;
private static final int READ_TIMEOUT = 3000;
private IOUtils() {
}
/**
* Closes a Closeable.
*
* @param closeable Closeable to close. If closeable is null then method just returns.
*/
public static void safeClose(@Nullable Closeable closeable) {
if (closeable == null) return;
try {
closeable.close();
} catch (IOException ignored) {
// We made out best effort to release this resource. Nothing more we can do.
}
}
/**
* Reads an InputStream into a string
*
* @param input InputStream to read.
* @return the String that was read.
* @throws IOException if the InputStream could not be read.
*/
@NonNull
public static String streamToString(@NonNull InputStream input) throws IOException {
return streamToString(input, DEFAULT_FILTER, NO_LIMIT);
}
/**
* Reads an InputStream into a string
*
* @param input InputStream to read.
* @param filter should return false for lines which should be excluded
* @return the String that was read.
* @throws IOException if the InputStream could not be read.
*/
@NonNull
public static String streamToString(@NonNull InputStream input, Predicate<String> filter) throws IOException {
return streamToString(input, filter, NO_LIMIT);
}
/**
* Reads an InputStream into a string
*
* @param input InputStream to read.
* @param limit the maximum number of lines to read (the last x lines are kept)
* @return the String that was read.
* @throws IOException if the InputStream could not be read.
*/
@NonNull
public static String streamToString(@NonNull InputStream input, int limit) throws IOException {
return streamToString(input, DEFAULT_FILTER, limit);
}
/**
* Reads an InputStream into a string
*
* @param input InputStream to read.
* @param filter Predicate that should return false for lines which should be excluded.
* @param limit the maximum number of lines to read (the last x lines are kept)
* @return the String that was read.
* @throws IOException if the InputStream could not be read.
*/
@NonNull
public static String streamToString(@NonNull InputStream input, Predicate<String> filter, int limit) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES);
try {
String line;
final List<String> buffer = limit == NO_LIMIT ? new LinkedList<String>() : new BoundedLinkedList<String>(limit);
while ((line = reader.readLine()) != null) {
if (filter.apply(line)) {
buffer.add(line);
}
}
return TextUtils.join("\n", buffer);
} finally {
safeClose(reader);
}
}
/**
* Reads an InputStream into a string without blocking the current thread.
* It has a default timeout of 3 seconds.
*
* @param input InputStream to read.
* @param filter Predicate that should return false for lines which should be excluded.
* @param limit the maximum number of lines to read (the last x lines are kept).
* @return the String that was read.
* @throws IOException if the InputStream could not be read.
*/
@NonNull
public static String streamToStringNonBlockingRead(@NonNull InputStream input, Predicate<String> filter, int limit) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES);
final NonBlockingBufferedReader nonBlockingReader = new NonBlockingBufferedReader(reader);
try {
final List<String> buffer = limit == NO_LIMIT ? new LinkedList<String>() : new BoundedLinkedList<String>(limit);
final long end = System.currentTimeMillis() + READ_TIMEOUT;
try {
while ((System.currentTimeMillis() < end)) {
final String line = nonBlockingReader.readLine();
if (line == null) {
break;
}
if (filter.apply(line)) {
buffer.add(line);
}
}
} catch (InterruptedException e) {
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Interrupted while reading stream", e);
}
return TextUtils.join("\n", buffer);
} finally {
nonBlockingReader.close();
}
}
public static void deleteReport(@NonNull File file) {
final boolean deleted = file.delete();
if (!deleted) {
ACRA.log.w(LOG_TAG, "Could not delete error report : " + file);
}
}
public static void writeStringToFile(@NonNull File file, @NonNull String content) throws IOException {
final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), ACRAConstants.UTF8);
try {
writer.write(content);
writer.flush();
} finally {
safeClose(writer);
}
}
}