/*
* Overchan Android (Meta Imageboard Client)
* Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan>
*
* 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 nya.miku.wishmaster.common;
import java.io.Closeable;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Locale;
import nya.miku.wishmaster.api.interfaces.CancellableTask;
import nya.miku.wishmaster.api.interfaces.ProgressListener;
public class IOUtils {
private static final String TAG = "IOUtils";
private IOUtils() {}
/**
* Копирование данных из потока from в поток to. (буфер 8КБ)
*/
public static void copyStream(InputStream from, OutputStream to) throws IOException {
byte data[] = new byte[8192];
int count;
while ((count = from.read(data)) != -1) {
to.write(data, 0, count);
}
}
/**
* Безопасное (тихое) закрытие {@link Closeable} объекта (исключение поглощается и пишется в log).
*/
public static void closeQuietly(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
Logger.e(TAG, e);
}
}
}
/**
* Проверить, если исключение возникло по причине отсутствия свободного места на диске
* @param e исключение
*/
public static boolean isENOSPC(Exception e) {
return isENOSPC(e.getMessage());
}
/**
* Проверить, если исключение возникло по причине отсутствия свободного места на диске
* @param exMessage сообщение исключения ({@link Exception#getMessage()})
*/
private static boolean isENOSPC(String exMessage) {
return (exMessage != null && (exMessage.toUpperCase(Locale.US).contains("ENOSPC") || exMessage.equals("No space left on device")));
}
/**
* Модифицирует входной поток, добавляет отображение прогресса в ProgressListener и возможность отмены задачи CancellableTask.
* Может принимать null в качестве одного (или нескольких) параметров, в этом случае данный объект просто не будут привязан.
* @param in исходный поток
* @param listener интерфейс отслеживания прогресса
* @param task задача, отмена которой прервёт поток
* @return модифицированный поток
*/
public static InputStream modifyInputStream(InputStream in, ProgressListener listener, CancellableTask task) {
if (in != null) {
if (listener != null) in = new ProgressInputStream(in, listener);
if (task != null) in = new CancellableInputStream(in, task);
}
return in;
}
/**
* Модифицирует выходной поток, добавляет отображение прогресса в ProgressListener и возможность отмены загрузки CancellableTask.
* Может принимать null в качестве одного (или нескольких) параметров, в этом случае данный объект просто не будут привязан.
* @param in исходный поток
* @param listener интерфейс отслеживания прогресса
* @param task задача, отмена которой прервёт поток
* @return модифицированный поток
*/
public static OutputStream modifyOutputStream(OutputStream out, ProgressListener listener, CancellableTask task) {
if (out != null) {
if (listener != null) out = new ProgressOutputStream(out, listener);
if (task != null) out = new CancellableOutputStream(out, task);
}
return out;
}
/**
* Модификация входного потока, возможность отмены передачи
* @author miku-nyan
*
*/
public static class CancellableInputStream extends FilterInputStream {
private final CancellableTask task;
/**
* Конструктор класса
* @param in исходный поток
* @param task задача, отмена которой прервёт поток
*/
public CancellableInputStream(InputStream in, CancellableTask task) {
super(in);
this.task = task;
}
@Override
public int read() throws IOException {
if (checkCancelled()) throw new InterruptedStreamException();
return super.read();
}
@Override
public int read(byte[] b) throws IOException {
if (checkCancelled()) throw new InterruptedStreamException();
return super.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (checkCancelled()) throw new InterruptedStreamException();
return super.read(b, off, len);
}
private boolean checkCancelled() {
return task != null && task.isCancelled();
}
}
/**
* Модификация выходного потока, возможность отмены передачи
* @author miku-nyan
*
*/
public static class CancellableOutputStream extends FilterOutputStream {
private final CancellableTask task;
/**
* Конструктор класса
* @param out исходный поток
* @param task задача, отмена которой прервёт поток
*/
public CancellableOutputStream(OutputStream out, CancellableTask task) {
super(out);
this.task = task;
}
@Override
public void write(int b) throws IOException {
if (checkCancelled()) throw new InterruptedStreamException();;
super.write(b);
}
@Override
public void write(byte[] b) throws IOException {
if (checkCancelled()) throw new InterruptedStreamException();;
super.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (checkCancelled()) throw new InterruptedStreamException();;
super.write(b, off, len);
}
private boolean checkCancelled() {
return task != null && task.isCancelled();
}
}
/**
* Модификация входного потока, передача текущего прогресса передачи слушателю listener
* @author miku-nyan
*
*/
public static class ProgressInputStream extends FilterInputStream {
private final ProgressListener listener;
private volatile long totalNumBytesRead;
/**
* Конструктор класса
* @param in исходный поток
* @param listener интерфейс отслеживания прогресса
*/
public ProgressInputStream(InputStream in, ProgressListener listener) {
super(in);
this.listener = listener;
}
@Override
public int read() throws IOException {
int b = in.read();
if (b != -1) updateProgress(1);
return b;
}
@Override
public int read(byte[] b) throws IOException {
return (int) updateProgress(in.read(b));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return (int) updateProgress(in.read(b, off, len));
}
@Override
public long skip(long n) throws IOException {
return updateProgress(in.skip(n));
}
private long updateProgress(long numBytesRead) {
if (numBytesRead > 0) {
totalNumBytesRead += numBytesRead;
listener.setProgress(totalNumBytesRead);
}
return numBytesRead;
}
}
/**
* Модификация выходного потока, передача текущего прогресса передачи слушателю listener
* @author miku-nyan
*
*/
public static class ProgressOutputStream extends FilterOutputStream {
private final ProgressListener listener;
private volatile long totalNumBytesWrite;
/**
* Конструктор класса
* @param out исходный поток
* @param listener интерфейс отслеживания прогресса
*/
public ProgressOutputStream(OutputStream out, ProgressListener listener) {
super(out);
this.listener = listener;
}
@Override
public void write(int b) throws IOException {
out.write(b);
updateProgress(1);
}
@Override
public void write(byte[] b) throws IOException {
out.write(b);
updateProgress(b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
updateProgress(len);
}
private void updateProgress(long numBytesWrite) {
if (numBytesWrite > 0) {
totalNumBytesWrite += numBytesWrite;
listener.setProgress(totalNumBytesWrite);
}
}
}
/**
* Исключение возбуждается, если задача, взаимодействующая с потоком, была отменена
* @author miku-nyan
*
*/
public static class InterruptedStreamException extends IOException {
private static final long serialVersionUID = 1L;
}
}