Compare commits

...

102 Commits

Author SHA1 Message Date
8e3a398080 Release 0.4.1 2019-06-28 16:42:37 +01:00
720b9688b4 Add unsharing of directories 2019-06-28 16:08:04 +01:00
e3066161c5 do not perform filesystem operations in the UI thread 2019-06-27 23:29:48 +01:00
a9aa3a524f disable i2cp interface on embedded router 2019-06-27 09:56:18 +01:00
92848e818a on empty properties source from java props 2019-06-27 03:47:56 +01:00
a7aa3008c0 bandwidth settings 2019-06-27 00:42:27 +01:00
485325e824 embedded router except for logs 2019-06-26 23:25:22 +01:00
0df2a0e039 start work on embedded router 2019-06-26 22:39:25 +01:00
fb7b4466c2 update readme 2019-06-26 22:05:04 +01:00
53105245f4 Release 0.4.0 2019-06-26 21:59:28 +01:00
b68eab91e0 Release 0.3.10 2019-06-25 22:39:43 +01:00
f72cf91462 wait for files to be loaded before sharing watched directories 2019-06-25 22:24:32 +01:00
a655c4ef50 add toString 2019-06-25 22:24:15 +01:00
5d46e9b796 switch 4_ to INFO 2019-06-25 21:50:15 +01:00
642e6e67b3 wait for all files loaded before watching dirs 2019-06-25 21:43:07 +01:00
2b6b86f903 show how many pieces the remote side already has 2019-06-25 17:44:05 +01:00
f2706a4426 clarify upload column 2019-06-25 17:24:42 +01:00
1af75413aa update for brackets 2019-06-25 16:27:02 +01:00
adc4077b1a filter asterix 2019-06-25 15:54:30 +01:00
01f4e2453b limit search length to 128 characters 2019-06-25 15:53:53 +01:00
61267374dd move button around 2019-06-25 08:10:20 +01:00
970f814685 make mesh expiration configurable 2019-06-25 08:04:57 +01:00
4fd9fc1991 add option to change download location 2019-06-25 07:59:30 +01:00
26207ffd1b add constructor 2019-06-25 07:53:24 +01:00
2614cfbe5f make host clear interval configurable 2019-06-25 07:41:20 +01:00
f11d461ec0 make download sequential ratio a property 2019-06-25 07:34:26 +01:00
b2eb2d2755 show hidden files in file choosers 2019-06-24 23:09:20 +01:00
ea46a54f19 enable AA by default 2019-06-24 22:55:26 +01:00
627add45ad remove griffon icons 2019-06-24 22:51:43 +01:00
d364855459 logo 2019-06-24 22:13:03 +01:00
14ee35e77a Release 0.3.9 2019-06-24 18:39:59 +01:00
8773eb4ee0 fix piece size calculation 2019-06-24 18:29:00 +01:00
51425bbfd9 Release 0.3.8 2019-06-24 07:38:39 +01:00
6a4879bc0b always save pieces 2019-06-24 07:29:49 +01:00
e7fe56439b persist X-Have, fix flickering bug 2019-06-24 07:20:53 +01:00
2886feab4a do not modify the set of available pieces 2019-06-23 17:08:07 +01:00
fb91194026 even noisier log 2019-06-23 16:39:38 +01:00
4527478b0d even noisier 4_ 2019-06-23 12:42:44 +01:00
b0062f146e log roots of download exceptions 2019-06-23 12:10:19 +01:00
bf16561170 Release 0.3.7 2019-06-23 11:25:19 +01:00
3b23dc29c4 if all sources are expired forget mesh 2019-06-23 11:21:39 +01:00
c0645b670e no split on list 2019-06-23 10:50:19 +01:00
30613fe530 update todo 2019-06-23 09:56:51 +01:00
e7822f6edc expire sources, fix compilation 2019-06-23 09:43:56 +01:00
7e5c9ba115 actually save 2019-06-23 09:41:20 +01:00
647fa3a481 persist download mesh 2019-06-23 09:38:42 +01:00
538eca9297 Release 0.3.6 2019-06-23 08:54:28 +01:00
e73a23d4a4 fix space not showing 2019-06-23 08:44:51 +01:00
76e41a0383 fix restoring paused downloads 2019-06-23 08:42:45 +01:00
7045927666 hide monitor options from gui 2019-06-23 08:02:28 +01:00
5fb3086b42 update faq 2019-06-23 07:52:01 +01:00
2de18227c1 persist pause state 2019-06-23 07:48:49 +01:00
bd12a1de3d pause/resume downloads 2019-06-23 06:59:52 +01:00
a3a91050c8 update todo 2019-06-23 01:50:30 +01:00
6c1cc28e49 shutdown if connection to I2P router is lost 2019-06-22 17:32:12 +01:00
b6e5b54f05 do not show monitor by default 2019-06-22 14:51:26 +01:00
a6e559ec67 change some defaults 2019-06-22 06:54:49 +01:00
f11badb824 update todo 2019-06-21 22:43:46 +01:00
44da44ff6f Release 0.3.5 2019-06-21 22:35:54 +01:00
aae3fc29ca add logging.properties with various degree of noisiness 2019-06-21 22:28:57 +01:00
c30aa19d8b Merge branch 'download-mesh' 2019-06-21 22:26:17 +01:00
c79e8712d0 correctly determine if uploader has requested piece 2019-06-21 20:36:33 +01:00
ed12d78a48 clear pieces on cancel 2019-06-21 17:22:55 +01:00
d27872cc8b investigate StringIndexOutOfBounds 2019-06-21 16:29:52 +01:00
f794c39760 personas not destinations 2019-06-21 16:15:35 +01:00
2be9c425f7 compute which pieces are requested 2019-06-21 16:09:57 +01:00
ab5fea9216 416 if piece not downloaded 2019-06-21 16:03:20 +01:00
d1c8328080 do not send alts if there aren't any 2019-06-21 15:39:00 +01:00
89e761f53b write personas on the wire part1 2019-06-21 15:26:18 +01:00
40410eba63 fix constructor 2019-06-21 14:57:53 +01:00
85466a8e80 fix npe 2019-06-21 14:45:14 +01:00
c210af7870 source partial uploads from incompletes file 2019-06-21 14:39:20 +01:00
38ff49d28f downloaders get pieces from mesh manager 2019-06-21 14:17:10 +01:00
710f9f52a8 send X-Have and X-Alts from uploader 2019-06-21 13:58:21 +01:00
1b6eda5a40 skeleton of mesh manager 2019-06-21 13:34:00 +01:00
1ee9ccf098 parse X-Have on uploader side 2019-06-21 12:55:25 +01:00
0f07562de3 pass new sources to active downloaders 2019-06-21 12:39:16 +01:00
6eb1aa07f5 key downloaders by infohash 2019-06-21 12:29:32 +01:00
05b02834af parse X-Alt 2019-06-21 12:25:04 +01:00
56125f6df8 refactor X-Have decoding logic 2019-06-21 09:32:10 +01:00
8f9996848b send X-Have from downloader too 2019-06-21 09:25:28 +01:00
dd655ed60f test for re-requesting available pieces 2019-06-21 09:12:42 +01:00
8923c6ff7d exclude local results by default 2019-06-21 08:15:20 +01:00
807ab22f8e test parsing of X-Have 2019-06-21 06:43:48 +01:00
a26ad229ee more tests 2019-06-21 05:56:42 +01:00
5504dd2251 tighten conditions 2019-06-21 05:45:11 +01:00
f9777d29f4 get existing tests to pass 2019-06-21 05:41:49 +01:00
b23226e8c6 wip on parsing X-Have from uploader 2019-06-21 05:30:56 +01:00
1249ad29e0 claim pieces from list of available pieces 2019-06-21 04:42:02 +01:00
7bb5e5b632 Release 0.3.4 2019-06-20 21:07:50 +01:00
b2e43f9765 update split pattern and add unit test 2019-06-20 21:06:39 +01:00
2aa73c203a Release 0.3.3 2019-06-20 18:08:02 +01:00
18d2b56563 fix indexing 2019-06-20 17:57:36 +01:00
a455b4ad6e redirect exceptions in result sender to log 2019-06-20 17:22:59 +01:00
761b683a81 Release 0.3.2 2019-06-20 16:04:46 +01:00
1d41bcd825 prevent empty tokens in search index 2019-06-20 16:02:48 +01:00
f1ac038b55 update split pattern 2019-06-20 15:47:00 +01:00
396c636e42 prevent empty search terms 2019-06-20 15:29:27 +01:00
e32c858e90 update README with quick FAQ 2019-06-20 14:18:37 +01:00
821555f3f1 Release 0.3.1 2019-06-20 14:02:22 +01:00
089ab4f0d9 do not retry downloads if core is shut(ting) down 2019-06-20 13:40:04 +01:00
948b6292fe add shutdown hook to shutdown core on SIGTERM 2019-06-20 13:29:15 +01:00
66 changed files with 1434 additions and 179 deletions

View File

@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
The current stable release - 0.2.5 is avaiable for download at http://muwire.com. You can find technical documentation in the "doc" folder.
The current stable release - 0.4.0 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
### Building
@ -31,3 +31,26 @@ The first time you run MuWire it will ask you to select a nickname. This nickna
### Known bugs and limitations
* Many UI features you would expect are not there yet
### Quick FAQ
* why is MuWire slow ?
- too few sources you're downloading from
- you can increase the number of tunnels by using more tunnels via Options->I2P Inbound/Outbound Quantity
the default is 4 and you could raise up to as high as 16 ( Caution !!!!)
* my search is not returning (enough) results !
- search is keyword or hash based
- keywords and hash(es) are NOT regexed or wildcarded so they have to be complete
so searching for 'musi' will not return results with 'music' - you have to search for 'music'
- ALL keywords have to match
- only use space for keyword separation
- if you already have the file in question it is not displayed ( can be changed via Options )
* what's this right click -> 'Copy hash to clipboard' for ?
- if you have a specific file you wish to share or download you can use the hash as a unique identifier
to make sure you have exactly the right file.
- you can share this hash with others to ensure they are getting the right file

View File

