megabasterd/src/main/java/com/tonikelope/megabasterd/ChunkWriterManager.java

258 lines
10 KiB
Java

package com.tonikelope.megabasterd;
import static com.tonikelope.megabasterd.CryptTools.*;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import static java.lang.String.valueOf;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.CipherInputStream;
import javax.crypto.NoSuchPaddingException;
/**
*
* @author tonikelope
*/
public class ChunkWriterManager implements Runnable, SecureSingleThreadNotifiable {
private static final Logger LOG = Logger.getLogger(ChunkWriterManager.class.getName());
public static long calculateChunkOffset(long chunk_id, int size_multi) {
long[] offs = {0, 128, 384, 768, 1280, 1920, 2688};
return (chunk_id <= 7 ? offs[(int) chunk_id - 1] : (3584 + (chunk_id - 8) * 1024 * size_multi)) * 1024;
}
public static String genChunkUrl(String file_url, long file_size, long offset, long chunk_size) {
return file_url != null ? file_url + "/" + offset + (offset + chunk_size == file_size ? "" : "-" + (offset + chunk_size - 1)) : null;
}
public static void checkChunkID(long chunk_id, long file_size, long offset) throws ChunkInvalidException {
if (file_size > 0) {
if (offset >= file_size) {
throw new ChunkInvalidException(valueOf(chunk_id));
}
} else {
if (chunk_id > 1) {
throw new ChunkInvalidException(valueOf(chunk_id));
}
}
}
public static long calculateChunkSize(long chunk_id, long file_size, long offset, int size_multi) {
long chunk_size = (chunk_id >= 1 && chunk_id <= 7) ? chunk_id * 128 * 1024 : 1024 * 1024 * size_multi;
if (offset + chunk_size > file_size) {
chunk_size = file_size - offset;
}
return chunk_size;
}
private volatile long _last_chunk_id_written;
private volatile long _bytes_written;
private final long _file_size;
private final Download _download;
private final byte[] _byte_file_key;
private final byte[] _byte_iv;
private volatile boolean _exit;
private final Object _secure_notify_lock;
private boolean _notified;
private final String _chunks_dir;
public ChunkWriterManager(Download downloader) throws Exception {
_notified = false;
_exit = false;
_download = downloader;
_chunks_dir = _create_chunks_temp_dir();
_secure_notify_lock = new Object();
_file_size = _download.getFile_size();
_byte_file_key = initMEGALinkKey(_download.getFile_key());
_byte_iv = initMEGALinkKeyIV(_download.getFile_key());
if (_download.getProgress() == 0) {
_last_chunk_id_written = 0;
_bytes_written = 0;
} else {
_last_chunk_id_written = _download.getLast_chunk_id_dispatched();
_bytes_written = _download.getProgress();
}
}
public String getChunks_dir() {
return _chunks_dir;
}
@Override
public void secureNotify() {
synchronized (_secure_notify_lock) {
_notified = true;
_secure_notify_lock.notify();
}
}
@Override
public void secureWait() {
synchronized (_secure_notify_lock) {
while (!_notified) {
try {
_secure_notify_lock.wait(1000);
} catch (InterruptedException ex) {
_exit = true;
LOG.log(Level.SEVERE, ex.getMessage());
}
}
_notified = false;
}
}
public boolean isExit() {
return _exit;
}
public long getLast_chunk_id_written() {
return _last_chunk_id_written;
}
private String _create_chunks_temp_dir() {
File chunks_temp_dir = new File((_download.getCustom_chunks_dir() != null ? _download.getCustom_chunks_dir() : _download.getDownload_path()) + "/.mb_chunks_" + _download.getFile_key());
chunks_temp_dir.mkdirs();
return chunks_temp_dir.getAbsolutePath();
}
public void delete_chunks_temp_dir() {
try {
MiscTools.deleteDirectoryRecursion(Paths.get(getChunks_dir()));
} catch (IOException ex) {
Logger.getLogger(ChunkWriterManager.class.getName()).log(Level.SEVERE, ex.getMessage());
}
}
@Override
public void run() {
LOG.log(Level.INFO, "{0} ChunkWriterManager: let's do some work! {1}", new Object[]{Thread.currentThread().getName(), _download.getFile_name()});
LOG.log(Level.INFO, "{0} ChunkWriterManager LAST CHUNK WRITTEN -> [{1}] {2} {3}...", new Object[]{Thread.currentThread().getName(), _last_chunk_id_written, _bytes_written, _download.getFile_name()});
boolean download_finished = false;
if (_file_size > 0) {
while (!_exit && (!_download.isStopped() || !_download.getChunkworkers().isEmpty()) && _bytes_written < _file_size) {
if (!download_finished && _download.getProgress() == _file_size) {
_download.getMain_panel().getDownload_manager().getTransference_running_list().remove(_download);
_download.getMain_panel().getDownload_manager().secureNotify();
_download.getView().printStatusNormal("Download finished. Joining file chunks, please wait...");
_download.getView().getPause_button().setVisible(false);
_download.getMain_panel().getGlobal_dl_speed().detachTransference(_download);
_download.getView().getSpeed_label().setVisible(false);
_download.getView().getSlots_label().setVisible(false);
_download.getView().getSlot_status_label().setVisible(false);
_download.getView().getSlots_spinner().setVisible(false);
download_finished = true;
}
boolean chunk_io_error;
do {
synchronized (ChunkWriterManager.class) {
chunk_io_error = false;
try {
File chunk_file = new File(getChunks_dir() + "/" + new File(_download.getFile_name()).getName() + ".chunk" + String.valueOf(_last_chunk_id_written + 1));
while (chunk_file.exists() && chunk_file.canRead() && chunk_file.canWrite() && chunk_file.length() > 0) {
if (!download_finished && _download.getProgress() == _file_size) {
_download.getMain_panel().getDownload_manager().getTransference_running_list().remove(_download);
_download.getMain_panel().getDownload_manager().secureNotify();
_download.getView().printStatusNormal("Download finished. Joining file chunks, please wait...");
_download.getView().getPause_button().setVisible(false);
_download.getMain_panel().getGlobal_dl_speed().detachTransference(_download);
_download.getView().getSpeed_label().setVisible(false);
_download.getView().getSlots_label().setVisible(false);
_download.getView().getSlot_status_label().setVisible(false);
_download.getView().getSlots_spinner().setVisible(false);
download_finished = true;
}
byte[] buffer = new byte[MainPanel.DEFAULT_BYTE_BUFFER_SIZE];
int reads;
try ( CipherInputStream cis = new CipherInputStream(new BufferedInputStream(new FileInputStream(chunk_file)), genDecrypter("AES", "AES/CTR/NoPadding", _byte_file_key, forwardMEGALinkKeyIV(_byte_iv, _bytes_written)))) {
while ((reads = cis.read(buffer)) != -1) {
_download.getOutput_stream().write(buffer, 0, reads);
}
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException ex) {
LOG.log(Level.SEVERE, ex.getMessage());
}
_bytes_written += chunk_file.length();
_last_chunk_id_written++;
LOG.log(Level.INFO, "{0} ChunkWriterManager has written to disk chunk [{1}] {2} {3} {4}...", new Object[]{Thread.currentThread().getName(), _last_chunk_id_written, _bytes_written, _download.calculateLastWrittenChunk(_bytes_written), _download.getFile_name()});
chunk_file.delete();
chunk_file = new File(getChunks_dir() + "/" + new File(_download.getFile_name()).getName() + ".chunk" + String.valueOf(_last_chunk_id_written + 1));
}
} catch (IOException ex) {
chunk_io_error = true;
LOG.log(Level.WARNING, ex.getMessage());
MiscTools.pausar(1000);
}
}
} while (chunk_io_error);
if (!_exit && (!_download.isStopped() || !_download.getChunkworkers().isEmpty()) && _bytes_written < _file_size) {
LOG.log(Level.INFO, "{0} ChunkWriterManager waiting for chunk [{1}] {2}...", new Object[]{Thread.currentThread().getName(), _last_chunk_id_written + 1, _download.getFile_name()});
secureWait();
}
}
if (_bytes_written == _file_size) {
delete_chunks_temp_dir();
}
}
_exit = true;
_download.secureNotify();
LOG.log(Level.INFO, "{0} ChunkWriterManager: bye bye {1}", new Object[]{Thread.currentThread().getName(), _download.getFile_name()});
}
}