/* * 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.http; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Random; import nya.miku.wishmaster.api.interfaces.CancellableTask; import nya.miku.wishmaster.api.interfaces.ProgressListener; import nya.miku.wishmaster.common.IOUtils; import cz.msebera.android.httpclient.Header; import cz.msebera.android.httpclient.HttpEntity; import cz.msebera.android.httpclient.entity.ContentType; import cz.msebera.android.httpclient.entity.mime.HttpMultipartMode; import cz.msebera.android.httpclient.entity.mime.MultipartEntityBuilder; import cz.msebera.android.httpclient.entity.mime.content.ContentBody; import cz.msebera.android.httpclient.entity.mime.content.FileBody; import cz.msebera.android.httpclient.entity.mime.content.StringBody; /** * Билдер модифицированных Multipart HttpEntity. * Позволяет отслеживать текущий прогресс передачи, или прервать поток. * По-другому генерируется boundary (как браузеры, начиная с дефисов, для совместимости с некоторыми бордами). * @author miku-nyan * */ public class ExtendedMultipartBuilder { private static final int RANDOMHASH_TAIL_SIZE = 6; private static Random random = new Random(); private static Random getRandom() { if (random == null) random = new Random(); return random; } private final MultipartEntityBuilder builder; private ProgressListener listener = null; private CancellableTask task = null; public ExtendedMultipartBuilder() { builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setBoundary(generateBoundary()); } public static ExtendedMultipartBuilder create() { return new ExtendedMultipartBuilder(); } public ExtendedMultipartBuilder setCharset(Charset charset) { builder.setCharset(charset); return this; } public ExtendedMultipartBuilder addPart(String key, ContentBody body) { builder.addPart(key, body); return this; } /** * Добавить строку в кодировке UTF-8 * @param key имя (ключ) * @param value строка * @return этот объект */ public ExtendedMultipartBuilder addString(String key, String value) { return addPart(key, new StringBody(value, ContentType.create("text/plain", "UTF-8"))); } private ExtendedMultipartBuilder addFile(String key, File value, final int randomTail) { return addPart(key, new FileBody(value) { @Override public long getContentLength() { return super.getContentLength() + randomTail; } @Override public void writeTo(OutputStream out) throws IOException { super.writeTo(out); if (randomTail > 0) { byte[] buf = new byte[randomTail]; getRandom().nextBytes(buf); out.write(buf); } } }); } /** * Добавить файл * @param key имя (ключ) * @param file прикрепляемый файл * @param uniqueHash если true, добавит в конец файла несколько рандомных байтов, чтобы создать уникальный хэш * @return этот объект */ public ExtendedMultipartBuilder addFile(String key, File file, boolean uniqueHash) { return addFile(key, file, uniqueHash ? RANDOMHASH_TAIL_SIZE : 0); } /** * Добавить файл * @param key имя (ключ) * @param file прикрепляемый файл * @return этот объект */ public ExtendedMultipartBuilder addFile(String key, File file) { return addFile(key, file, false); } /** * Установить отслеживатель прогресса и отменяемую задачу * @param listener интерфейс отслеживания прогресса * @param task задача, отмена которой прервёт поток * @return этот объект */ public ExtendedMultipartBuilder setDelegates(ProgressListener listener, CancellableTask task) { this.listener = listener; this.task = task; return this; } public HttpEntity build() { return new HttpEntityWrapper(builder.build(), listener, task); } protected String generateBoundary() { StringBuilder stringBuilder = new StringBuilder(); for (int i=0; i<27; ++i) stringBuilder.append("-"); int length = 26 + getRandom().nextInt(4); for (int i=0; i<length; ++i) stringBuilder.append(Integer.toString(getRandom().nextInt(10))); return stringBuilder.toString(); } private static class HttpEntityWrapper implements HttpEntity { private final HttpEntity entity; private final ProgressListener listener; private final CancellableTask task; private HttpEntityWrapper(HttpEntity entity, ProgressListener listener, CancellableTask task) { this.entity = entity; this.listener = listener; this.task = task; } @SuppressWarnings("deprecation") @Override public void consumeContent() throws IOException { entity.consumeContent(); } @Override public InputStream getContent() throws IOException, IllegalStateException { return entity.getContent(); } @Override public Header getContentEncoding() { return entity.getContentEncoding(); } @Override public long getContentLength() { return entity.getContentLength(); } @Override public Header getContentType() { return entity.getContentType(); } @Override public boolean isChunked() { return entity.isChunked(); } @Override public boolean isRepeatable() { return entity.isRepeatable(); } @Override public boolean isStreaming() { return entity.isStreaming(); } @Override public void writeTo(OutputStream outstream) throws IOException { if (listener != null) listener.setMaxValue(this.getContentLength()); entity.writeTo(IOUtils.modifyOutputStream(outstream, listener, task)); if (listener != null) listener.setIndeterminate(); } } }