implement resume across restart

pull/4/head
Zlatin Balevsky 2019-06-09 17:35:32 +01:00
parent 42621a2dfb
commit dc6b1199f3
4 changed files with 66 additions and 14 deletions

View File

@ -31,5 +31,4 @@ The first time you run MuWire it will ask you to select a nickname. This nickna
### Known bugs and limitations ### Known bugs and limitations
* Many UI features you would expect are not there yet * Many UI features you would expect are not there yet
* Downloads in progress do not get remembered between restarts

View File

@ -191,7 +191,7 @@ public class Core {
eventBus.register(ResultsEvent.class, searchManager) eventBus.register(ResultsEvent.class, searchManager)
log.info("initializing download manager") log.info("initializing download manager")
DownloadManager downloadManager = new DownloadManager(eventBus, i2pConnector, new File(home, "incompletes"), me) DownloadManager downloadManager = new DownloadManager(eventBus, i2pConnector, home, me)
eventBus.register(UIDownloadEvent.class, downloadManager) eventBus.register(UIDownloadEvent.class, downloadManager)
eventBus.register(UILoadedEvent.class, downloadManager) eventBus.register(UILoadedEvent.class, downloadManager)
eventBus.register(FileDownloadedEvent.class, downloadManager) eventBus.register(FileDownloadedEvent.class, downloadManager)

View File

@ -2,12 +2,18 @@ package com.muwire.core.download
import com.muwire.core.connection.I2PConnector import com.muwire.core.connection.I2PConnector
import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHasher
import com.muwire.core.util.DataUtil
import groovy.json.JsonBuilder
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import net.i2p.data.Base64 import net.i2p.data.Base64
import net.i2p.data.Destination import net.i2p.data.Destination
import net.i2p.util.ConcurrentHashSet import net.i2p.util.ConcurrentHashSet
import com.muwire.core.EventBus import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.Persona import com.muwire.core.Persona
import com.muwire.core.UILoadedEvent import com.muwire.core.UILoadedEvent
@ -19,15 +25,16 @@ public class DownloadManager {
private final EventBus eventBus private final EventBus eventBus
private final I2PConnector connector private final I2PConnector connector
private final Executor executor private final Executor executor
private final File incompletes private final File incompletes, home
private final Persona me private final Persona me
private final Set<Downloader> downloaders = new ConcurrentHashSet<>() private final Set<Downloader> downloaders = new ConcurrentHashSet<>()
public DownloadManager(EventBus eventBus, I2PConnector connector, File incompletes, Persona me) { public DownloadManager(EventBus eventBus, I2PConnector connector, File home, Persona me) {
this.eventBus = eventBus this.eventBus = eventBus
this.connector = connector this.connector = connector
this.incompletes = incompletes this.incompletes = new File(home,"incompletes")
this.home = home
this.me = me this.me = me
incompletes.mkdir() incompletes.mkdir()
@ -66,14 +73,51 @@ public class DownloadManager {
} }
void onUILoadedEvent(UILoadedEvent e) { void onUILoadedEvent(UILoadedEvent e) {
// TODO: load downloads here File downloadsFile = new File(home, "downloads.json")
if (!downloadsFile.exists())
return
def slurper = new JsonSlurper()
downloadsFile.eachLine {
def json = slurper.parseText(it)
File file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
def destinations = new HashSet<>()
json.destinations.each { destination ->
destinations.add new Destination(destination)
}
byte[] hashList = Base64.decode(json.hashList)
InfoHash infoHash = InfoHash.fromHashList(hashList)
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
infoHash, json.pieceSizePow2, connector, destinations, incompletes)
downloader.download()
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
}
} }
void onFileDownloadedEvent(FileDownloadedEvent e) { void onFileDownloadedEvent(FileDownloadedEvent e) {
downloaders.remove(e.downloader) downloaders.remove(e.downloader)
persistDownloaders() persistDownloaders()
} }
private void persistDownloaders() { private void persistDownloaders() {
File downloadsFile = new File(home,"downloads.json")
downloadsFile.withPrintWriter { writer ->
downloaders.each { downloader ->
if (!downloader.cancelled) {
def json = [:]
json.file = Base64.encode(DataUtil.encodei18nString(downloader.file.getAbsolutePath()))
json.length = downloader.length
json.pieceSizePow2 = downloader.pieceSizePow2
def destinations = []
downloader.destinations.each {
destinations << it.toBase64()
}
json.destinations = destinations
json.hashList = Base64.encode(downloader.infoHash.hashList)
writer.println(JsonOutput.toJson(json))
}
}
}
} }
} }

View File

@ -42,6 +42,7 @@ public class Downloader {
private final Set<Destination> destinations private final Set<Destination> destinations
private final int nPieces private final int nPieces
private final File piecesFile private final File piecesFile
final int pieceSizePow2
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>() private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
@ -61,6 +62,7 @@ public class Downloader {
this.connector = connector this.connector = connector
this.destinations = destinations this.destinations = destinations
this.piecesFile = new File(incompletes, file.getName()+".pieces") this.piecesFile = new File(incompletes, file.getName()+".pieces")
this.pieceSizePow2 = pieceSizePow2
this.pieceSize = 1 << pieceSizePow2 this.pieceSize = 1 << pieceSizePow2
int nPieces int nPieces
@ -88,8 +90,8 @@ public class Downloader {
void readPieces() { void readPieces() {
if (!piecesFile.exists()) if (!piecesFile.exists())
return return
piecesFile.withReader { piecesFile.eachLine {
int piece = Integer.parseInt(it.readLine()) int piece = Integer.parseInt(it)
downloaded.markDownloaded(piece) downloaded.markDownloaded(piece)
} }
} }
@ -163,11 +165,18 @@ public class Downloader {
} }
public void resume() { public void resume() {
activeWorkers.each { destination, worker -> destinations.each { destination ->
if (worker.currentState == WorkerState.FINISHED) { def worker = activeWorkers.get(destination)
def newWorker = new DownloadWorker(destination) if (worker != null) {
activeWorkers.put(destination, newWorker) if (worker.currentState == WorkerState.FINISHED) {
executorService.submit(newWorker) def newWorker = new DownloadWorker(destination)
activeWorkers.put(destination, newWorker)
executorService.submit(newWorker)
}
} else {
worker = new DownloadWorker(destination)
activeWorkers.put(destination, worker)
executorService.submit(worker)
} }
} }
} }