@ -4,10 +4,6 @@ Not in any particular order yet
### Big Items
##### Alternate Locations
This helps peers discover new sources for a file while the download is in progress. Also makes sharing of partial files possible.
##### Bloom Filters
This reduces query traffic by not sending last hop queries to peers that definitely do not have the file
@ -38,7 +34,6 @@ To enable parsing of metadata from known file types and the user editing it or a
### Small Items
* Detect if router is dead and show warning or exit
* Wrapper of some kind for in-place upgrades
* Download file sequentially
* Unsharing of files

View File

@ -35,7 +35,7 @@ class Cli {
Core core
try {
core = new Core(props, home, "0.3.0")
core = new Core(props, home, "0.4.1")
} catch (Exception bad) {
bad.printStackTrace(System.out)
println "Failed to initialize core, exiting"

View File

@ -53,7 +53,7 @@ class CliDownloader {
Core core
try {
core = new Core(props, home, "0.3.0")
core = new Core(props, home, "0.4.1")
} catch (Exception bad) {
bad.printStackTrace(System.out)
println "Failed to initialize core, exiting"

View File

@ -2,6 +2,7 @@ apply plugin : 'application'
mainClassName = 'com.muwire.core.Core'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
dependencies {
compile 'net.i2p:router:0.9.40'
compile 'net.i2p.client:mstreaming:0.9.40'
compile 'net.i2p.client:streaming:0.9.40'

View File

@ -9,7 +9,5 @@ class Constants {
public static final int MAX_HEADER_SIZE = 0x1 << 14
public static final int MAX_HEADERS = 16
public static final float DOWNLOAD_SEQUENTIAL_RATIO = 0.8f
public static final String SPLIT_PATTERN = "[\\.,_-]"
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}]"
}

View File

@ -1,6 +1,7 @@
package com.muwire.core
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicBoolean
import com.muwire.core.connection.ConnectionAcceptor
import com.muwire.core.connection.ConnectionEstablisher
@ -12,8 +13,11 @@ import com.muwire.core.connection.I2PConnector
import com.muwire.core.connection.LeafConnectionManager
import com.muwire.core.connection.UltrapeerConnectionManager
import com.muwire.core.download.DownloadManager
import com.muwire.core.download.SourceDiscoveredEvent
import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHashedEvent
import com.muwire.core.files.FileHasher
@ -23,10 +27,13 @@ import com.muwire.core.files.FileSharedEvent
import com.muwire.core.files.FileUnsharedEvent
import com.muwire.core.files.HasherService
import com.muwire.core.files.PersisterService
import com.muwire.core.files.AllFilesLoadedEvent
import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.files.DirectoryWatcher
import com.muwire.core.hostcache.CacheClient
import com.muwire.core.hostcache.HostCache
import com.muwire.core.hostcache.HostDiscoveredEvent
import com.muwire.core.mesh.MeshManager
import com.muwire.core.search.QueryEvent
import com.muwire.core.search.ResultsEvent
import com.muwire.core.search.ResultsSender
@ -45,6 +52,7 @@ import net.i2p.client.I2PSession
import net.i2p.client.streaming.I2PSocketManager
import net.i2p.client.streaming.I2PSocketManagerFactory
import net.i2p.client.streaming.I2PSocketOptions
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener
import net.i2p.crypto.DSAEngine
import net.i2p.crypto.SigType
import net.i2p.data.Destination
@ -52,6 +60,9 @@ import net.i2p.data.PrivateKey
import net.i2p.data.Signature
import net.i2p.data.SigningPrivateKey
import net.i2p.router.Router
import net.i2p.router.RouterContext
@Log
public class Core {
@ -73,13 +84,33 @@ public class Core {
private final DownloadManager downloadManager
private final DirectoryWatcher directoryWatcher
final FileManager fileManager
private final Router router
final AtomicBoolean shutdown = new AtomicBoolean()
public Core(MuWireSettings props, File home, String myVersion) {
this.home = home
this.muOptions = props
log.info "Initializing I2P context"
I2PAppContext.getGlobalContext().logManager()
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
if (!props.embeddedRouter) {
log.info "Initializing I2P context"
I2PAppContext.getGlobalContext().logManager()
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
router = null
} else {
log.info("launching embedded router")
Properties routerProps = new Properties()
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
routerProps.setProperty("i2np.outboundKBytesPerSecond", String.valueOf(props.outBw))
routerProps.setProperty("i2cp.disableInterface", "true")
router = new Router(routerProps)
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass()
router.runRouter()
while(!router.isRunning())
Thread.sleep(100)
}
log.info("initializing I2P socket manager")
def i2pClient = new I2PClientFactory().createClient()
@ -119,6 +150,7 @@ public class Core {
}
socketManager.getDefaultOptions().setReadTimeout(60000)
socketManager.getDefaultOptions().setConnectTimeout(30000)
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
i2pSession = socketManager.getSession()
def destination = new Destination()
@ -162,6 +194,11 @@ public class Core {
eventBus.register(FileDownloadedEvent.class, fileManager)
eventBus.register(FileUnsharedEvent.class, fileManager)
eventBus.register(SearchEvent.class, fileManager)
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
log.info("initializing mesh manager")
MeshManager meshManager = new MeshManager(fileManager, home, props)
eventBus.register(SourceDiscoveredEvent.class, meshManager)
log.info "initializing persistence service"
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
@ -200,14 +237,17 @@ public class Core {
eventBus.register(ResultsEvent.class, searchManager)
log.info("initializing download manager")
downloadManager = new DownloadManager(eventBus, i2pConnector, home, me)
downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me)
eventBus.register(UIDownloadEvent.class, downloadManager)
eventBus.register(UILoadedEvent.class, downloadManager)
eventBus.register(FileDownloadedEvent.class, downloadManager)
eventBus.register(UIDownloadCancelledEvent.class, downloadManager)
eventBus.register(SourceDiscoveredEvent.class, downloadManager)
eventBus.register(UIDownloadPausedEvent.class, downloadManager)
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
log.info("initializing upload manager")
UploadManager uploadManager = new UploadManager(eventBus, fileManager)
UploadManager uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
log.info("initializing connection establisher")
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
@ -220,6 +260,8 @@ public class Core {
log.info("initializing directory watcher")
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
eventBus.register(FileSharedEvent.class, directoryWatcher)
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
log.info("initializing hasher service")
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
@ -228,7 +270,6 @@ public class Core {
public void startServices() {
hasherService.start()
directoryWatcher.start()
trustService.start()
trustService.waitForLoad()
hostCache.start()
@ -241,6 +282,10 @@ public class Core {
}
public void shutdown() {
if (!shutdown.compareAndSet(false, true)) {
log.info("already shutting down")
return
}
log.info("shutting down download manageer")
downloadManager.shutdown()
log.info("shutting down connection acceeptor")
@ -251,8 +296,25 @@ public class Core {
directoryWatcher.stop()
log.info("shutting down connection manager")
connectionManager.shutdown()
if (router != null) {
log.info("shutting down embedded router")
router.shutdown(0)
}
}
static class RouterContextMetaClass extends DelegatingMetaClass {
private final Object logManager = new MuWireLogManager()
RouterContextMetaClass() {
super(RouterContext.class)
}
Object invokeMethod(Object object, String name, Object[] args) {
if (name == "logManager")
return logManager
super.invokeMethod(object, name, args)
}
}
static main(args) {
def home = System.getProperty("user.home") + File.separator + ".MuWire"
home = new File(home)
@ -277,7 +339,7 @@ public class Core {
}
}
Core core = new Core(props, home, "0.3.0")
Core core = new Core(props, home, "0.4.1")
core.startServices()
// ... at the end, sleep or execute script

View File

@ -18,6 +18,11 @@ class MuWireSettings {
CrawlerResponse crawlerResponse
boolean shareDownloadedFiles
Set<String> watchedDirectories
float downloadSequentialRatio
int hostClearInterval
int meshExpiration
boolean embeddedRouter
int inBw, outBw
MuWireSettings() {
this(new Properties())
@ -30,9 +35,15 @@ class MuWireSettings {
nickname = props.getProperty("nickname","MuWireUser")
downloadLocation = new File((String)props.getProperty("downloadLocation",
System.getProperty("user.home")))
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","5"))
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","36"))
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","60"))
meshExpiration = Integer.valueOf(props.getProperty("meshExpiration","60"))
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
inBw = Integer.valueOf(props.getProperty("inBw","256"))
outBw = Integer.valueOf(props.getProperty("outBw","128"))
watchedDirectories = new HashSet<>()
if (props.containsKey("watchedDirectories")) {
@ -52,6 +63,12 @@ class MuWireSettings {
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
props.setProperty("inBw", String.valueOf(inBw))
props.setProperty("outBw", String.valueOf(outBw))
if (!watchedDirectories.isEmpty()) {
String encoded = watchedDirectories.stream().

View File

@ -0,0 +1,4 @@
package com.muwire.core
class RouterDisconnectedEvent extends Event {
}

View File

@ -3,6 +3,10 @@ package com.muwire.core.download
import com.muwire.core.connection.I2PConnector
import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHasher
import com.muwire.core.mesh.Mesh
import com.muwire.core.mesh.MeshManager
import com.muwire.core.trust.TrustLevel
import com.muwire.core.trust.TrustService
import com.muwire.core.util.DataUtil
import groovy.json.JsonBuilder
@ -14,24 +18,33 @@ import net.i2p.util.ConcurrentHashSet
import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.MuWireSettings
import com.muwire.core.Persona
import com.muwire.core.UILoadedEvent
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
import java.util.concurrent.Executors
public class DownloadManager {
private final EventBus eventBus
private final TrustService trustService
private final MeshManager meshManager
private final MuWireSettings muSettings
private final I2PConnector connector
private final Executor executor
private final File incompletes, home
private final Persona me
private final Set<Downloader> downloaders = new ConcurrentHashSet<>()
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
public DownloadManager(EventBus eventBus, I2PConnector connector, File home, Persona me) {
public DownloadManager(EventBus eventBus, TrustService trustService, MeshManager meshManager, MuWireSettings muSettings,
I2PConnector connector, File home, Persona me) {
this.eventBus = eventBus
this.trustService = trustService
this.meshManager = meshManager
this.muSettings = muSettings
this.connector = connector
this.incompletes = new File(home,"incompletes")
this.home = home
@ -61,17 +74,27 @@ public class DownloadManager {
destinations.addAll(e.sources)
destinations.remove(me.destination)
Pieces pieces = getPieces(infohash, size, pieceSize)
def downloader = new Downloader(eventBus, this, me, e.target, size,
infohash, pieceSize, connector, destinations,
incompletes)
downloaders.add(downloader)
incompletes, pieces)
downloaders.put(infohash, downloader)
persistDownloaders()
executor.execute({downloader.download()} as Runnable)
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
}
public void onUIDownloadCancelledEvent(UIDownloadCancelledEvent e) {
downloaders.remove(e.downloader)
downloaders.remove(e.downloader.infoHash)
persistDownloaders()
}
public void onUIDownloadPausedEvent(UIDownloadPausedEvent e) {
persistDownloaders()
}
public void onUIDownloadResumedEvent(UIDownloadResumedEvent e) {
persistDownloaders()
}
@ -99,23 +122,54 @@ public class DownloadManager {
byte [] root = Base64.decode(json.hashRoot)
infoHash = new InfoHash(root)
}
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2)
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
infoHash, json.pieceSizePow2, connector, destinations, incompletes)
downloaders.add(downloader)
downloader.download()
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
if (json.paused != null)
downloader.paused = json.paused
downloaders.put(infoHash, downloader)
downloader.readPieces()
if (!downloader.paused)
downloader.download()
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
}
}
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2) {
int pieceSize = 0x1 << pieceSizePow2
int nPieces = (int)(length / pieceSize)
if (length % pieceSize != 0)
nPieces++
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces)
mesh.pieces
}
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
Downloader downloader = downloaders.get(e.infoHash)
if (downloader == null)
return
boolean ok = false
switch(trustService.getLevel(e.source.destination)) {
case TrustLevel.TRUSTED: ok = true; break
case TrustLevel.NEUTRAL: ok = muSettings.allowUntrusted; break
case TrustLevel.DISTRUSTED: ok = false; break
}
if (ok)
downloader.addSource(e.source.destination)
}
void onFileDownloadedEvent(FileDownloadedEvent e) {
downloaders.remove(e.downloader)
downloaders.remove(e.downloader.infoHash)
persistDownloaders()
}
private void persistDownloaders() {
File downloadsFile = new File(home,"downloads.json")
downloadsFile.withPrintWriter { writer ->
downloaders.each { downloader ->
downloaders.values().each { downloader ->
if (!downloader.cancelled) {
def json = [:]
json.file = Base64.encode(DataUtil.encodei18nString(downloader.file.getAbsolutePath()))
@ -132,6 +186,8 @@ public class DownloadManager {
json.hashList = Base64.encode(infoHash.hashList)
else
json.hashRoot = Base64.encode(infoHash.getRoot())
json.paused = downloader.paused
writer.println(JsonOutput.toJson(json))
}
}
@ -139,7 +195,7 @@ public class DownloadManager {
}
public void shutdown() {
downloaders.each { it.stop() }
downloaders.values().each { it.stop() }
Downloader.executorService.shutdownNow()
}
}

View File

@ -3,8 +3,12 @@ package com.muwire.core.download;
import net.i2p.data.Base64
import com.muwire.core.Constants
import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.Persona
import com.muwire.core.connection.Endpoint
import com.muwire.core.util.DataUtil
import static com.muwire.core.util.DataUtil.readTillRN
import groovy.util.logging.Log
@ -23,6 +27,7 @@ class DownloadSession {
private static int SAMPLES = 10
private final EventBus eventBus
private final String meB64
private final Pieces pieces
private final InfoHash infoHash
@ -30,6 +35,7 @@ class DownloadSession {
private final File file
private final int pieceSize
private final long fileLength
private final Set<Integer> available
private final MessageDigest digest
private final LinkedList<Long> timestamps = new LinkedList<>()
@ -37,8 +43,9 @@ class DownloadSession {
private ByteBuffer mapped
DownloadSession(String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
int pieceSize, long fileLength) {
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
int pieceSize, long fileLength, Set<Integer> available) {
this.eventBus = eventBus
this.meB64 = meB64
this.pieces = pieces
this.endpoint = endpoint
@ -46,6 +53,7 @@ class DownloadSession {
this.file = file
this.pieceSize = pieceSize
this.fileLength = fileLength
this.available = available
try {
digest = MessageDigest.getInstance("SHA-256")
} catch (NoSuchAlgorithmException impossible) {
@ -63,7 +71,11 @@ class DownloadSession {
OutputStream os = endpoint.getOutputStream()
InputStream is = endpoint.getInputStream()
int piece = pieces.claim()
int piece
if (available.isEmpty())
piece = pieces.claim()
else
piece = pieces.claim(new HashSet<>(available))
if (piece == -1)
return false
boolean unclaim = true
@ -79,45 +91,79 @@ class DownloadSession {
try {
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("X-Persona: $meB64\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("X-Persona: $meB64\r\n".getBytes(StandardCharsets.US_ASCII))
String xHave = DataUtil.encodeXHave(pieces.getDownloaded(), pieces.nPieces)
os.write("X-Have: $xHave\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
os.flush()
String code = readTillRN(is)
if (code.startsWith("404 ")) {
String codeString = readTillRN(is)
int space = codeString.indexOf(' ')
if (space > 0)
codeString = codeString.substring(0, space)
int code = Integer.parseInt(codeString.trim())
if (code == 404) {
log.warning("file not found")
endpoint.close()
return false
}
if (code.startsWith("416 ")) {
log.warning("range $start-$end cannot be satisfied")
return // leave endpoint open
}
if (!code.startsWith("200 ")) {
if (!(code == 200 || code == 416)) {
log.warning("unknown code $code")
endpoint.close()
return false
}
// parse all headers
Set<String> headers = new HashSet<>()
Map<String,String> headers = new HashMap<>()
String header
while((header = readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS)
headers.add(header)
long receivedStart = -1
long receivedEnd = -1
for (String receivedHeader : headers) {
def group = (receivedHeader =~ /^Content-Range: (\d+)-(\d+)$/)
if (group.size() != 1) {
log.info("ignoring header $receivedHeader")
continue
}
receivedStart = Long.parseLong(group[0][1])
receivedEnd = Long.parseLong(group[0][2])
while((header = readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS) {
int colon = header.indexOf(':')
if (colon == -1 || colon == header.length() - 1)
throw new IOException("invalid header $header")
String key = header.substring(0, colon)
String value = header.substring(colon + 1)
headers[key] = value.trim()
}
// prase X-Alt if present
if (headers.containsKey("X-Alt")) {
headers["X-Alt"].split(",").each {
if (it.length() > 0) {
byte [] raw = Base64.decode(it)
Persona source = new Persona(new ByteArrayInputStream(raw))
eventBus.publish(new SourceDiscoveredEvent(infoHash : infoHash, source : source))
}
}
}
// parse X-Have if present
if (headers.containsKey("X-Have")) {
DataUtil.decodeXHave(headers["X-Have"]).each {
available.add(it)
}
if (!available.contains(piece))
return true // try again next time
} else {
if (code != 200)
throw new IOException("Code $code but no X-Have")
available.clear()
}
if (code != 200)
return true
String range = headers["Content-Range"]
if (range == null)
throw new IOException("Code 200 but no Content-Range")
def group = (range =~ /^(\d+)-(\d+)$/)
if (group.size() != 1)
throw new IOException("invalid Content-Range header $range")
long receivedStart = Long.parseLong(group[0][1])
long receivedEnd = Long.parseLong(group[0][2])
if (receivedStart != start || receivedEnd != end) {
log.warning("We don't support mismatching ranges yet")
endpoint.close()

View File

@ -18,6 +18,7 @@ import com.muwire.core.DownloadedFile
import com.muwire.core.EventBus
import com.muwire.core.connection.I2PConnector
import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.util.DataUtil
import groovy.util.logging.Log
import net.i2p.data.Destination
@ -25,7 +26,7 @@ import net.i2p.util.ConcurrentHashSet
@Log
public class Downloader {
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, FINISHED }
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
private static final ExecutorService executorService = Executors.newCachedThreadPool({r ->
@ -53,14 +54,14 @@ public class Downloader {
private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>()
private volatile boolean cancelled
private volatile boolean cancelled, paused
private final AtomicBoolean eventFired = new AtomicBoolean()
private boolean piecesFileClosed
public Downloader(EventBus eventBus, DownloadManager downloadManager,
Persona me, File file, long length, InfoHash infoHash,
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
File incompletes) {
File incompletes, Pieces pieces) {
this.eventBus = eventBus
this.me = me
this.downloadManager = downloadManager
@ -73,15 +74,8 @@ public class Downloader {
this.incompleteFile = new File(incompletes, file.getName()+".part")
this.pieceSizePow2 = pieceSizePow2
this.pieceSize = 1 << pieceSizePow2
int nPieces
if (length % pieceSize == 0)
nPieces = length / pieceSize
else
nPieces = length / pieceSize + 1
this.nPieces = nPieces
pieces = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
this.pieces = pieces
this.nPieces = pieces.nPieces
}
public synchronized InfoHash getInfoHash() {
@ -143,6 +137,9 @@ public class Downloader {
public DownloadState getCurrentState() {
if (cancelled)
return DownloadState.CANCELLED
if (paused)
return DownloadState.PAUSED
boolean allFinished = true
activeWorkers.values().each {
allFinished &= it.currentState == WorkerState.FINISHED
@ -187,6 +184,12 @@ public class Downloader {
piecesFile.delete()
}
incompleteFile.delete()
pieces.clearAll()
}
public void pause() {
paused = true
stop()
}
void stop() {
@ -205,6 +208,8 @@ public class Downloader {
}
public void resume() {
paused = false
readPieces()
destinations.each { destination ->
def worker = activeWorkers.get(destination)
if (worker != null) {
@ -221,12 +226,21 @@ public class Downloader {
}
}
void addSource(Destination d) {
if (activeWorkers.containsKey(d))
return
DownloadWorker newWorker = new DownloadWorker(d)
activeWorkers.put(d, newWorker)
executorService.submit(newWorker)
}
class DownloadWorker implements Runnable {
private final Destination destination
private volatile WorkerState currentState
private volatile Thread downloadThread
private Endpoint endpoint
private volatile DownloadSession currentSession
private final Set<Integer> available = new HashSet<>()
DownloadWorker(Destination destination) {
this.destination = destination
@ -247,7 +261,8 @@ public class Downloader {
currentState = WorkerState.DOWNLOADING
boolean requestPerformed
while(!pieces.isComplete()) {
currentSession = new DownloadSession(me.toBase64(), pieces, getInfoHash(), endpoint, incompleteFile, pieceSize, length)
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
endpoint, incompleteFile, pieceSize, length, available)
requestPerformed = currentSession.request()
if (!requestPerformed)
break
@ -255,7 +270,7 @@ public class Downloader {
writePieces()
}
} catch (Exception bad) {
log.log(Level.WARNING,"Exception while downloading",bad)
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
} finally {
currentState = WorkerState.FINISHED
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {

View File

@ -38,6 +38,18 @@ class Pieces {
}
}
synchronized int claim(Set<Integer> available) {
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
available.remove(i)
if (available.isEmpty())
return -1
List<Integer> toList = available.toList()
Collections.shuffle(toList)
int rv = toList[0]
claimed.set(rv)
rv
}
synchronized def getDownloaded() {
def rv = []
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
@ -62,4 +74,13 @@ class Pieces {
synchronized int donePieces() {
done.cardinality()
}
synchronized boolean isDownloaded(int piece) {
done.get(piece)
}
synchronized void clearAll() {
done.clear()
claimed.clear()
}
}

View File

@ -0,0 +1,10 @@
package com.muwire.core.download
import com.muwire.core.Event
import com.muwire.core.InfoHash
import com.muwire.core.Persona
class SourceDiscoveredEvent extends Event {
InfoHash infoHash
Persona source
}

View File

@ -0,0 +1,6 @@
package com.muwire.core.download
import com.muwire.core.Event
class UIDownloadPausedEvent extends Event {
}

View File

@ -0,0 +1,6 @@
package com.muwire.core.download
import com.muwire.core.Event
class UIDownloadResumedEvent extends Event {
}

View File

@ -0,0 +1,7 @@
package com.muwire.core.files
import com.muwire.core.Event
class DirectoryUnsharedEvent extends Event {
File directory
}

View File

@ -35,6 +35,7 @@ class DirectoryWatcher {
private final FileManager fileManager
private final Thread watcherThread, publisherThread
private final Map<File, Long> waitingFiles = new ConcurrentHashMap<>()
private final Map<File, WatchKey> watchedDirectories = new ConcurrentHashMap<>()
private WatchService watchService
private volatile boolean shutdown
@ -47,7 +48,7 @@ class DirectoryWatcher {
publisherThread.setDaemon(true)
}
void start() {
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
watchService = FileSystems.getDefault().newWatchService()
watcherThread.start()
publisherThread.start()
@ -55,18 +56,24 @@ class DirectoryWatcher {
void stop() {
shutdown = true
watcherThread.interrupt()
publisherThread.interrupt()
watchService.close()
watcherThread?.interrupt()
publisherThread?.interrupt()
watchService?.close()
}
void onFileSharedEvent(FileSharedEvent e) {
if (!e.file.isDirectory())
return
Path path = e.file.getCanonicalFile().toPath()
path.register(watchService, kinds)
WatchKey wk = path.register(watchService, kinds)
watchedDirectories.put(e.file, wk)
}
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
WatchKey wk = watchedDirectories.remove(e.directory)
wk?.cancel()
}
private void watch() {
try {

View File

@ -7,4 +7,10 @@ class FileHashedEvent extends Event {
SharedFile sharedFile
String error
@Override
public String toString() {
super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error"
}
}

View File

@ -135,4 +135,16 @@ class FileManager {
}
rv
}
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
e.directory.listFiles().each {
if (it.isDirectory())
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
else {
SharedFile sf = fileToSharedFile.get(it)
if (sf != null)
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
}
}
}
}

View File

@ -5,4 +5,9 @@ import com.muwire.core.Event
class FileSharedEvent extends Event {
File file
@Override
public String toString() {
return super.toString() + " file: "+file.getAbsolutePath()
}
}

View File

@ -5,14 +5,15 @@ import net.i2p.data.Destination
class Host {
private static final int MAX_FAILURES = 3
private static final int CLEAR_INTERVAL = 60 * 60 * 1000
final Destination destination
private final int clearInterval
int failures,successes
long lastAttempt
public Host(Destination destination) {
public Host(Destination destination, int clearInterval) {
this.destination = destination
this.clearInterval = clearInterval
}
synchronized void onConnect() {
@ -40,6 +41,6 @@ class Host {
}
synchronized void canTryAgain() {
System.currentTimeMillis() - lastAttempt > CLEAR_INTERVAL
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
}
}

View File

@ -52,7 +52,7 @@ class HostCache extends Service {
hosts.get(e.destination).clearFailures()
return
}
Host host = new Host(e.destination)
Host host = new Host(e.destination, settings.hostClearInterval)
if (allowHost(host)) {
hosts.put(e.destination, host)
}
@ -64,7 +64,7 @@ class HostCache extends Service {
Destination dest = e.endpoint.destination
Host host = hosts.get(dest)
if (host == null) {
host = new Host(dest)
host = new Host(dest, settings.hostClearInterval)
hosts.put(dest, host)
}
@ -106,7 +106,7 @@ class HostCache extends Service {
storage.eachLine {
def entry = slurper.parseText(it)
Destination dest = new Destination(entry.destination)
Host host = new Host(dest)
Host host = new Host(dest, settings.hostClearInterval)
host.failures = Integer.valueOf(String.valueOf(entry.failures))
host.successes = Integer.valueOf(String.valueOf(entry.successes))
if (entry.lastAttempt != null)

View File

@ -0,0 +1,28 @@
package com.muwire.core.mesh
import com.muwire.core.InfoHash
import com.muwire.core.Persona
import com.muwire.core.download.Pieces
import net.i2p.data.Destination
import net.i2p.util.ConcurrentHashSet
class Mesh {
private final InfoHash infoHash
private final Set<Persona> sources = new ConcurrentHashSet<>()
private final Pieces pieces
Mesh(InfoHash infoHash, Pieces pieces) {
this.infoHash = infoHash
this.pieces = pieces
}
Set<Persona> getRandom(int n, Persona exclude) {
List<Persona> tmp = new ArrayList<>(sources)
tmp.remove(exclude)
Collections.shuffle(tmp)
if (tmp.size() < n)
return tmp
tmp[0..n-1]
}
}

View File

@ -0,0 +1,102 @@
package com.muwire.core.mesh
import java.util.stream.Collectors
import com.muwire.core.Constants
import com.muwire.core.InfoHash
import com.muwire.core.MuWireSettings
import com.muwire.core.Persona
import com.muwire.core.download.Pieces
import com.muwire.core.download.SourceDiscoveredEvent
import com.muwire.core.files.FileManager
import com.muwire.core.util.DataUtil
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import net.i2p.data.Base64
class MeshManager {
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
private final FileManager fileManager
private final File home
private final MuWireSettings settings
MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
this.fileManager = fileManager
this.home = home
this.settings = settings
load()
}
Mesh get(InfoHash infoHash) {
meshes.get(infoHash)
}
Mesh getOrCreate(InfoHash infoHash, int nPieces) {
synchronized(meshes) {
if (meshes.containsKey(infoHash))
return meshes.get(infoHash)
Pieces pieces = new Pieces(nPieces, settings.downloadSequentialRatio)
if (fileManager.rootToFiles.containsKey(infoHash)) {
for (int i = 0; i < nPieces; i++)
pieces.markDownloaded(i)
}
Mesh rv = new Mesh(infoHash, pieces)
meshes.put(infoHash, rv)
return rv
}
}
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
Mesh mesh = meshes.get(e.infoHash)
if (mesh == null)
return
mesh.sources.add(e.source)
save()
}
private void save() {
File meshFile = new File(home, "mesh.json")
synchronized(meshes) {
meshFile.withPrintWriter { writer ->
meshes.values().each { mesh ->
def json = [:]
json.timestamp = System.currentTimeMillis()
json.infoHash = Base64.encode(mesh.infoHash.getRoot())
json.sources = mesh.sources.stream().map({it.toBase64()}).collect(Collectors.toList())
json.nPieces = mesh.pieces.nPieces
json.xHave = DataUtil.encodeXHave(mesh.pieces.downloaded, mesh.pieces.nPieces)
writer.println(JsonOutput.toJson(json))
}
}
}
}
private void load() {
File meshFile = new File(home, "mesh.json")
if (!meshFile.exists())
return
long now = System.currentTimeMillis()
JsonSlurper slurper = new JsonSlurper()
meshFile.eachLine {
def json = slurper.parseText(it)
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
return
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
Mesh mesh = new Mesh(infoHash, pieces)
json.sources.each { source ->
Persona persona = new Persona(new ByteArrayInputStream(Base64.decode(source)))
mesh.sources.add(persona)
}
if (json.xHave != null)
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
if (!mesh.sources.isEmpty())
meshes.put(infoHash, mesh)
}
}
}

View File

@ -11,6 +11,7 @@ import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Level
import java.util.stream.Collectors
import com.muwire.core.DownloadedFile
@ -83,50 +84,54 @@ class ResultsSender {
@Override
public void run() {
byte [] tmp = new byte[InfoHash.SIZE]
JsonOutput jsonOutput = new JsonOutput()
Endpoint endpoint = null;
try {
endpoint = connector.connect(target)
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
os.write("POST $uuid\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
me.write(os)
os.writeShort((short)results.length)
results.each {
byte [] name = it.getFile().getName().getBytes(StandardCharsets.UTF_8)
def baos = new ByteArrayOutputStream()
def daos = new DataOutputStream(baos)
daos.writeShort((short) name.length)
daos.write(name)
daos.flush()
String encodedName = Base64.encode(baos.toByteArray())
def obj = [:]
obj.type = "Result"
obj.version = oobInfohash ? 2 : 1
obj.name = encodedName
obj.infohash = Base64.encode(it.getInfoHash().getRoot())
obj.size = it.getFile().length()
obj.pieceSize = it.getPieceSize()
if (!oobInfohash) {
byte [] hashList = it.getInfoHash().getHashList()
def hashListB64 = []
for (int i = 0; i < hashList.length / InfoHash.SIZE; i++) {
System.arraycopy(hashList, InfoHash.SIZE * i, tmp, 0, InfoHash.SIZE)
hashListB64 << Base64.encode(tmp)
byte [] tmp = new byte[InfoHash.SIZE]
JsonOutput jsonOutput = new JsonOutput()
Endpoint endpoint = null;
try {
endpoint = connector.connect(target)
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
os.write("POST $uuid\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
me.write(os)
os.writeShort((short)results.length)
results.each {
byte [] name = it.getFile().getName().getBytes(StandardCharsets.UTF_8)
def baos = new ByteArrayOutputStream()
def daos = new DataOutputStream(baos)
daos.writeShort((short) name.length)
daos.write(name)
daos.flush()
String encodedName = Base64.encode(baos.toByteArray())
def obj = [:]
obj.type = "Result"
obj.version = oobInfohash ? 2 : 1
obj.name = encodedName
obj.infohash = Base64.encode(it.getInfoHash().getRoot())
obj.size = it.getFile().length()
obj.pieceSize = it.getPieceSize()
if (!oobInfohash) {
byte [] hashList = it.getInfoHash().getHashList()
def hashListB64 = []
for (int i = 0; i < hashList.length / InfoHash.SIZE; i++) {
System.arraycopy(hashList, InfoHash.SIZE * i, tmp, 0, InfoHash.SIZE)
hashListB64 << Base64.encode(tmp)
}
obj.hashList = hashListB64
}
obj.hashList = hashListB64
if (it instanceof DownloadedFile)
obj.sources = it.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
def json = jsonOutput.toJson(obj)
os.writeShort((short)json.length())
os.write(json.getBytes(StandardCharsets.US_ASCII))
}
if (it instanceof DownloadedFile)
obj.sources = it.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
def json = jsonOutput.toJson(obj)
os.writeShort((short)json.length())
os.write(json.getBytes(StandardCharsets.US_ASCII))
os.flush()
} finally {
endpoint?.close()
}
os.flush()
} finally {
endpoint?.close()
} catch (Exception e) {
log.log(Level.WARNING, "problem sending results",e)
}
}
}

View File

@ -33,7 +33,10 @@ class SearchIndex {
private static String[] split(String source) {
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
source.split(" ")
String [] split = source.split(" ")
def rv = []
split.each { if (it.length() > 0) rv << it }
rv.toArray(new String[0])
}
String[] search(List<String> terms) {

View File

@ -2,4 +2,5 @@ package com.muwire.core.upload
class ContentRequest extends Request {
Range range
int have
}

View File

@ -5,33 +5,57 @@ import java.nio.channels.FileChannel
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.StandardOpenOption
import java.util.stream.Collectors
import com.muwire.core.Persona
import com.muwire.core.connection.Endpoint
import com.muwire.core.mesh.Mesh
import com.muwire.core.util.DataUtil
import net.i2p.data.Destination
class ContentUploader extends Uploader {
private final File file
private final ContentRequest request
private final Mesh mesh
private final int pieceSize
ContentUploader(File file, ContentRequest request, Endpoint endpoint) {
ContentUploader(File file, ContentRequest request, Endpoint endpoint, Mesh mesh, int pieceSize) {
super(endpoint)
this.file = file
this.request = request
this.mesh = mesh
this.pieceSize = pieceSize
}
@Override
void respond() {
OutputStream os = endpoint.getOutputStream()
Range range = request.getRange()
if (range.start >= file.length() || range.end >= file.length()) {
os.write("416 Range Not Satisfiable\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
boolean satisfiable = true
final long length = file.length()
if (range.start >= length || range.end >= length)
satisfiable = false
if (satisfiable) {
int startPiece = range.start / (0x1 << pieceSize)
int endPiece = range.end / (0x1 << pieceSize)
for (int i = startPiece; i <= endPiece; i++)
satisfiable &= mesh.pieces.isDownloaded(i)
}
if (!satisfiable) {
os.write("416 Range Not Satisfiable\r\n".getBytes(StandardCharsets.US_ASCII))
writeMesh(request.downloader)
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
os.flush()
return
}
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Content-Range: $range.start-$range.end\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Content-Range: $range.start-$range.end\r\n".getBytes(StandardCharsets.US_ASCII))
writeMesh(request.downloader)
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
FileChannel channel
try {
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
@ -50,6 +74,17 @@ class ContentUploader extends Uploader {
endpoint.getOutputStream().flush()
}
}
private void writeMesh(Persona toExclude) {
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
Set<Persona> sources = mesh.getRandom(3, toExclude)
if (!sources.isEmpty()) {
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
}
}
@Override
public String getName() {
@ -70,4 +105,14 @@ class ContentUploader extends Uploader {
request.downloader.getHumanReadableName()
}
@Override
public int getDonePieces() {
return request.have;
}
@Override
public int getTotalPieces() {
return mesh.pieces.nPieces;
}
}

View File

@ -50,6 +50,16 @@ class HashListUploader extends Uploader {
public String getDownloader() {
request.downloader.getHumanReadableName()
}
@Override
public int getDonePieces() {
return 0;
}
@Override
public int getTotalPieces() {
return 1;
}
}

View File

@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets
import com.muwire.core.Constants
import com.muwire.core.InfoHash
import com.muwire.core.Persona
import com.muwire.core.util.DataUtil
import groovy.util.logging.Log
import net.i2p.data.Base64
@ -48,8 +49,14 @@ class Request {
def decoded = Base64.decode(encoded)
downloader = new Persona(new ByteArrayInputStream(decoded))
}
int have = 0
if (headers.containsKey("X-Have")) {
def encoded = headers["X-Have"].trim()
have = DataUtil.decodeXHave(encoded).size()
}
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
headers : headers, downloader : downloader)
headers : headers, downloader : downloader, have : have)
}
static Request parseHashListRequest(InfoHash infoHash, InputStream is) throws IOException {

View File

@ -6,7 +6,12 @@ import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.SharedFile
import com.muwire.core.connection.Endpoint
import com.muwire.core.download.DownloadManager
import com.muwire.core.download.Downloader
import com.muwire.core.download.SourceDiscoveredEvent
import com.muwire.core.files.FileManager
import com.muwire.core.mesh.Mesh
import com.muwire.core.mesh.MeshManager
import groovy.util.logging.Log
import net.i2p.data.Base64
@ -15,12 +20,17 @@ import net.i2p.data.Base64
public class UploadManager {
private final EventBus eventBus
private final FileManager fileManager
private final MeshManager meshManager
private final DownloadManager downloadManager
public UploadManager() {}
public UploadManager(EventBus eventBus, FileManager fileManager) {
public UploadManager(EventBus eventBus, FileManager fileManager,
MeshManager meshManager, DownloadManager downloadManager) {
this.eventBus = eventBus
this.fileManager = fileManager
this.meshManager = meshManager
this.downloadManager = downloadManager
}
public void processGET(Endpoint e) throws IOException {
@ -44,8 +54,10 @@ public class UploadManager {
log.info("Responding to upload request for root $infoHashString")
byte [] infoHashRoot = Base64.decode(infoHashString)
InfoHash infoHash = new InfoHash(infoHashRoot)
Set<SharedFile> sharedFiles = fileManager.getSharedFiles(infoHashRoot)
if (sharedFiles == null || sharedFiles.isEmpty()) {
Downloader downloader = downloadManager.downloaders.get(infoHash)
if (downloader == null && (sharedFiles == null || sharedFiles.isEmpty())) {
log.info "file not found"
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
e.getOutputStream().flush()
@ -61,13 +73,31 @@ public class UploadManager {
return
}
Request request = Request.parseContentRequest(new InfoHash(infoHashRoot), e.getInputStream())
ContentRequest request = Request.parseContentRequest(infoHash, e.getInputStream())
if (request.downloader != null && request.downloader.destination != e.destination) {
log.info("Downloader persona doesn't match their destination")
e.close()
return
}
Uploader uploader = new ContentUploader(sharedFiles.iterator().next().file, request, e)
if (request.have > 0)
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
Mesh mesh
File file
int pieceSize
if (downloader != null) {
mesh = meshManager.get(infoHash)
file = downloader.incompleteFile
pieceSize = downloader.pieceSizePow2
} else {
SharedFile sharedFile = sharedFiles.iterator().next();
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces)
file = sharedFile.file
pieceSize = sharedFile.pieceSize
}
Uploader uploader = new ContentUploader(file, request, e, mesh, pieceSize)
eventBus.publish(new UploadEvent(uploader : uploader))
try {
uploader.respond()
@ -85,8 +115,10 @@ public class UploadManager {
log.info("Responding to hashlist request for root $infoHashString")
byte [] infoHashRoot = Base64.decode(infoHashString)
InfoHash infoHash = new InfoHash(infoHashRoot)
Downloader downloader = downloadManager.downloaders.get(infoHash)
Set<SharedFile> sharedFiles = fileManager.getSharedFiles(infoHashRoot)
if (sharedFiles == null || sharedFiles.isEmpty()) {
if (downloader == null && (sharedFiles == null || sharedFiles.isEmpty())) {
log.info "file not found"
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
e.getOutputStream().flush()
@ -102,13 +134,30 @@ public class UploadManager {
return
}
Request request = Request.parseHashListRequest(new InfoHash(infoHashRoot), e.getInputStream())
Request request = Request.parseHashListRequest(infoHash, e.getInputStream())
if (request.downloader != null && request.downloader.destination != e.destination) {
log.info("Downloader persona doesn't match their destination")
e.close()
return
}
Uploader uploader = new HashListUploader(e, sharedFiles.iterator().next().infoHash, request)
InfoHash fullInfoHash
if (downloader == null) {
fullInfoHash = sharedFiles.iterator().next().infoHash
} else {
byte [] hashList = downloader.getInfoHash().getHashList()
if (hashList != null && hashList.length > 0)
fullInfoHash = downloader.getInfoHash()
else {
log.info("infohash not found in downloader")
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
e.getOutputStream().flush()
e.close()
return
}
}
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
eventBus.publish(new UploadEvent(uploader : uploader))
try {
uploader.respond()
@ -130,8 +179,10 @@ public class UploadManager {
log.info("Responding to upload request for root $infoHashString")
infoHashRoot = Base64.decode(infoHashString)
infoHash = new InfoHash(infoHashRoot)
sharedFiles = fileManager.getSharedFiles(infoHashRoot)
if (sharedFiles == null || sharedFiles.isEmpty()) {
downloader = downloadManager.downloaders.get(infoHash)
if (downloader == null && (sharedFiles == null || sharedFiles.isEmpty())) {
log.info "file not found"
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
e.getOutputStream().flush()
@ -153,7 +204,25 @@ public class UploadManager {
e.close()
return
}
uploader = new ContentUploader(sharedFiles.iterator().next().file, request, e)
if (request.have > 0)
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
Mesh mesh
File file
int pieceSize
if (downloader != null) {
mesh = meshManager.get(infoHash)
file = downloader.incompleteFile
pieceSize = downloader.pieceSizePow2
} else {
SharedFile sharedFile = sharedFiles.iterator().next();
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces)
file = sharedFile.file
pieceSize = sharedFile.pieceSize
}
uploader = new ContentUploader(file, request, e, mesh, pieceSize)
eventBus.publish(new UploadEvent(uploader : uploader))
try {
uploader.respond()

View File

@ -32,4 +32,8 @@ abstract class Uploader {
abstract int getProgress();
abstract String getDownloader();
abstract int getDonePieces();
abstract int getTotalPieces()
}

View File

@ -4,6 +4,8 @@ import java.nio.charset.StandardCharsets
import com.muwire.core.Constants
import net.i2p.data.Base64
class DataUtil {
private final static int MAX_SHORT = (0x1 << 16) - 1
@ -79,4 +81,38 @@ class DataUtil {
}
new String(baos.toByteArray(), StandardCharsets.US_ASCII)
}
public static String encodeXHave(List<Integer> pieces, int totalPieces) {
int bytes = totalPieces / 8
if (totalPieces % 8 != 0)
bytes++
byte[] raw = new byte[bytes]
pieces.each {
int byteIdx = it / 8
int offset = it % 8
int mask = 0x80 >>> offset
raw[byteIdx] |= mask
}
Base64.encode(raw)
}
public static List<Integer> decodeXHave(String xHave) {
byte [] availablePieces = Base64.decode(xHave)
List<Integer> available = new ArrayList<>()
availablePieces.eachWithIndex {b, i ->
for (int j = 0; j < 8 ; j++) {
byte mask = 0x80 >>> j
if ((b & mask) == mask) {
available.add(i * 8 + j)
}
}
}
available
}
public static Exception findRoot(Exception e) {
while(e.getCause() != null)
e = e.getCause()
e
}
}

View File

@ -1,6 +1,7 @@
package com.muwire.core;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import net.i2p.data.Destination;
@ -9,7 +10,8 @@ public class DownloadedFile extends SharedFile {
private final Set<Destination> sources;
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources) {
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
throws IOException {
super(file, infoHash, pieceSize);
this.sources = sources;
}

View File

@ -1,6 +1,7 @@
package com.muwire.core;
import java.io.File;
import java.io.IOException;
public class SharedFile {
@ -8,10 +9,15 @@ public class SharedFile {
private final InfoHash infoHash;
private final int pieceSize;
public SharedFile(File file, InfoHash infoHash, int pieceSize) {
private final String cachedPath;
private final long cachedLength;
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
this.file = file;
this.infoHash = infoHash;
this.pieceSize = pieceSize;
this.cachedPath = file.getAbsolutePath();
this.cachedLength = file.length();
}
public File getFile() {
@ -26,6 +32,23 @@ public class SharedFile {
return pieceSize;
}
public int getNPieces() {
long length = file.length();
int rawPieceSize = 0x1 << pieceSize;
int rv = (int) (length / rawPieceSize);
if (length % rawPieceSize != 0)
rv++;
return rv;
}
public String getCachedPath() {
return cachedPath;
}
public long getCachedLength() {
return cachedLength;
}
@Override
public int hashCode() {
return file.hashCode() ^ infoHash.hashCode();

View File

@ -1,17 +1,26 @@
package com.muwire.core.download
import static org.junit.Assert.fail
import org.junit.After
import org.junit.Before
import org.junit.Test
import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.Persona
import com.muwire.core.Personas
import com.muwire.core.connection.Endpoint
import com.muwire.core.files.FileHasher
import static com.muwire.core.util.DataUtil.readTillRN
import static com.muwire.core.util.DataUtil.encodeXHave
import net.i2p.data.Base64
import net.i2p.util.ConcurrentHashSet
class DownloadSessionTest {
private EventBus eventBus
private File source, target
private InfoHash infoHash
private Endpoint endpoint
@ -24,6 +33,16 @@ class DownloadSessionTest {
private InputStream fromDownloader, fromUploader
private OutputStream toDownloader, toUploader
private volatile boolean performed
private Set<Integer> available = new ConcurrentHashSet<>()
private volatile IOException thrown
@Before
public void setUp() {
eventBus = new EventBus()
}
private void initSession(int size, def claimedPieces = []) {
Random r = new Random()
byte [] content = new byte[size]
@ -56,12 +75,20 @@ class DownloadSessionTest {
toUploader = new PipedOutputStream(fromDownloader)
endpoint = new Endpoint(null, fromUploader, toUploader, null)
session = new DownloadSession("",pieces, infoHash, endpoint, target, pieceSize, size)
downloadThread = new Thread( { session.request() } as Runnable)
session = new DownloadSession(eventBus, "",pieces, infoHash, endpoint, target, pieceSize, size, available)
downloadThread = new Thread( { perform() } as Runnable)
downloadThread.setDaemon(true)
downloadThread.start()
}
private void perform() {
try {
performed = session.request()
} catch (IOException e) {
thrown = e
}
}
@After
public void teardown() {
source?.delete()
@ -76,6 +103,7 @@ class DownloadSessionTest {
assert "GET $rootBase64" == readTillRN(fromDownloader)
assert "Range: 0-19" == readTillRN(fromDownloader)
readTillRN(fromDownloader)
readTillRN(fromDownloader)
assert "" == readTillRN(fromDownloader)
toDownloader.write("200 OK\r\n".bytes)
@ -87,6 +115,9 @@ class DownloadSessionTest {
assert pieces.isComplete()
assert target.bytes == source.bytes
assert performed
assert available.isEmpty()
assert thrown == null
}
@Test
@ -98,6 +129,7 @@ class DownloadSessionTest {
assert "GET $rootBase64" == readTillRN(fromDownloader)
readTillRN(fromDownloader)
readTillRN(fromDownloader)
readTillRN(fromDownloader)
assert "" == readTillRN(fromDownloader)
toDownloader.write("200 OK\r\n".bytes)
@ -108,6 +140,9 @@ class DownloadSessionTest {
Thread.sleep(150)
assert pieces.isComplete()
assert target.bytes == source.bytes
assert performed
assert available.isEmpty()
assert thrown == null
}
@Test
@ -125,6 +160,7 @@ class DownloadSessionTest {
assert (start == 0 && end == ((1 << pieceSize) - 1)) ||
(start == (1 << pieceSize) && end == (1 << pieceSize))
readTillRN(fromDownloader)
readTillRN(fromDownloader)
assert "" == readTillRN(fromDownloader)
@ -138,6 +174,9 @@ class DownloadSessionTest {
Thread.sleep(150)
assert !pieces.isComplete()
assert 1 == pieces.donePieces()
assert performed
assert available.isEmpty()
assert thrown == null
}
@Test
@ -145,7 +184,10 @@ class DownloadSessionTest {
initSession(20, [0])
long now = System.currentTimeMillis()
downloadThread.join(100)
assert 100 > (System.currentTimeMillis() - now)
assert 100 >= (System.currentTimeMillis() - now)
assert !performed
assert available.isEmpty()
assert thrown == null
}
@Test
@ -164,4 +206,128 @@ class DownloadSessionTest {
assert pieces.claimed.get(0)
assert start == 0 && end == (1 << pieceSize) - 1
}
@Test
public void test416NoHave() {
initSession(20)
readAllHeaders(fromDownloader)
toDownloader.write("416 don't have it\r\n\r\n".bytes)
toDownloader.flush()
Thread.sleep(150)
assert !performed
assert available.isEmpty()
assert thrown != null
}
@Test
public void test416Have() {
initSession(20)
readAllHeaders(fromDownloader)
toDownloader.write("416 don't have it\r\n".bytes)
toDownloader.write("X-Have: ${encodeXHave([0], 1)}\r\n\r\n".bytes)
toDownloader.flush()
Thread.sleep(150)
assert performed
assert available.contains(0)
assert thrown == null
}
@Test
public void test416Have2Pieces() {
int pieceSize = FileHasher.getPieceSize(1)
int size = (1 << pieceSize) + 1
initSession(size)
readAllHeaders(fromDownloader)
toDownloader.write("416 don't have it\r\n".bytes)
toDownloader.write("X-Have: ${encodeXHave([1], 2)}\r\n\r\n".bytes)
toDownloader.flush()
Thread.sleep(150)
assert performed
assert available.contains(1)
assert thrown == null
}
@Test
public void test200TwoPieces1Available() {
int pieceSize = FileHasher.getPieceSize(1)
int size = (1 << pieceSize) * 9 + 1
initSession(size)
Set<String> headers = readAllHeaders(fromDownloader)
def matcher = null
headers.each {
if (it.startsWith("Range"))
matcher = (it =~ /^Range: (\d+)-(\d+)$/)
}
assert matcher.groupCount() > 0
int start = Integer.parseInt(matcher[0][1])
int end = Integer.parseInt(matcher[0][2])
if (start == 0)
fail("inconlcusive")
toDownloader.write("416 don't have it \r\n".bytes)
toDownloader.write("X-Have: ${encodeXHave([0],2)}\r\n\r\n".bytes)
toDownloader.flush()
downloadThread.join()
assert performed
performed = false
assert available.contains(0)
assert thrown == null
// request same session
downloadThread = new Thread( { perform() } as Runnable)
downloadThread.setDaemon(true)
downloadThread.start()
Thread.sleep(150)
headers = readAllHeaders(fromDownloader)
matcher = null
headers.each {
if (it.startsWith("Range"))
matcher = (it =~ /^Range: (\d+)-(\d+)$/)
}
assert matcher.groupCount() > 0
start = Integer.parseInt(matcher[0][1])
end = Integer.parseInt(matcher[0][2])
assert start == 0
}
@Test
public void testXAlt() throws Exception {
Personas personas = new Personas()
def sources = []
def listener = new Object() {
public void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
sources << e.source
}
}
eventBus.register(SourceDiscoveredEvent.class, listener)
initSession(20)
readAllHeaders(fromDownloader)
toDownloader.write("416 don't have it\r\n".bytes)
toDownloader.write("X-Alt: ${personas.persona1.toBase64()},${personas.persona2.toBase64()}\r\n\r\n".bytes)
toDownloader.flush()
Thread.sleep(150)
assert sources.contains(personas.persona1)
assert sources.contains(personas.persona2)
assert 2 == sources.size()
}
private static Set<String> readAllHeaders(InputStream is) {
Set<String> rv = new HashSet<>()
String header
while((header = readTillRN(is)) != "")
rv.add(header)
rv
}
}

View File

@ -34,4 +34,19 @@ class PiecesTest {
pieces.markDownloaded(piece2)
assert pieces.isComplete()
}
@Test
public void testClaimAvailable() {
pieces = new Pieces(2)
int claimed = pieces.claim([0].toSet())
assert claimed == 0
assert -1 == pieces.claim([0].toSet())
}
@Test
public void testClaimNoneAvailable() {
pieces = new Pieces(20)
int claimed = pieces.claim()
assert -1 == pieces.claim([claimed].toSet())
}
}

View File

@ -83,4 +83,11 @@ class SearchIndexTest {
assert found.size() == 1
assert found.contains("b c.d")
}
@Test
void testDuplicateTerm() {
initIndex(["MuWire-0.3.3.jar"])
def found = index.search(["muwire", "0", "3", "jar"])
assert found.size() == 1
}
}

View File

@ -1,5 +1,5 @@
group = com.muwire
version = 0.3.0
version = 0.4.1
groovyVersion = 2.4.15
slf4jVersion = 1.7.25
spockVersion = 1.1-groovy-2.4

View File

@ -17,6 +17,9 @@ import com.muwire.core.Core
import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.search.QueryEvent
import com.muwire.core.search.SearchEvent
import com.muwire.core.trust.TrustEvent
@ -42,6 +45,8 @@ class MainFrameController {
search = search.trim()
if (search.length() == 0)
return
if (search.length() > 128)
search = search.substring(0,128)
def uuid = UUID.randomUUID()
Map<String, Object> params = new HashMap<>()
params["search-terms"] = search
@ -67,7 +72,9 @@ class MainFrameController {
// this can be improved a lot
def replaced = search.toLowerCase().trim().replaceAll(Constants.SPLIT_PATTERN, " ")
def terms = replaced.split(" ")
searchEvent = new SearchEvent(searchTerms : terms, uuid : uuid, oobInfohash: true)
def nonEmpty = []
terms.each { if (it.length() > 0) nonEmpty << it }
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true)
}
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true,
replyTo: core.me.destination, receivedOn: core.me.destination,
@ -162,6 +169,14 @@ class MainFrameController {
void resume() {
def downloader = model.downloads[selectedDownload()].downloader
downloader.resume()
core.eventBus.publish(new UIDownloadResumedEvent())
}
@ControllerAction
void pause() {
def downloader = model.downloads[selectedDownload()].downloader
downloader.pause()
core.eventBus.publish(new UIDownloadPausedEvent())
}
private void markTrust(String tableName, TrustLevel level, def list) {
@ -195,6 +210,18 @@ class MainFrameController {
println "unsharing selected files"
}
void stopWatchingDirectory() {
String directory = mvcGroup.view.getSelectedWatchedDirectory()
if (directory == null)
return
core.muOptions.watchedDirectories.remove(directory)
saveMuWireSettings()
core.eventBus.publish(new DirectoryUnsharedEvent(directory : new File(directory)))
model.watched.remove(directory)
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
}
void saveMuWireSettings() {
File f = new File(core.home, "MuWire.properties")
f.withOutputStream {

View File

@ -4,9 +4,15 @@ import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import groovy.util.logging.Log
import java.util.logging.Level
import javax.annotation.Nonnull
import javax.swing.JFileChooser
import com.muwire.core.Core
import com.muwire.core.MuWireSettings
@ArtifactProviderFor(GriffonController)
class OptionsController {
@ -46,7 +52,7 @@ class OptionsController {
text = view.retryField.text
model.downloadRetryInterval = text
def settings = application.context.get("muwire-settings")
MuWireSettings settings = application.context.get("muwire-settings")
settings.downloadRetryInterval = Integer.valueOf(text)
text = view.updateField.text
@ -60,7 +66,19 @@ class OptionsController {
boolean shareDownloaded = view.shareDownloadedCheckbox.model.isSelected()
model.shareDownloadedFiles = shareDownloaded
settings.shareDownloadedFiles = shareDownloaded
String downloadLocation = model.downloadLocation
settings.downloadLocation = new File(downloadLocation)
if (settings.embeddedRouter) {
text = view.inBwField.text
model.inBw = text
settings.inBw = Integer.valueOf(text)
text = view.outBwField.text
model.outBw = text
settings.outBw = Integer.valueOf(text)
}
File settingsFile = new File(core.home, "MuWire.properties")
settingsFile.withOutputStream {
settings.write(it)
@ -77,9 +95,9 @@ class OptionsController {
model.font = text
uiSettings.font = text
boolean showMonitor = view.monitorCheckbox.model.isSelected()
model.showMonitor = showMonitor
uiSettings.showMonitor = showMonitor
// boolean showMonitor = view.monitorCheckbox.model.isSelected()
// model.showMonitor = showMonitor
// uiSettings.showMonitor = showMonitor
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
model.clearCancelledDownloads = clearCancelledDownloads
@ -93,9 +111,9 @@ class OptionsController {
model.excludeLocalResult = excludeLocalResult
uiSettings.excludeLocalResult = excludeLocalResult
boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected()
model.showSearchHashes = showSearchHashes
uiSettings.showSearchHashes = showSearchHashes
// boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected()
// model.showSearchHashes = showSearchHashes
// uiSettings.showSearchHashes = showSearchHashes
File uiSettingsFile = new File(core.home, "gui.properties")
uiSettingsFile.withOutputStream {
@ -110,4 +128,15 @@ class OptionsController {
view.d.setVisible(false)
mvcGroup.destroy()
}
@ControllerAction
void downloadLocation() {
def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select location for downloaded files")
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
int rv = chooser.showOpenDialog(null)
if (rv == JFileChooser.APPROVE_OPTION)
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
}
}

View File

@ -43,6 +43,8 @@ class Initialize extends AbstractLifecycleHandler {
application.context.put("muwire-home", home.getAbsolutePath())
System.getProperties().setProperty("awt.useSystemAAFontSettings", "true")
def guiPropsFile = new File(home, "gui.properties")
UISettings uiSettings
if (guiPropsFile.exists()) {

View File

@ -48,6 +48,7 @@ class Ready extends AbstractLifecycleHandler {
} else {
log.info("creating new properties")
props = new MuWireSettings()
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
def nickname
while (true) {
nickname = JOptionPane.showInputDialog(null,
@ -74,6 +75,7 @@ class Ready extends AbstractLifecycleHandler {
props.downloadLocation = new File(portableDownloads)
} else {
def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select a directory where downloads will be saved")
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
int rv = chooser.showOpenDialog(null)
@ -98,6 +100,9 @@ class Ready extends AbstractLifecycleHandler {
"Can't connect to I2P router", JOptionPane.WARNING_MESSAGE)
System.exit(0)
}
Runtime.getRuntime().addShutdownHook({
core.shutdown()
})
core.startServices()
application.context.put("muwire-settings", props)
application.context.put("core",core)

View File

@ -11,11 +11,13 @@ import com.muwire.core.Core
import com.muwire.core.InfoHash
import com.muwire.core.MuWireSettings
import com.muwire.core.Persona
import com.muwire.core.RouterDisconnectedEvent
import com.muwire.core.connection.ConnectionAttemptStatus
import com.muwire.core.connection.ConnectionEvent
import com.muwire.core.connection.DisconnectionEvent
import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.Downloader
import com.muwire.core.files.AllFilesLoadedEvent
import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHashedEvent
import com.muwire.core.files.FileLoadedEvent
@ -67,6 +69,8 @@ class MainFrameModel {
@Observable boolean trustButtonsEnabled
@Observable boolean cancelButtonEnabled
@Observable boolean retryButtonEnabled
@Observable boolean pauseButtonEnabled
@Observable String resumeButtonText
private final Set<InfoHash> infoHashes = new HashSet<>()
@ -135,8 +139,12 @@ class MainFrameModel {
core.eventBus.register(UpdateAvailableEvent.class, this)
core.eventBus.register(FileDownloadedEvent.class, this)
core.eventBus.register(FileUnsharedEvent.class, this)
core.eventBus.register(RouterDisconnectedEvent.class, this)
core.eventBus.register(AllFilesLoadedEvent.class, this)
timer.schedule({
if (core.shutdown.get())
return
int retryInterval = core.muOptions.downloadRetryInterval
if (retryInterval > 0) {
retryInterval *= 60000
@ -161,14 +169,20 @@ class MainFrameModel {
trusted.addAll(core.trustService.good.values())
distrusted.addAll(core.trustService.bad.values())
watched.addAll(core.muOptions.watchedDirectories)
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
resumeButtonText = "Retry"
}
})
}
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
runInsideUIAsync {
watched.addAll(core.muOptions.watchedDirectories)
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
}
}
void onUIResultEvent(UIResultEvent e) {
MVCGroup resultsGroup = results.get(e.uuid)
resultsGroup?.model.handleResult(e)
@ -337,6 +351,14 @@ class MainFrameModel {
}
}
void onRouterDisconnectedEvent(RouterDisconnectedEvent e) {
runInsideUIAsync {
JOptionPane.showMessageDialog(null, "MuWire lost connection to the I2P router and will now exit.",
"Connection to I2P router lost", JOptionPane.WARNING_MESSAGE)
System.exit(0)
}
}
void onFileDownloadedEvent(FileDownloadedEvent e) {
if (!core.muOptions.shareDownloadedFiles)
return

View File

@ -13,6 +13,7 @@ class OptionsModel {
@Observable String updateCheckInterval
@Observable boolean onlyTrusted
@Observable boolean shareDownloadedFiles
@Observable String downloadLocation
// i2p options
@Observable String inboundLength
@ -29,12 +30,17 @@ class OptionsModel {
@Observable boolean excludeLocalResult
@Observable boolean showSearchHashes
// bw options
@Observable String inBw
@Observable String outBw
void mvcGroupInit(Map<String, String> args) {
MuWireSettings settings = application.context.get("muwire-settings")
downloadRetryInterval = settings.downloadRetryInterval
updateCheckInterval = settings.updateCheckInterval
onlyTrusted = !settings.allowUntrusted()
shareDownloadedFiles = settings.shareDownloadedFiles
downloadLocation = settings.downloadLocation.getAbsolutePath()
Core core = application.context.get("core")
inboundLength = core.i2pOptions["inbound.length"]
@ -50,5 +56,10 @@ class OptionsModel {
clearFinishedDownloads = uiSettings.clearFinishedDownloads
excludeLocalResult = uiSettings.excludeLocalResult
showSearchHashes = uiSettings.showSearchHashes
if (core.router != null) {
inBw = String.valueOf(settings.inBw)
outBw = String.valueOf(settings.outBw)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -21,10 +21,10 @@ class EventListView {
application(size: [320, 80], id: 'event-list',
locationRelativeTo : null,
title: application.configuration['application.title'],
iconImage: imageIcon('/griffon-icon-48x48.png').image,
iconImages: [imageIcon('/griffon-icon-48x48.png').image,
imageIcon('/griffon-icon-32x32.png').image,
imageIcon('/griffon-icon-16x16.png').image],
iconImage: imageIcon('/MuWire-48x48.png').image,
iconImages: [imageIcon('/MuWire-48x48.png').image,
imageIcon('/MuWire-32x32.png').image,
imageIcon('/MuWire-16x16.png').image],
visible: bind { !model.coreInitialized} ) {
panel {
vbox {

View File

@ -52,6 +52,7 @@ class MainFrameView {
def downloadsTable
def lastDownloadSortEvent
def lastSharedSortEvent
def lastWatchedSortEvent
void initUI() {
UISettings settings = application.context.get("ui-settings")
@ -60,10 +61,10 @@ class MainFrameView {
locationRelativeTo : null,
title: application.configuration['application.title'] + " " +
metadata["application.version"] + " revision " + metadata["build.revision"],
iconImage: imageIcon('/griffon-icon-48x48.png').image,
iconImages: [imageIcon('/griffon-icon-48x48.png').image,
imageIcon('/griffon-icon-32x32.png').image,
imageIcon('/griffon-icon-16x16.png').image],
iconImage: imageIcon('/MuWire-48x48.png').image,
iconImages: [imageIcon('/MuWire-48x48.png').image,
imageIcon('/MuWire-32x32.png').image,
imageIcon('/MuWire-16x16.png').image],
pack : false,
visible : bind { model.coreInitialized }) {
menuBar {
@ -135,8 +136,9 @@ class MainFrameView {
}
}
panel (constraints : BorderLayout.SOUTH) {
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
button(text: "Retry", enabled : bind {model.retryButtonEnabled}, resumeAction)
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
}
}
}
@ -166,8 +168,8 @@ class MainFrameView {
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "shared-files-table", autoCreateRowSorter: true) {
tableModel(list : model.shared) {
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.file.getAbsolutePath()})
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.file.length() })
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
}
}
}
@ -184,11 +186,14 @@ class MainFrameView {
closureColumn(header : "Name", type : String, read : {row -> row.getName() })
closureColumn(header : "Progress", type : String, read : { row ->
int percent = row.getProgress()
"$percent%"
"$percent% of piece".toString()
})
closureColumn(header : "Downloader", type : String, read : { row ->
row.getDownloader()
})
closureColumn(header : "Remote Pieces", type : String, read : { row ->
"${row.getDonePieces()}/${row.getTotalPieces()}".toString()
})
}
}
}
@ -295,6 +300,7 @@ class MainFrameView {
if (selectedRow < 0) {
model.cancelButtonEnabled = false
model.retryButtonEnabled = false
model.pauseButtonEnabled = false
return
}
def downloader = model.downloads[selectedRow]?.downloader
@ -305,15 +311,25 @@ class MainFrameView {
case Downloader.DownloadState.DOWNLOADING :
case Downloader.DownloadState.HASHLIST:
model.cancelButtonEnabled = true
model.pauseButtonEnabled = true
model.retryButtonEnabled = false
break
case Downloader.DownloadState.FAILED:
model.cancelButtonEnabled = true
model.retryButtonEnabled = true
model.resumeButtonText = "Retry"
model.pauseButtonEnabled = false
break
case Downloader.DownloadState.PAUSED:
model.cancelButtonEnabled = true
model.retryButtonEnabled = true
model.resumeButtonText = "Resume"
model.pauseButtonEnabled = false
break
default:
model.cancelButtonEnabled = false
model.retryButtonEnabled = false
model.pauseButtonEnabled = false
}
})
@ -382,6 +398,27 @@ class MainFrameView {
}
})
// watched directories table
def watchedTable = builder.getVariable("watched-directories-table")
watchedTable.rowSorter.addRowSorterListener({evt -> lastWatchedSortEvent = evt})
watchedTable.rowSorter.setSortsOnUpdates(true)
JPopupMenu watchedMenu = new JPopupMenu()
JMenuItem stopWatching = new JMenuItem("Stop sharing")
stopWatching.addActionListener({mvcGroup.controller.stopWatchingDirectory()})
watchedMenu.add(stopWatching)
watchedTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(watchedMenu, e)
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(watchedMenu, e)
}
})
}
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
@ -421,21 +458,32 @@ class MainFrameView {
int selected = selectedDownloaderRow()
if (selected < 0)
return
boolean pauseEnabled = false
boolean cancelEnabled = false
boolean retryEnabled = false
String resumeText = "Retry"
Downloader downloader = model.downloads[selected].downloader
switch(downloader.currentState) {
case Downloader.DownloadState.DOWNLOADING:
case Downloader.DownloadState.HASHLIST:
case Downloader.DownloadState.CONNECTING:
pauseEnabled = true
cancelEnabled = true
retryEnabled = false
break
case Downloader.DownloadState.FAILED:
pauseEnabled = false
cancelEnabled = true
retryEnabled = true
break
case Downloader.DownloadState.PAUSED:
pauseEnabled = false
cancelEnabled = true
retryEnabled = true
resumeText = "Resume"
break
default :
pauseEnabled = false
cancelEnabled = false
retryEnabled = false
}
@ -450,6 +498,12 @@ class MainFrameView {
})
menu.add(copyHashToClipboard)
if (pauseEnabled) {
JMenuItem pause = new JMenuItem("Pause")
pause.addActionListener({mvcGroup.controller.pause()})
menu.add(pause)
}
if (cancelEnabled) {
JMenuItem cancel = new JMenuItem("Cancel")
cancel.addActionListener({mvcGroup.controller.cancel()})
@ -457,7 +511,7 @@ class MainFrameView {
}
if (retryEnabled) {
JMenuItem retry = new JMenuItem("Retry")
JMenuItem retry = new JMenuItem(resumeText)
retry.addActionListener({mvcGroup.controller.resume()})
menu.add(retry)
}
@ -487,6 +541,7 @@ class MainFrameView {
def shareFiles = {
def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select file to share")
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
int rv = chooser.showOpenDialog(null)
@ -497,6 +552,7 @@ class MainFrameView {
def watchDirectories = {
def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select directory to watch")
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
int rv = chooser.showOpenDialog(null)
@ -509,4 +565,14 @@ class MainFrameView {
model.core.eventBus.publish(new FileSharedEvent(file : f))
}
}
String getSelectedWatchedDirectory() {
def watchedTable = builder.getVariable("watched-directories-table")
int selectedRow = watchedTable.getSelectedRow()
if (selectedRow < 0)
return null
if (lastWatchedSortEvent != null)
selectedRow = watchedTable.rowSorter.convertRowIndexToModel(selectedRow)
model.watched[selectedRow]
}
}

View File

@ -9,6 +9,8 @@ import javax.swing.JPanel
import javax.swing.JTabbedPane
import javax.swing.SwingConstants
import com.muwire.core.Core
import java.awt.BorderLayout
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
@ -26,6 +28,7 @@ class OptionsView {
def p
def i
def u
def bandwidth
def retryField
def updateField
@ -45,6 +48,10 @@ class OptionsView {
def excludeLocalResultCheckbox
def showSearchHashesCheckbox
def inBwField
def outBwField
def buttonsPanel
def mainFrame
@ -68,6 +75,10 @@ class OptionsView {
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
}
i = builder.panel {
@ -89,17 +100,27 @@ class OptionsView {
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
}
bandwidth = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1))
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 2))
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 2))
}
buttonsPanel = builder.panel {
gridBagLayout()
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
@ -112,6 +133,10 @@ class OptionsView {
tabbedPane.addTab("MuWire", p)
tabbedPane.addTab("I2P", i)
tabbedPane.addTab("GUI", u)
Core core = application.context.get("core")
if (core.router != null) {
tabbedPane.addTab("Bandwidth", bandwidth)
}
JPanel panel = new JPanel()
panel.setLayout(new BorderLayout())

View File

@ -12,12 +12,12 @@ class UISettings {
UISettings(Properties props) {
lnf = props.getProperty("lnf", "system")
showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "true"))
showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "false"))
font = props.getProperty("font",null)
clearCancelledDownloads = Boolean.parseBoolean(props.getProperty("clearCancelledDownloads","false"))
clearCancelledDownloads = Boolean.parseBoolean(props.getProperty("clearCancelledDownloads","true"))
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","false"))
showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","false"))
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","true"))
showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","true"))
}
void write(OutputStream out) throws IOException {

View File

@ -0,0 +1,61 @@
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.FileHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= SEVERE
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = MuWire.log
java.util.logging.FileHandler.limit = 50000000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
# <level>: <log message> [<date/time>]
#
#java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %2$s %5$s %6$s %n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE

View File

@ -0,0 +1,61 @@
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.FileHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= WARNING
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = MuWire.log
java.util.logging.FileHandler.limit = 50000000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
# <level>: <log message> [<date/time>]
#
#java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %2$s %5$s %6$s %n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE

View File

@ -0,0 +1,61 @@
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.FileHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = MuWire.log
java.util.logging.FileHandler.limit = 50000000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
# <level>: <log message> [<date/time>]
#
#java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %2$s %5$s %6$s %n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE

View File

@ -0,0 +1,64 @@
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.FileHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = MuWire.log
java.util.logging.FileHandler.limit = 150000000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
# <level>: <log message> [<date/time>]
#
#java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %2$s %5$s %6$s %n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE
com.muwire.core.level = FINE
net.i2p.client.streaming.impl.level = FINE
net.i2p.client.impl.level = FINE