Compare commits
392 Commits
muwire-0.6
...
muwire-0.6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
853b9f67fc | ||
![]() |
a505a2449a | ||
![]() |
c11d81c6c3 | ||
![]() |
ee5e90c4ab | ||
![]() |
64d2a87d26 | ||
![]() |
f0304dbe7d | ||
![]() |
bdad8d9309 | ||
![]() |
8c110bbae5 | ||
![]() |
2cc1e384bc | ||
![]() |
9337d1b74d | ||
![]() |
16ed5dd346 | ||
![]() |
7b55fc9ed8 | ||
![]() |
d5c8050572 | ||
![]() |
83546d68d2 | ||
a891c83518 | |||
aa56cc23c0 | |||
a2b37ef567 | |||
4bc04ae631 | |||
56da9a16b0 | |||
2935ee1a1d | |||
855183397b | |||
e27704c1af | |||
5c18b4a141 | |||
dcd233b7ad | |||
7cee8a28ba | |||
7446fc949a | |||
598ab90f63 | |||
043028c296 | |||
cd1757fac3 | |||
9d4b365e63 | |||
![]() |
b12d57e30a | ||
![]() |
f33d1b6db3 | ||
![]() |
9e451460da | ||
![]() |
ffa52c129a | ||
b779fb75a0 | |||
fbe6b53278 | |||
b2bd95788d | |||
83d4a2624b | |||
03e20e21aa | |||
8a08955675 | |||
4ec54ebe54 | |||
758af6f48e | |||
a7bdd47fcd | |||
f7caa77a18 | |||
7641f64536 | |||
02baaace48 | |||
![]() |
d90067ff39 | ||
c910a215f5 | |||
65e073b1b9 | |||
489a7518c3 | |||
3733e48bbd | |||
c3723a1348 | |||
0e0f52bc77 | |||
60b9e990cf | |||
28ad0ae30f | |||
9142de85cd | |||
4eb31c11e3 | |||
e8afe358a5 | |||
![]() |
3db4317fc1 | ||
![]() |
5ad2b28527 | ||
![]() |
3036765f81 | ||
![]() |
8f9b1e5a8b | ||
![]() |
e6d59a2438 | ||
![]() |
32609b4779 | ||
![]() |
74ac4cfecf | ||
![]() |
69173c4156 | ||
![]() |
6283287bee | ||
![]() |
8e3f76f68c | ||
![]() |
574294fdc6 | ||
![]() |
8bd41546cd | ||
![]() |
ba5425c958 | ||
![]() |
22580f002c | ||
![]() |
5c773cec80 | ||
![]() |
7df00e6709 | ||
![]() |
5c05bd2562 | ||
![]() |
9df1d043e4 | ||
![]() |
6ea1a15641 | ||
![]() |
c0575facec | ||
![]() |
09168844e0 | ||
![]() |
e21d482393 | ||
![]() |
f5fc3e40c2 | ||
![]() |
796a0138fa | ||
![]() |
505b4ddb06 | ||
![]() |
a35216ff56 | ||
![]() |
fba92fe9b9 | ||
![]() |
1cc511b0ae | ||
![]() |
fa94c8ebfa | ||
![]() |
88b68a3c5c | ||
![]() |
b3e0d2ee7a | ||
![]() |
ce293cbda8 | ||
![]() |
3abc617e9f | ||
![]() |
67ee634f20 | ||
![]() |
503d54927f | ||
![]() |
5788329e1a | ||
![]() |
f0ffc68122 | ||
![]() |
3d710cebe5 | ||
![]() |
7d67573c92 | ||
![]() |
3acc676448 | ||
![]() |
2bf03b6b84 | ||
![]() |
b8ba6df4d5 | ||
![]() |
9fa7fa07b4 | ||
![]() |
1c7253ea0a | ||
![]() |
d947ad2997 | ||
![]() |
dd0bd6f5f8 | ||
![]() |
f05b6d0b40 | ||
![]() |
906c69a482 | ||
![]() |
5375b7aec0 | ||
![]() |
ea5da2431a | ||
![]() |
14b3a9ac9e | ||
![]() |
40bbef4583 | ||
![]() |
f811653247 | ||
![]() |
f321000071 | ||
![]() |
6eb85283cd | ||
![]() |
2973759cd9 | ||
![]() |
fe945a9941 | ||
![]() |
5f7e949310 | ||
![]() |
11edb2cb3c | ||
![]() |
ff1f801155 | ||
![]() |
0a98083c64 | ||
![]() |
75b2852f6e | ||
![]() |
5774cdee94 | ||
![]() |
2b0f4e52ca | ||
![]() |
1d20dc917b | ||
![]() |
63e3b3710c | ||
![]() |
0878b89082 | ||
![]() |
fecf0ecae8 | ||
![]() |
fec8d4ef9f | ||
![]() |
067ac8582a | ||
![]() |
31cac25a23 | ||
![]() |
6bcc44e01e | ||
![]() |
31652b34d7 | ||
![]() |
41a15fc7d5 | ||
![]() |
da3d7d7a50 | ||
![]() |
3a079d9f21 | ||
![]() |
ba0c85fe07 | ||
![]() |
ecb2283886 | ||
![]() |
cf9a18cee5 | ||
![]() |
982a93a04b | ||
![]() |
58137d11d1 | ||
![]() |
d87bec927d | ||
![]() |
dc8dd96495 | ||
![]() |
add9fb6feb | ||
![]() |
c500e95ab6 | ||
![]() |
477c3285d2 | ||
![]() |
1f5b112bfe | ||
![]() |
b0d09853e4 | ||
![]() |
b96d997037 | ||
![]() |
a631ec1e14 | ||
![]() |
62a06bc891 | ||
![]() |
3534b23194 | ||
![]() |
c561ae9140 | ||
![]() |
5926457eb5 | ||
![]() |
37c93e352b | ||
![]() |
be8fecda39 | ||
![]() |
7ec6257ac0 | ||
![]() |
c4ea58c330 | ||
![]() |
a482fe5c93 | ||
![]() |
2ee84848c4 | ||
![]() |
e29d7f6872 | ||
![]() |
5ded824ef2 | ||
![]() |
c607560cb8 | ||
![]() |
8b341bb125 | ||
![]() |
6bc5a9075b | ||
![]() |
6b1d2bc5ce | ||
![]() |
0cbbaf6a63 | ||
![]() |
3363b99675 | ||
![]() |
4ab4785539 | ||
![]() |
e595fa97e8 | ||
![]() |
65a7088463 | ||
![]() |
2d5bd653c1 | ||
![]() |
a864343c05 | ||
![]() |
696b348469 | ||
![]() |
b08333c5ea | ||
![]() |
0cf368c1af | ||
![]() |
62ab957892 | ||
![]() |
2b9e722165 | ||
![]() |
8cf4b23762 | ||
![]() |
1285c68521 | ||
![]() |
daa9e0bafc | ||
![]() |
8efd9c2c88 | ||
![]() |
918549f164 | ||
![]() |
e30a4666cb | ||
![]() |
26167abc08 | ||
![]() |
93f7c67f37 | ||
![]() |
f9a0a5e08a | ||
![]() |
d8ae275df2 | ||
![]() |
fce879be5d | ||
![]() |
0b58e22714 | ||
![]() |
dd230c4dfc | ||
![]() |
fba0b001c0 | ||
![]() |
6978c7b992 | ||
![]() |
7355e76e1b | ||
![]() |
5147cf21a0 | ||
![]() |
e8dd7d710d | ||
![]() |
fc9114eaa5 | ||
![]() |
20b7104c41 | ||
![]() |
570616951a | ||
![]() |
e075bfac55 | ||
![]() |
b6411a555c | ||
![]() |
d395475727 | ||
![]() |
8ae0a16b8a | ||
![]() |
38fcdfc97a | ||
![]() |
a0fb07cf99 | ||
![]() |
3747f9a5d5 | ||
![]() |
3a738f8f62 | ||
![]() |
ca56363438 | ||
![]() |
e06cb05e2a | ||
![]() |
8ab2dd7900 | ||
![]() |
26116d313a | ||
![]() |
738f177d6c | ||
![]() |
62c4579bbd | ||
![]() |
18d84685ec | ||
![]() |
c05a7a021c | ||
![]() |
a9935eba62 | ||
![]() |
e3d80bf809 | ||
![]() |
a59a1d3f30 | ||
![]() |
37ed75a3e8 | ||
![]() |
cd4b600ba2 | ||
![]() |
fcd6dbcfbd | ||
![]() |
f3ab15bd74 | ||
![]() |
cddaad0f29 | ||
![]() |
ecb597e0a0 | ||
![]() |
ec2a934f73 | ||
![]() |
e1d630fdee | ||
![]() |
5807672503 | ||
![]() |
2fadb314d3 | ||
![]() |
ec5c15ff64 | ||
![]() |
c169a7613f | ||
![]() |
0f762968ae | ||
![]() |
8e6517e7d8 | ||
![]() |
6946bff7f9 | ||
![]() |
37dcedb99b | ||
![]() |
afb92b0e4e | ||
![]() |
7c39dff34f | ||
![]() |
e41c122d2d | ||
![]() |
117c5eaf67 | ||
![]() |
10fab2b47f | ||
![]() |
3f71df3d29 | ||
![]() |
813e211200 | ||
![]() |
1adb130fba | ||
![]() |
f69d4027db | ||
![]() |
e0d006ec69 | ||
![]() |
81d8af57ed | ||
![]() |
42c48a8e37 | ||
![]() |
3b1349b643 | ||
![]() |
0250ea329c | ||
![]() |
b722c64ad8 | ||
![]() |
effa3b567e | ||
![]() |
64f198d599 | ||
![]() |
131b2defbb | ||
![]() |
df5aab67ac | ||
![]() |
fdc030904c | ||
![]() |
2a4fae8de4 | ||
![]() |
662b065116 | ||
![]() |
300938fa44 | ||
![]() |
086e27876d | ||
![]() |
247c62bfb4 | ||
![]() |
a13315c324 | ||
![]() |
65f40ef23a | ||
![]() |
96a611ff78 | ||
![]() |
0f4119b74f | ||
![]() |
6847329093 | ||
![]() |
9d2bcf70c7 | ||
![]() |
aa33709f04 | ||
![]() |
eacaedaf3d | ||
![]() |
f9c428cfcd | ||
![]() |
aa1ede46d2 | ||
![]() |
3c43244631 | ||
![]() |
b468a6f19b | ||
![]() |
cfdc750ac0 | ||
![]() |
6f8b006227 | ||
![]() |
3f4bf986f3 | ||
![]() |
bef1033e12 | ||
![]() |
13061d60a4 | ||
![]() |
5c6917a7e6 | ||
![]() |
2ec15cfbbc | ||
![]() |
1325a8dc65 | ||
![]() |
b5d8fcf25b | ||
![]() |
c22ff0678e | ||
![]() |
07051b813a | ||
![]() |
5c22af6576 | ||
![]() |
c3e1298ea3 | ||
![]() |
949b616fdd | ||
![]() |
2b1d95e2ef | ||
![]() |
3d967da110 | ||
![]() |
66fde32b64 | ||
![]() |
80a89a5ac0 | ||
![]() |
c59e038c2a | ||
![]() |
844bd8fd6e | ||
![]() |
7d9ebb5b0b | ||
![]() |
7fd7444dbf | ||
![]() |
13af6cce22 | ||
![]() |
458dbec5fd | ||
![]() |
2137d6d30b | ||
![]() |
b28de0c119 | ||
![]() |
0fd4695b7c | ||
![]() |
74dddc4da4 | ||
![]() |
8bff987d30 | ||
![]() |
de8684bafc | ||
![]() |
905f559aa9 | ||
![]() |
c7f57c0b15 | ||
![]() |
0f0f46f425 | ||
![]() |
d6a3c8b24c | ||
![]() |
8c661ca1ae | ||
![]() |
f579c8754f | ||
![]() |
5c17536683 | ||
![]() |
8536353c26 | ||
![]() |
84375c0201 | ||
![]() |
9c0c187a18 | ||
![]() |
8ae735e5c0 | ||
![]() |
8224dda3fd | ||
![]() |
c852d7474e | ||
![]() |
71685d2052 | ||
![]() |
e57e513ca1 | ||
![]() |
aa4fb14540 | ||
![]() |
5f74abc944 | ||
![]() |
c4135389a4 | ||
![]() |
a6e0834722 | ||
![]() |
bc628b9c00 | ||
![]() |
9b2669a8b8 | ||
![]() |
a0f70f7677 | ||
![]() |
23b2c912e2 | ||
![]() |
ecfd4180c0 | ||
![]() |
42489ba6b2 | ||
![]() |
61207f893d | ||
![]() |
4e32359718 | ||
![]() |
8d4af48eca | ||
![]() |
693f63534d | ||
![]() |
b057e848d0 | ||
![]() |
0114224d1f | ||
![]() |
beab2be713 | ||
![]() |
edd4a1ff4b | ||
![]() |
85814b7544 | ||
![]() |
d46fbd66f0 | ||
![]() |
06bd9c80e8 | ||
![]() |
54b8628435 | ||
![]() |
b37a548771 | ||
![]() |
a14689acff | ||
![]() |
a73bc956bf | ||
![]() |
d595a768b8 | ||
![]() |
0fd6421fae | ||
![]() |
6e9a36461a | ||
![]() |
d115f54812 | ||
![]() |
f627f661f2 | ||
![]() |
0e7ec3dfb3 | ||
![]() |
0188bd34a9 | ||
![]() |
a2becfa6e2 | ||
![]() |
ea32af9b91 | ||
![]() |
c74c26e4c6 | ||
![]() |
382e21225b | ||
![]() |
81c406cbf6 | ||
![]() |
d9eb46d65c | ||
![]() |
dadfed20f1 | ||
![]() |
6dad29a772 | ||
![]() |
884253fe29 | ||
![]() |
a5eccbdc2b | ||
![]() |
d0318e3e83 | ||
![]() |
d1c308f118 | ||
![]() |
3871170e44 | ||
![]() |
95dd5c4a7c | ||
![]() |
0bff4b55a5 | ||
![]() |
a2022415c2 | ||
![]() |
2b8bd8144f | ||
![]() |
7bf520ac8c | ||
![]() |
ad8983e889 | ||
![]() |
d0b62af32e | ||
![]() |
bc8e259974 | ||
![]() |
ff0a4661fd | ||
![]() |
9151df6816 | ||
![]() |
9c0878408b | ||
![]() |
61baa53076 | ||
![]() |
b2841ee9ab | ||
![]() |
9edea17fb7 | ||
![]() |
ac17618f0c | ||
![]() |
e94ed4eafa | ||
![]() |
8c33a5e62f | ||
![]() |
f9f1017e5b | ||
![]() |
5d2d831b9e | ||
![]() |
562d9a0f4a | ||
![]() |
b981f9199b | ||
![]() |
efef0f3734 | ||
![]() |
cd0b860210 | ||
![]() |
9cb0655cfa | ||
![]() |
3775f28af7 | ||
![]() |
c33b824871 | ||
![]() |
cf396b739e | ||
![]() |
631963f43c | ||
![]() |
06cedb4f41 | ||
![]() |
7a0c60a164 | ||
![]() |
4c038ad932 | ||
![]() |
f6dd38685a | ||
![]() |
2eab0f0567 |
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Dot directories
|
||||||
|
.gradle/
|
||||||
|
.idea/
|
||||||
|
.git/
|
||||||
|
|
||||||
|
# Build directories
|
||||||
|
build/
|
||||||
|
**/build/
|
||||||
|
|
||||||
|
# We execute COPY . .
|
||||||
|
# Modifying these files would unnecessarily invalidate the build context
|
||||||
|
Dockerfile
|
2
.gitignore
vendored
@@ -4,3 +4,5 @@
|
|||||||
.gradle
|
.gradle
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
|
**/*.rej
|
||||||
|
**/*.orig
|
||||||
|
9
.tx/config
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
lang_map = he: iw, id: in, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, yi: ji, zh_CN: zh
|
||||||
|
|
||||||
|
[I2P.MuWire]
|
||||||
|
file_filter = webui/locale/messages_<lang>.po
|
||||||
|
source_file = webui/locale/messages_en.po
|
||||||
|
source_lang = en
|
||||||
|
minimum_perc = 10
|
64
Dockerfile
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
FROM jlesage/baseimage-gui:alpine-3.10-glibc
|
||||||
|
|
||||||
|
# Docker image version is provided via build arg.
|
||||||
|
ARG DOCKER_IMAGE_VERSION=unknown
|
||||||
|
|
||||||
|
# JDK version
|
||||||
|
ARG JDK=11
|
||||||
|
|
||||||
|
# Important directories
|
||||||
|
ARG TMP_DIR=/muwire-tmp
|
||||||
|
ENV APP_HOME=/muwire
|
||||||
|
|
||||||
|
# Define working directory.
|
||||||
|
WORKDIR $TMP_DIR
|
||||||
|
|
||||||
|
# Put sources into dir
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Install final dependencies
|
||||||
|
RUN add-pkg openjdk${JDK}-jre
|
||||||
|
|
||||||
|
# Build and untar in future distribution dir
|
||||||
|
RUN add-pkg --virtual openjdk${JDK}-jdk \
|
||||||
|
&& ./gradlew --no-daemon clean assemble \
|
||||||
|
&& mkdir -p ${APP_HOME} \
|
||||||
|
# Extract to ${APP_HOME and ignore the first dir
|
||||||
|
# First dir in tar is the "MuWire-<version>"
|
||||||
|
&& tar -C ${APP_HOME} --strip 1 -xvf gui/build/distributions/MuWire*.tar \
|
||||||
|
# Cleanup
|
||||||
|
&& rm -rf "${TMP_DIR}" /root/.gradle /root/.java \
|
||||||
|
&& del-pkg openjdk${JDK}-jdk
|
||||||
|
|
||||||
|
WORKDIR ${APP_HOME}
|
||||||
|
|
||||||
|
# Maximize only the main/initial window.
|
||||||
|
RUN \
|
||||||
|
sed-patch 's/<application type="normal">/<application type="normal" title="MuWire">/' \
|
||||||
|
/etc/xdg/openbox/rc.xml
|
||||||
|
|
||||||
|
# Generate and install favicons.
|
||||||
|
RUN \
|
||||||
|
APP_ICON_URL=https://github.com/zlatinb/muwire/raw/master/gui/griffon-app/resources/MuWire-128x128.png && \
|
||||||
|
install_app_icon.sh "$APP_ICON_URL"
|
||||||
|
|
||||||
|
# Add files.
|
||||||
|
COPY docker/rootfs/ /
|
||||||
|
|
||||||
|
# Set environment variables.
|
||||||
|
ENV APP_NAME="MuWire" \
|
||||||
|
S6_KILL_GRACETIME=8000
|
||||||
|
|
||||||
|
# Define mountable directories.
|
||||||
|
VOLUME ["$APP_HOME/.MuWire"]
|
||||||
|
VOLUME ["/incompletes"]
|
||||||
|
VOLUME ["/output"]
|
||||||
|
|
||||||
|
|
||||||
|
# Metadata.
|
||||||
|
LABEL \
|
||||||
|
org.label-schema.name="muwire" \
|
||||||
|
org.label-schema.description="Docker container for MuWire" \
|
||||||
|
org.label-schema.version="$DOCKER_IMAGE_VERSION" \
|
||||||
|
org.label-schema.vcs-url="https://github.com/zlatinb/muwire" \
|
||||||
|
org.label-schema.schema-version="1.0"
|
32
README.md
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
MuWire is an easy to use file-sharing program which offers anonymity using [I2P technology](http://geti2p.net). It works on any platform Java works on, including Windows,MacOS,Linux.
|
MuWire is an easy to use file-sharing program which offers anonymity using [I2P technology](http://geti2p.net). It works on any platform Java works on, including Windows,MacOS,Linux.
|
||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
The current stable release - 0.6.8 is avaiable for download at https://muwire.com. The latest plugin build and instructions how to install the plugin are available inside I2P at http://muwire.i2p.
|
||||||
|
|
||||||
The current stable release - 0.6.6 is avaiable for download at https://muwire.com. You can find technical documentation in the [doc] folder. Also check out the [Wiki] for various documentation.
|
You can find technical documentation in the [doc] folder. Also check out the [Wiki] for various other documentation.
|
||||||
|
|
||||||
### Building
|
## Building
|
||||||
|
|
||||||
You need JDK 9 or newer. After installing that and setting up the appropriate paths, just type
|
You need JDK 9 or newer. After installing that and setting up the appropriate paths, just type
|
||||||
|
|
||||||
@@ -21,25 +21,35 @@ If you want to run the unit tests, type
|
|||||||
|
|
||||||
If you want to build binary bundles that do not depend on Java or I2P, see the [muwire-pkg] project
|
If you want to build binary bundles that do not depend on Java or I2P, see the [muwire-pkg] project
|
||||||
|
|
||||||
### Running the GUI
|
## Running the GUI
|
||||||
|
|
||||||
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar gui-x.y.z-all.jar` in a terminal or command prompt.
|
Type
|
||||||
|
```
|
||||||
|
./gradlew gui:run
|
||||||
|
```
|
||||||
|
|
||||||
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
|
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
|
||||||
|
|
||||||
[Default I2CP port]\: `7654`
|
[Default I2CP port]\: `7654`
|
||||||
|
|
||||||
### Running the CLI
|
## Running the CLI
|
||||||
|
|
||||||
Look inside `cli-lanterna/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar cli-lanterna-x.y.z-all.jar` in a terminal. The CLI will ask you about the router host and port on startup, no need to edit any files. However, the CLI does not have an options window yet, so if you need to change any options you will need to edit the configuration files. The CLI options are documented here [cli options]
|
Look inside `cli-lanterna/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar cli-lanterna-x.y.z-all.jar` in a terminal. The CLI will ask you about the router host and port on startup, no need to edit any files. However, the CLI does not have an options window yet, so if you need to change any options you will need to edit the configuration files. The CLI options are documented here [cli options]
|
||||||
|
|
||||||
The CLI is under active development and doesn't have all the features of the GUI.
|
The CLI is under active development and doesn't have all the features of the GUI.
|
||||||
|
|
||||||
### Web UI
|
## Running the Web UI / Plugin
|
||||||
|
|
||||||
If you are a Grails/Scala/JRuby/Kotlin developer and are interested in building a Web UI for MuWire, please get in touch. The MuWire core is written in Groovy and should be easy to integrate with any JVM-based language.
|
There is a Web-based UI under development. It is intended to be run as a plugin to the Java I2P router. Instructions how to build it are available at the wiki [Plugin] page.
|
||||||
|
|
||||||
### GPG Fingerprint
|
## Docker
|
||||||
|
|
||||||
|
MuWire is available as a Docker image. For more information see the [Docker] page.
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
If you want to help translate MuWire, instructions are on the wiki https://github.com/zlatinb/muwire/wiki/Translate
|
||||||
|
|
||||||
|
## GPG Fingerprint
|
||||||
|
|
||||||
```
|
```
|
||||||
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
||||||
@@ -53,3 +63,7 @@ You can find the full key at https://keybase.io/zlatinb
|
|||||||
[doc]: https://github.com/zlatinb/muwire/tree/master/doc
|
[doc]: https://github.com/zlatinb/muwire/tree/master/doc
|
||||||
[muwire-pkg]: https://github.com/zlatinb/muwire-pkg
|
[muwire-pkg]: https://github.com/zlatinb/muwire-pkg
|
||||||
[cli options]: https://github.com/zlatinb/muwire/wiki/CLI-Configuration-Options
|
[cli options]: https://github.com/zlatinb/muwire/wiki/CLI-Configuration-Options
|
||||||
|
[I2P Github]: https://github.com/i2p/i2p.i2p
|
||||||
|
[Plugin]: https://github.com/zlatinb/muwire/wiki/Plugin
|
||||||
|
[Docker]: https://github.com/zlatinb/muwire/wiki/Docker
|
||||||
|
[jlesage/docker-baseimage-gui]: https://github.com/jlesage/docker-baseimage-gui
|
||||||
|
42
TODO.md
@@ -1,8 +1,6 @@
|
|||||||
# TODO List
|
# TODO List
|
||||||
|
|
||||||
Not in any particular order yet
|
### Network
|
||||||
|
|
||||||
### Big Items
|
|
||||||
|
|
||||||
##### Bloom Filters
|
##### Bloom Filters
|
||||||
|
|
||||||
@@ -12,15 +10,33 @@ This reduces query traffic by not sending last hop queries to peers that definit
|
|||||||
|
|
||||||
This helps with scalability
|
This helps with scalability
|
||||||
|
|
||||||
##### Web UI, REST Interface, etc.
|
### Core
|
||||||
|
|
||||||
Basically any non-gui non-cli user interface
|
* Metadata parsing and search
|
||||||
|
|
||||||
##### Metadata editing and search
|
|
||||||
|
|
||||||
To enable parsing of metadata from known file types and the user editing it or adding manual metadata
|
|
||||||
|
|
||||||
### Small Items
|
|
||||||
|
|
||||||
* Wrapper of some kind for in-place upgrades
|
|
||||||
* Automatic adjustment of number of I2P tunnels
|
* Automatic adjustment of number of I2P tunnels
|
||||||
|
* Persist trust immediately
|
||||||
|
* Check if user-selected download and incomplete locations exist and are writeable
|
||||||
|
* Enum i18n
|
||||||
|
* Ability to share trust list only with trusted users
|
||||||
|
* Confidential files visible only to certain users
|
||||||
|
* Public Feed feature
|
||||||
|
|
||||||
|
### Chat
|
||||||
|
* echo "unknown/innappropriate command" in the console
|
||||||
|
* break up lines on CR/LF, send multiple messages
|
||||||
|
* Style timestamps and persona names
|
||||||
|
* enforce # in room names or ignore it
|
||||||
|
* auto-create/join channel on server start
|
||||||
|
* jump from notification window to room with message
|
||||||
|
|
||||||
|
### Swing GUI
|
||||||
|
* I2P Status panel - display message when connected to external router
|
||||||
|
* Search box - left identation
|
||||||
|
|
||||||
|
### Web UI/Plugin
|
||||||
|
* HTML 5 media players
|
||||||
|
* Minimal dependency (break up groovy-all.jar)
|
||||||
|
* Remove versions from jar names
|
||||||
|
* Security: POST nonces, CSP headers
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2,8 +2,9 @@ subprojects {
|
|||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "net.i2p:i2p:${i2pVersion}"
|
compile 'org.codehaus.groovy:groovy:2.4.15'
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
compile 'org.codehaus.groovy:groovy-jsr223:2.4.15'
|
||||||
|
compile 'org.codehaus.groovy:groovy-json:2.4.15'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileGroovy {
|
compileGroovy {
|
||||||
|
@@ -32,7 +32,7 @@ import com.muwire.core.UILoadedEvent
|
|||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
|
||||||
class CliLanterna {
|
class CliLanterna {
|
||||||
private static final String MW_VERSION = "0.6.7"
|
private static final String MW_VERSION = "0.6.10"
|
||||||
|
|
||||||
private static volatile Core core
|
private static volatile Core core
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ package com.muwire.clilanterna
|
|||||||
import com.googlecode.lanterna.gui2.TextGUIThread
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
import com.googlecode.lanterna.gui2.table.TableModel
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
import com.muwire.core.files.DirectoryWatchedEvent
|
import com.muwire.core.files.DirectoryWatchedEvent
|
||||||
@@ -72,7 +73,7 @@ class FilesModel {
|
|||||||
sharedFiles.each {
|
sharedFiles.each {
|
||||||
long size = it.getCachedLength()
|
long size = it.getCachedLength()
|
||||||
boolean comment = it.comment != null
|
boolean comment = it.comment != null
|
||||||
boolean certified = core.certificateManager.hasLocalCertificate(it.getInfoHash())
|
boolean certified = core.certificateManager.hasLocalCertificate(new InfoHash(it.getRoot()))
|
||||||
String hits = String.valueOf(it.getHits())
|
String hits = String.valueOf(it.getHits())
|
||||||
String downloaders = String.valueOf(it.getDownloaders().size())
|
String downloaders = String.valueOf(it.getDownloaders().size())
|
||||||
model.addRow(new SharedFileWrapper(it), DataHelper.formatSize2(size, false)+"B", comment, certified, hits, downloaders)
|
model.addRow(new SharedFileWrapper(it), DataHelper.formatSize2(size, false)+"B", comment, certified, hits, downloaders)
|
||||||
|
@@ -21,7 +21,6 @@ import com.muwire.core.filecert.UICreateCertificateEvent
|
|||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
import com.muwire.core.files.FileSharedEvent
|
import com.muwire.core.files.FileSharedEvent
|
||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
import com.muwire.core.files.UIPersistFilesEvent
|
|
||||||
|
|
||||||
class FilesView extends BasicWindow {
|
class FilesView extends BasicWindow {
|
||||||
private final FilesModel model
|
private final FilesModel model
|
||||||
@@ -84,7 +83,6 @@ class FilesView extends BasicWindow {
|
|||||||
|
|
||||||
Button unshareButton = new Button("Unshare", {
|
Button unshareButton = new Button("Unshare", {
|
||||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||||
core.eventBus.publish(new UIPersistFilesEvent())
|
|
||||||
MessageDialog.showMessageDialog(textGUI, "File Unshared", "Unshared "+sf.getFile().getName(), MessageDialogButton.OK)
|
MessageDialog.showMessageDialog(textGUI, "File Unshared", "Unshared "+sf.getFile().getName(), MessageDialogButton.OK)
|
||||||
} )
|
} )
|
||||||
Button addCommentButton = new Button("Add Comment", {
|
Button addCommentButton = new Button("Add Comment", {
|
||||||
|
@@ -2,6 +2,7 @@ apply plugin : 'application'
|
|||||||
mainClassName = 'com.muwire.core.Core'
|
mainClassName = 'com.muwire.core.Core'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile "net.i2p:i2p:${i2pVersion}"
|
||||||
compile "net.i2p:router:${i2pVersion}"
|
compile "net.i2p:router:${i2pVersion}"
|
||||||
compile "net.i2p.client:mstreaming:${i2pVersion}"
|
compile "net.i2p.client:mstreaming:${i2pVersion}"
|
||||||
compile "net.i2p.client:streaming:${i2pVersion}"
|
compile "net.i2p.client:streaming:${i2pVersion}"
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
package com.muwire.core
|
package com.muwire.core
|
||||||
|
|
||||||
|
import com.muwire.core.files.PersisterDoneEvent
|
||||||
|
import com.muwire.core.files.PersisterFolderService
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
@@ -31,7 +34,6 @@ import com.muwire.core.filecert.UIFetchCertificatesEvent
|
|||||||
import com.muwire.core.filecert.UIImportCertificateEvent
|
import com.muwire.core.filecert.UIImportCertificateEvent
|
||||||
import com.muwire.core.files.FileDownloadedEvent
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
import com.muwire.core.files.FileHashedEvent
|
import com.muwire.core.files.FileHashedEvent
|
||||||
import com.muwire.core.files.FileHashingEvent
|
|
||||||
import com.muwire.core.files.FileHasher
|
import com.muwire.core.files.FileHasher
|
||||||
import com.muwire.core.files.FileLoadedEvent
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
import com.muwire.core.files.FileManager
|
import com.muwire.core.files.FileManager
|
||||||
@@ -41,7 +43,7 @@ import com.muwire.core.files.HasherService
|
|||||||
import com.muwire.core.files.PersisterService
|
import com.muwire.core.files.PersisterService
|
||||||
import com.muwire.core.files.SideCarFileEvent
|
import com.muwire.core.files.SideCarFileEvent
|
||||||
import com.muwire.core.files.UICommentEvent
|
import com.muwire.core.files.UICommentEvent
|
||||||
import com.muwire.core.files.UIPersistFilesEvent
|
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
import com.muwire.core.files.DirectoryWatchedEvent
|
import com.muwire.core.files.DirectoryWatchedEvent
|
||||||
@@ -74,10 +76,8 @@ import net.i2p.client.I2PClientFactory
|
|||||||
import net.i2p.client.I2PSession
|
import net.i2p.client.I2PSession
|
||||||
import net.i2p.client.streaming.I2PSocketManager
|
import net.i2p.client.streaming.I2PSocketManager
|
||||||
import net.i2p.client.streaming.I2PSocketManagerFactory
|
import net.i2p.client.streaming.I2PSocketManagerFactory
|
||||||
import net.i2p.client.streaming.I2PSocketOptions
|
|
||||||
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener
|
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener
|
||||||
import net.i2p.crypto.DSAEngine
|
import net.i2p.crypto.DSAEngine
|
||||||
import net.i2p.crypto.SigType
|
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
import net.i2p.data.PrivateKey
|
import net.i2p.data.PrivateKey
|
||||||
import net.i2p.data.Signature
|
import net.i2p.data.Signature
|
||||||
@@ -91,13 +91,16 @@ public class Core {
|
|||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final Persona me
|
final Persona me
|
||||||
|
final String version;
|
||||||
final File home
|
final File home
|
||||||
final Properties i2pOptions
|
final Properties i2pOptions
|
||||||
final MuWireSettings muOptions
|
final MuWireSettings muOptions
|
||||||
|
|
||||||
private final TrustService trustService
|
private final I2PSession i2pSession;
|
||||||
private final TrustSubscriber trustSubscriber
|
final TrustService trustService
|
||||||
|
final TrustSubscriber trustSubscriber
|
||||||
private final PersisterService persisterService
|
private final PersisterService persisterService
|
||||||
|
private final PersisterFolderService persisterFolderService
|
||||||
private final HostCache hostCache
|
private final HostCache hostCache
|
||||||
private final ConnectionManager connectionManager
|
private final ConnectionManager connectionManager
|
||||||
private final CacheClient cacheClient
|
private final CacheClient cacheClient
|
||||||
@@ -122,26 +125,27 @@ public class Core {
|
|||||||
|
|
||||||
public Core(MuWireSettings props, File home, String myVersion) {
|
public Core(MuWireSettings props, File home, String myVersion) {
|
||||||
this.home = home
|
this.home = home
|
||||||
|
this.version = myVersion
|
||||||
this.muOptions = props
|
this.muOptions = props
|
||||||
|
|
||||||
i2pOptions = new Properties()
|
i2pOptions = new Properties()
|
||||||
def i2pOptionsFile = new File(home,"i2p.properties")
|
// Read defaults
|
||||||
|
def defaultI2PFile = getClass()
|
||||||
|
.getClassLoader().getResource("defaults/i2p.properties");
|
||||||
|
defaultI2PFile.withInputStream { i2pOptions.load(it) }
|
||||||
|
|
||||||
|
def i2pOptionsFile = new File(home, "i2p.properties")
|
||||||
if (i2pOptionsFile.exists()) {
|
if (i2pOptionsFile.exists()) {
|
||||||
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
|
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
|
||||||
|
|
||||||
if (!i2pOptions.containsKey("inbound.nickname"))
|
if (!i2pOptions.containsKey("inbound.nickname"))
|
||||||
i2pOptions["inbound.nickname"] = "MuWire"
|
i2pOptions["inbound.nickname"] = "MuWire"
|
||||||
if (!i2pOptions.containsKey("outbound.nickname"))
|
if (!i2pOptions.containsKey("outbound.nickname"))
|
||||||
i2pOptions["outbound.nickname"] = "MuWire"
|
i2pOptions["outbound.nickname"] = "MuWire"
|
||||||
} else {
|
}
|
||||||
i2pOptions["inbound.nickname"] = "MuWire"
|
if (!(i2pOptions.hasProperty("i2np.ntcp.port")
|
||||||
i2pOptions["outbound.nickname"] = "MuWire"
|
&& i2pOptions.hasProperty("i2np.udp.port")
|
||||||
i2pOptions["inbound.length"] = "3"
|
)) {
|
||||||
i2pOptions["inbound.quantity"] = "4"
|
|
||||||
i2pOptions["outbound.length"] = "3"
|
|
||||||
i2pOptions["outbound.quantity"] = "4"
|
|
||||||
i2pOptions["i2cp.tcp.host"] = "127.0.0.1"
|
|
||||||
i2pOptions["i2cp.tcp.port"] = "7654"
|
|
||||||
Random r = new Random()
|
Random r = new Random()
|
||||||
int port = r.nextInt(60000) + 4000
|
int port = r.nextInt(60000) + 4000
|
||||||
i2pOptions["i2np.ntcp.port"] = String.valueOf(port)
|
i2pOptions["i2np.ntcp.port"] = String.valueOf(port)
|
||||||
@@ -150,15 +154,18 @@ public class Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!props.embeddedRouter) {
|
if (!props.embeddedRouter) {
|
||||||
log.info "Initializing I2P context"
|
if (!(I2PAppContext.getGlobalContext() instanceof RouterContext)) {
|
||||||
I2PAppContext.getGlobalContext().logManager()
|
log.info "Initializing I2P context"
|
||||||
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
|
I2PAppContext.getGlobalContext().logManager()
|
||||||
router = null
|
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
|
||||||
|
router = null
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("launching embedded router")
|
log.info("launching embedded router")
|
||||||
Properties routerProps = new Properties()
|
Properties routerProps = new Properties()
|
||||||
routerProps.setProperty("i2p.dir.base", home.getAbsolutePath())
|
routerProps.setProperty("i2p.dir.base", home.getAbsolutePath())
|
||||||
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
|
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
|
||||||
|
routerProps.setProperty("geoip.dir", home.getAbsolutePath() + File.separator + "geoip")
|
||||||
routerProps.setProperty("router.excludePeerCaps", "KLM")
|
routerProps.setProperty("router.excludePeerCaps", "KLM")
|
||||||
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
||||||
routerProps.setProperty("i2np.outboundKBytesPerSecond", String.valueOf(props.outBw))
|
routerProps.setProperty("i2np.outboundKBytesPerSecond", String.valueOf(props.outBw))
|
||||||
@@ -185,7 +192,6 @@ public class Core {
|
|||||||
|
|
||||||
|
|
||||||
// options like tunnel length and quantity
|
// options like tunnel length and quantity
|
||||||
I2PSession i2pSession
|
|
||||||
I2PSocketManager socketManager
|
I2PSocketManager socketManager
|
||||||
keyDat.withInputStream {
|
keyDat.withInputStream {
|
||||||
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
||||||
@@ -254,7 +260,15 @@ public class Core {
|
|||||||
log.info "initializing persistence service"
|
log.info "initializing persistence service"
|
||||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
||||||
eventBus.register(UILoadedEvent.class, persisterService)
|
eventBus.register(UILoadedEvent.class, persisterService)
|
||||||
eventBus.register(UIPersistFilesEvent.class, persisterService)
|
|
||||||
|
log.info "initializing folder persistence service"
|
||||||
|
persisterFolderService = new PersisterFolderService(this, new File(home, "files"), eventBus)
|
||||||
|
eventBus.register(PersisterDoneEvent.class, persisterFolderService)
|
||||||
|
eventBus.register(FileDownloadedEvent.class, persisterFolderService)
|
||||||
|
eventBus.register(FileLoadedEvent.class, persisterFolderService)
|
||||||
|
eventBus.register(FileHashedEvent.class, persisterFolderService)
|
||||||
|
eventBus.register(FileUnsharedEvent.class, persisterFolderService)
|
||||||
|
eventBus.register(UICommentEvent.class, persisterFolderService)
|
||||||
|
|
||||||
log.info("initializing host cache")
|
log.info("initializing host cache")
|
||||||
File hostStorage = new File(home, "hosts.json")
|
File hostStorage = new File(home, "hosts.json")
|
||||||
@@ -274,10 +288,13 @@ public class Core {
|
|||||||
log.info("initializing cache client")
|
log.info("initializing cache client")
|
||||||
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
||||||
|
|
||||||
|
if (!props.plugin) {
|
||||||
log.info("initializing update client")
|
log.info("initializing update client")
|
||||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me, spk)
|
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me, spk)
|
||||||
eventBus.register(FileDownloadedEvent.class, updateClient)
|
eventBus.register(FileDownloadedEvent.class, updateClient)
|
||||||
eventBus.register(UIResultBatchEvent.class, updateClient)
|
eventBus.register(UIResultBatchEvent.class, updateClient)
|
||||||
|
} else
|
||||||
|
log.info("running as plugin, not initializing update client")
|
||||||
|
|
||||||
log.info("initializing connector")
|
log.info("initializing connector")
|
||||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||||
@@ -313,7 +330,7 @@ public class Core {
|
|||||||
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
||||||
|
|
||||||
log.info("initializing upload manager")
|
log.info("initializing upload manager")
|
||||||
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager, props)
|
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager, persisterFolderService, props)
|
||||||
|
|
||||||
log.info("initializing connection establisher")
|
log.info("initializing connection establisher")
|
||||||
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
||||||
@@ -372,7 +389,7 @@ public class Core {
|
|||||||
connectionAcceptor.start()
|
connectionAcceptor.start()
|
||||||
connectionEstablisher.start()
|
connectionEstablisher.start()
|
||||||
hostCache.waitForLoad()
|
hostCache.waitForLoad()
|
||||||
updateClient.start()
|
updateClient?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
@@ -382,8 +399,16 @@ public class Core {
|
|||||||
}
|
}
|
||||||
log.info("saving settings")
|
log.info("saving settings")
|
||||||
saveMuSettings()
|
saveMuSettings()
|
||||||
|
log.info("shutting down host cache")
|
||||||
|
hostCache.stop()
|
||||||
log.info("shutting down trust subscriber")
|
log.info("shutting down trust subscriber")
|
||||||
trustSubscriber.stop()
|
trustSubscriber.stop()
|
||||||
|
log.info("shutting down trust service")
|
||||||
|
trustService.stop()
|
||||||
|
log.info("shutting down persister service")
|
||||||
|
persisterService.stop()
|
||||||
|
log.info("shutting down persisterFolder service")
|
||||||
|
persisterFolderService.stop()
|
||||||
log.info("shutting down download manager")
|
log.info("shutting down download manager")
|
||||||
downloadManager.shutdown()
|
downloadManager.shutdown()
|
||||||
log.info("shutting down connection acceptor")
|
log.info("shutting down connection acceptor")
|
||||||
@@ -400,10 +425,14 @@ public class Core {
|
|||||||
chatManager.shutdown()
|
chatManager.shutdown()
|
||||||
log.info("shutting down connection manager")
|
log.info("shutting down connection manager")
|
||||||
connectionManager.shutdown()
|
connectionManager.shutdown()
|
||||||
|
log.info("killing i2p session")
|
||||||
|
i2pSession.destroySession()
|
||||||
if (router != null) {
|
if (router != null) {
|
||||||
log.info("shutting down embedded router")
|
log.info("shutting down embedded router")
|
||||||
router.shutdown(0)
|
router.shutdown(0)
|
||||||
}
|
}
|
||||||
|
log.info("shutting down event bus");
|
||||||
|
eventBus.shutdown()
|
||||||
log.info("shutdown complete")
|
log.info("shutdown complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,6 +440,11 @@ public class Core {
|
|||||||
File f = new File(home, "MuWire.properties")
|
File f = new File(home, "MuWire.properties")
|
||||||
f.withPrintWriter("UTF-8", { muOptions.write(it) })
|
f.withPrintWriter("UTF-8", { muOptions.write(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void saveI2PSettings() {
|
||||||
|
File f = new File(home, "i2p.properties")
|
||||||
|
f.withOutputStream { i2pOptions.store(it, "I2P Options") }
|
||||||
|
}
|
||||||
|
|
||||||
static main(args) {
|
static main(args) {
|
||||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||||
@@ -436,7 +470,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.6.7")
|
Core core = new Core(props, home, "0.6.10")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -2,6 +2,7 @@ package com.muwire.core
|
|||||||
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ import groovy.util.logging.Log
|
|||||||
class EventBus {
|
class EventBus {
|
||||||
|
|
||||||
private Map handlers = new HashMap()
|
private Map handlers = new HashMap()
|
||||||
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
private final ExecutorService executor = Executors.newSingleThreadExecutor {r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("event-bus")
|
rv.setName("event-bus")
|
||||||
@@ -53,4 +54,8 @@ class EventBus {
|
|||||||
log.info("Unregistering $handler for type $eventType")
|
log.info("Unregistering $handler for type $eventType")
|
||||||
handlers[eventType]?.remove(handler)
|
handlers[eventType]?.remove(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void shutdown() {
|
||||||
|
executor.shutdownNow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -41,6 +41,7 @@ class MuWireSettings {
|
|||||||
int meshExpiration
|
int meshExpiration
|
||||||
int speedSmoothSeconds
|
int speedSmoothSeconds
|
||||||
boolean embeddedRouter
|
boolean embeddedRouter
|
||||||
|
boolean plugin
|
||||||
int inBw, outBw
|
int inBw, outBw
|
||||||
Set<String> watchedKeywords
|
Set<String> watchedKeywords
|
||||||
Set<String> watchedRegexes
|
Set<String> watchedRegexes
|
||||||
@@ -76,6 +77,7 @@ class MuWireSettings {
|
|||||||
hostRejectInterval = Integer.valueOf(props.getProperty("hostRejectInterval", "1"))
|
hostRejectInterval = Integer.valueOf(props.getProperty("hostRejectInterval", "1"))
|
||||||
meshExpiration = Integer.valueOf(props.getProperty("meshExpiration","60"))
|
meshExpiration = Integer.valueOf(props.getProperty("meshExpiration","60"))
|
||||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||||
|
plugin = Boolean.valueOf(props.getProperty("plugin","false"))
|
||||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||||
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
||||||
@@ -130,6 +132,7 @@ class MuWireSettings {
|
|||||||
props.setProperty("hostRejectInterval", String.valueOf(hostRejectInterval))
|
props.setProperty("hostRejectInterval", String.valueOf(hostRejectInterval))
|
||||||
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
|
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
|
||||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||||
|
props.setProperty("plugin", String.valueOf(plugin))
|
||||||
props.setProperty("inBw", String.valueOf(inBw))
|
props.setProperty("inBw", String.valueOf(inBw))
|
||||||
props.setProperty("outBw", String.valueOf(outBw))
|
props.setProperty("outBw", String.valueOf(outBw))
|
||||||
props.setProperty("searchComments", String.valueOf(searchComments))
|
props.setProperty("searchComments", String.valueOf(searchComments))
|
||||||
|
@@ -255,7 +255,6 @@ abstract class Connection implements Closeable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this mandatory at some point
|
|
||||||
byte[] sig2 = null
|
byte[] sig2 = null
|
||||||
long queryTime = 0
|
long queryTime = 0
|
||||||
if (search.sig2 != null) {
|
if (search.sig2 != null) {
|
||||||
@@ -278,8 +277,10 @@ abstract class Connection implements Closeable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
log.info("no extended signature in query")
|
log.info("no extended signature in query")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||||
searchHash : infohash,
|
searchHash : infohash,
|
||||||
|
@@ -369,13 +369,18 @@ class ConnectionAcceptor {
|
|||||||
|
|
||||||
def sharedFiles = fileManager.getSharedFiles().values()
|
def sharedFiles = fileManager.getSharedFiles().values()
|
||||||
|
|
||||||
os.write("Count: ${sharedFiles.size()}\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("Count: ${sharedFiles.size()}\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
|
boolean chat = chatServer.running.get() && settings.advertiseChat
|
||||||
|
os.write("Chat: ${chat}\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
|
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
||||||
JsonOutput jsonOutput = new JsonOutput()
|
JsonOutput jsonOutput = new JsonOutput()
|
||||||
sharedFiles.each {
|
sharedFiles.each {
|
||||||
it.hit(browser, System.currentTimeMillis(), "Browse Host");
|
it.hit(browser, System.currentTimeMillis(), "Browse Host");
|
||||||
int certificates = certificateManager.getByInfoHash(it.getInfoHash()).size()
|
int certificates = certificateManager.getByInfoHash(new InfoHash(it.getRoot())).size()
|
||||||
def obj = ResultsSender.sharedFileToObj(it, false, certificates)
|
def obj = ResultsSender.sharedFileToObj(it, false, certificates)
|
||||||
def json = jsonOutput.toJson(obj)
|
def json = jsonOutput.toJson(obj)
|
||||||
dos.writeShort((short)json.length())
|
dos.writeShort((short)json.length())
|
||||||
|
@@ -92,6 +92,22 @@ public class Downloader {
|
|||||||
public synchronized InfoHash getInfoHash() {
|
public synchronized InfoHash getInfoHash() {
|
||||||
infoHash
|
infoHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
file
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNPieces() {
|
||||||
|
nPieces
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPieceSize() {
|
||||||
|
pieceSize
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLength() {
|
||||||
|
length
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void setInfoHash(InfoHash infoHash) {
|
private synchronized void setInfoHash(InfoHash infoHash) {
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
@@ -249,6 +265,10 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
active
|
active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTotalWorkers() {
|
||||||
|
return activeWorkers.size();
|
||||||
|
}
|
||||||
|
|
||||||
public void resume() {
|
public void resume() {
|
||||||
paused = false
|
paused = false
|
||||||
@@ -385,8 +405,9 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
eventBus.publish(
|
eventBus.publish(
|
||||||
new FileDownloadedEvent(
|
new FileDownloadedEvent(
|
||||||
downloadedFile : new DownloadedFile(file.getCanonicalFile(), getInfoHash(), pieceSizePow2, successfulDestinations),
|
downloadedFile : new DownloadedFile(file.getCanonicalFile(), getInfoHash().getRoot(), pieceSizePow2, successfulDestinations),
|
||||||
downloader : Downloader.this))
|
downloader : Downloader.this,
|
||||||
|
infoHash: getInfoHash()))
|
||||||
|
|
||||||
}
|
}
|
||||||
endpoint?.close()
|
endpoint?.close()
|
||||||
|
@@ -10,17 +10,20 @@ import net.i2p.crypto.DSAEngine
|
|||||||
import net.i2p.data.Signature
|
import net.i2p.data.Signature
|
||||||
import net.i2p.data.SigningPrivateKey
|
import net.i2p.data.SigningPrivateKey
|
||||||
import net.i2p.data.SigningPublicKey
|
import net.i2p.data.SigningPublicKey
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
class Certificate {
|
class Certificate {
|
||||||
private final byte version
|
private final byte version
|
||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final Name name, comment
|
final Name name, comment
|
||||||
private final long timestamp
|
final long timestamp
|
||||||
private final Persona issuer
|
final Persona issuer
|
||||||
private final byte[] sig
|
private final byte[] sig
|
||||||
|
|
||||||
private volatile byte [] payload
|
private volatile byte [] payload
|
||||||
|
|
||||||
|
private String base64;
|
||||||
|
|
||||||
Certificate(InputStream is) {
|
Certificate(InputStream is) {
|
||||||
version = (byte) (is.read() & 0xFF)
|
version = (byte) (is.read() & 0xFF)
|
||||||
if (version > Constants.FILE_CERT_VERSION)
|
if (version > Constants.FILE_CERT_VERSION)
|
||||||
@@ -131,6 +134,15 @@ class Certificate {
|
|||||||
os.write(payload)
|
os.write(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toBase64() {
|
||||||
|
if (base64 == null) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
|
write(baos)
|
||||||
|
base64 = Base64.encode(baos.toByteArray())
|
||||||
|
}
|
||||||
|
return base64;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
version.hashCode() ^ infoHash.hashCode() ^ timestamp.hashCode() ^ name.hashCode() ^ issuer.hashCode() ^ Objects.hashCode(comment)
|
version.hashCode() ^ infoHash.hashCode() ^ timestamp.hashCode() ^ name.hashCode() ^ issuer.hashCode() ^ Objects.hashCode(comment)
|
||||||
|
@@ -32,7 +32,8 @@ class CertificateClient {
|
|||||||
fetcherThread.execute({
|
fetcherThread.execute({
|
||||||
Endpoint endpoint = null
|
Endpoint endpoint = null
|
||||||
try {
|
try {
|
||||||
eventBus.publish(new CertificateFetchEvent(status : CertificateFetchStatus.CONNECTING))
|
eventBus.publish(new CertificateFetchEvent(status : CertificateFetchStatus.CONNECTING,
|
||||||
|
user : e.host, infoHash : e.infoHash))
|
||||||
endpoint = connector.connect(e.host.destination)
|
endpoint = connector.connect(e.host.destination)
|
||||||
|
|
||||||
String infoHashString = Base64.encode(e.infoHash.getRoot())
|
String infoHashString = Base64.encode(e.infoHash.getRoot())
|
||||||
@@ -62,7 +63,8 @@ class CertificateClient {
|
|||||||
int count = Integer.parseInt(headers['Count'])
|
int count = Integer.parseInt(headers['Count'])
|
||||||
|
|
||||||
// start pulling the certs
|
// start pulling the certs
|
||||||
eventBus.publish(new CertificateFetchEvent(status : CertificateFetchStatus.FETCHING, count : count))
|
eventBus.publish(new CertificateFetchEvent(status : CertificateFetchStatus.FETCHING, count : count,
|
||||||
|
user : e.host, infoHash : e.infoHash))
|
||||||
|
|
||||||
DataInputStream dis = new DataInputStream(is)
|
DataInputStream dis = new DataInputStream(is)
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
@@ -77,11 +79,14 @@ class CertificateClient {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (cert.infoHash == e.infoHash)
|
if (cert.infoHash == e.infoHash)
|
||||||
eventBus.publish(new CertificateFetchedEvent(certificate : cert))
|
eventBus.publish(new CertificateFetchedEvent(certificate : cert, user : e.host, infoHash : e.infoHash))
|
||||||
}
|
}
|
||||||
|
eventBus.publish(new CertificateFetchEvent(status : CertificateFetchStatus.DONE, count : count,
|
||||||
|
user : e.host, infoHash : e.infoHash))
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.WARNING,"Fetching certificates failed", bad)
|
log.log(Level.WARNING,"Fetching certificates failed", bad)
|
||||||
eventBus.publish(new CertificateFetchEvent(status : CertificateFetchStatus.FAILED))
|
eventBus.publish(new CertificateFetchEvent(status : CertificateFetchStatus.FAILED,
|
||||||
|
user : e.host, infoHash : e.infoHash))
|
||||||
} finally {
|
} finally {
|
||||||
endpoint?.close()
|
endpoint?.close()
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
package com.muwire.core.filecert
|
package com.muwire.core.filecert
|
||||||
|
|
||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
class CertificateFetchEvent extends Event {
|
class CertificateFetchEvent extends Event {
|
||||||
CertificateFetchStatus status
|
CertificateFetchStatus status
|
||||||
int count
|
int count
|
||||||
|
Persona user
|
||||||
|
InfoHash infoHash
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
package com.muwire.core.filecert
|
package com.muwire.core.filecert
|
||||||
|
|
||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
class CertificateFetchedEvent extends Event {
|
class CertificateFetchedEvent extends Event {
|
||||||
Certificate certificate
|
Certificate certificate
|
||||||
|
Persona user
|
||||||
|
InfoHash infoHash
|
||||||
}
|
}
|
||||||
|
@@ -70,7 +70,7 @@ class CertificateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onUICreateCertificateEvent(UICreateCertificateEvent e) {
|
void onUICreateCertificateEvent(UICreateCertificateEvent e) {
|
||||||
InfoHash infoHash = e.sharedFile.getInfoHash()
|
InfoHash infoHash = new InfoHash(e.sharedFile.getRoot())
|
||||||
String name = e.sharedFile.getFile().getName()
|
String name = e.sharedFile.getFile().getName()
|
||||||
long timestamp = System.currentTimeMillis()
|
long timestamp = System.currentTimeMillis()
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ class CertificateManager {
|
|||||||
added
|
added
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasLocalCertificate(InfoHash infoHash) {
|
public boolean hasLocalCertificate(InfoHash infoHash) {
|
||||||
if (!byInfoHash.containsKey(infoHash))
|
if (!byInfoHash.containsKey(infoHash))
|
||||||
return false
|
return false
|
||||||
Set<Certificate> set = byInfoHash.get(infoHash)
|
Set<Certificate> set = byInfoHash.get(infoHash)
|
||||||
@@ -130,6 +130,13 @@ class CertificateManager {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isImported(Certificate certificate) {
|
||||||
|
Set<Certificate> forInfoHash = byInfoHash.get(certificate.infoHash)
|
||||||
|
if (forInfoHash == null)
|
||||||
|
return false
|
||||||
|
forInfoHash.contains(certificate)
|
||||||
|
}
|
||||||
|
|
||||||
Set<Certificate> getByInfoHash(InfoHash infoHash) {
|
Set<Certificate> getByInfoHash(InfoHash infoHash) {
|
||||||
Set<Certificate> rv = new HashSet<>()
|
Set<Certificate> rv = new HashSet<>()
|
||||||
if (byInfoHash.containsKey(infoHash))
|
if (byInfoHash.containsKey(infoHash))
|
||||||
|
@@ -0,0 +1,152 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.DownloadedFile
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.Service
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
abstract class BasePersisterService extends Service{
|
||||||
|
|
||||||
|
protected static FileLoadedEvent fromJson(def json) {
|
||||||
|
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
||||||
|
throw new IllegalArgumentException()
|
||||||
|
if (!(json.hashList instanceof List))
|
||||||
|
throw new IllegalArgumentException()
|
||||||
|
|
||||||
|
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||||
|
file = file.getCanonicalFile()
|
||||||
|
if (!file.exists() || file.isDirectory())
|
||||||
|
return null
|
||||||
|
long length = Long.valueOf(json.length)
|
||||||
|
if (length != file.length())
|
||||||
|
return null
|
||||||
|
|
||||||
|
List hashList = (List) json.hashList
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
|
hashList.each {
|
||||||
|
byte [] hash = Base64.decode it.toString()
|
||||||
|
if (hash == null)
|
||||||
|
throw new IllegalArgumentException()
|
||||||
|
baos.write hash
|
||||||
|
}
|
||||||
|
byte[] hashListBytes = baos.toByteArray()
|
||||||
|
|
||||||
|
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
||||||
|
byte [] root = Base64.decode(json.infoHash.toString())
|
||||||
|
if (root == null)
|
||||||
|
throw new IllegalArgumentException()
|
||||||
|
if (!Arrays.equals(root, ih.getRoot()))
|
||||||
|
return null
|
||||||
|
|
||||||
|
int pieceSize = 0
|
||||||
|
if (json.pieceSize != null)
|
||||||
|
pieceSize = json.pieceSize
|
||||||
|
|
||||||
|
if (json.sources != null) {
|
||||||
|
List sources = (List)json.sources
|
||||||
|
Set<Destination> sourceSet = sources.stream().map({ d -> new Destination(d.toString())}).collect Collectors.toSet()
|
||||||
|
DownloadedFile df = new DownloadedFile(file, ih.getRoot(), pieceSize, sourceSet)
|
||||||
|
df.setComment(json.comment)
|
||||||
|
return new FileLoadedEvent(loadedFile : df, infoHash: ih)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SharedFile sf = new SharedFile(file, ih.getRoot(), pieceSize)
|
||||||
|
sf.setComment(json.comment)
|
||||||
|
if (json.downloaders != null)
|
||||||
|
sf.getDownloaders().addAll(json.downloaders)
|
||||||
|
if (json.searchers != null) {
|
||||||
|
json.searchers.each {
|
||||||
|
Persona searcher = null
|
||||||
|
if (it.searcher != null)
|
||||||
|
searcher = new Persona(new ByteArrayInputStream(Base64.decode(it.searcher)))
|
||||||
|
long timestamp = it.timestamp
|
||||||
|
String query = it.query
|
||||||
|
sf.hit(searcher, timestamp, query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FileLoadedEvent(loadedFile: sf, infoHash: ih)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static FileLoadedEvent fromJsonLite(json) {
|
||||||
|
if (json.file == null || json.length == null || json.root == null)
|
||||||
|
throw new IllegalArgumentException()
|
||||||
|
|
||||||
|
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||||
|
file = file.getCanonicalFile()
|
||||||
|
if (!file.exists() || file.isDirectory())
|
||||||
|
return null
|
||||||
|
long length = Long.valueOf(json.length)
|
||||||
|
if (length != file.length())
|
||||||
|
return null
|
||||||
|
|
||||||
|
byte[] root = Base64.decode(json.root)
|
||||||
|
InfoHash ih = new InfoHash(root)
|
||||||
|
|
||||||
|
int pieceSize = 0
|
||||||
|
if (json.pieceSize != null)
|
||||||
|
pieceSize = json.pieceSize
|
||||||
|
|
||||||
|
if (json.sources != null) {
|
||||||
|
List sources = (List)json.sources
|
||||||
|
Set<Destination> sourceSet = sources.stream().map({ d -> new Destination(d.toString())}).collect Collectors.toSet()
|
||||||
|
DownloadedFile df = new DownloadedFile(file, ih.getRoot(), pieceSize, sourceSet)
|
||||||
|
df.setComment(json.comment)
|
||||||
|
return new FileLoadedEvent(loadedFile : df, infoHash: ih)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SharedFile sf = new SharedFile(file, ih.getRoot(), pieceSize)
|
||||||
|
sf.setComment(json.comment)
|
||||||
|
if (json.downloaders != null)
|
||||||
|
sf.getDownloaders().addAll(json.downloaders)
|
||||||
|
if (json.searchers != null) {
|
||||||
|
json.searchers.each {
|
||||||
|
Persona searcher = null
|
||||||
|
if (it.searcher != null)
|
||||||
|
searcher = new Persona(new ByteArrayInputStream(Base64.decode(it.searcher)))
|
||||||
|
long timestamp = it.timestamp
|
||||||
|
String query = it.query
|
||||||
|
sf.hit(searcher, timestamp, query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FileLoadedEvent(loadedFile: sf, infoHash: ih)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static toJson(SharedFile sf) {
|
||||||
|
def json = [:]
|
||||||
|
json.file = sf.getB64EncodedFileName()
|
||||||
|
json.length = sf.getCachedLength()
|
||||||
|
json.root = Base64.encode(sf.getRoot())
|
||||||
|
json.pieceSize = sf.getPieceSize()
|
||||||
|
json.comment = sf.getComment()
|
||||||
|
json.hits = sf.getHits()
|
||||||
|
json.downloaders = sf.getDownloaders()
|
||||||
|
|
||||||
|
if (!sf.searches.isEmpty()) {
|
||||||
|
Set searchers = new HashSet<>()
|
||||||
|
sf.searches.each {
|
||||||
|
def search = [:]
|
||||||
|
if (it.searcher != null)
|
||||||
|
search.searcher = it.searcher.toBase64()
|
||||||
|
search.timestamp = it.timestamp
|
||||||
|
search.query = it.query
|
||||||
|
searchers.add(search)
|
||||||
|
}
|
||||||
|
json.searchers = searchers
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sf instanceof DownloadedFile) {
|
||||||
|
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,7 @@ package com.muwire.core.files
|
|||||||
|
|
||||||
import com.muwire.core.DownloadedFile
|
import com.muwire.core.DownloadedFile
|
||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
|
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
@@ -9,4 +10,5 @@ import net.i2p.data.Destination
|
|||||||
class FileDownloadedEvent extends Event {
|
class FileDownloadedEvent extends Event {
|
||||||
Downloader downloader
|
Downloader downloader
|
||||||
DownloadedFile downloadedFile
|
DownloadedFile downloadedFile
|
||||||
|
InfoHash infoHash
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
package com.muwire.core.files
|
package com.muwire.core.files
|
||||||
|
|
||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
class FileHashedEvent extends Event {
|
class FileHashedEvent extends Event {
|
||||||
|
|
||||||
SharedFile sharedFile
|
SharedFile sharedFile
|
||||||
|
InfoHash infoHash
|
||||||
String error
|
String error
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
package com.muwire.core.files;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public interface FileListCallback<T> {
|
||||||
|
|
||||||
|
public void onFile(File f, T value);
|
||||||
|
|
||||||
|
public void onDirectory(File f);
|
||||||
|
}
|
@@ -1,9 +1,12 @@
|
|||||||
package com.muwire.core.files
|
package com.muwire.core.files
|
||||||
|
|
||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
class FileLoadedEvent extends Event {
|
class FileLoadedEvent extends Event {
|
||||||
|
|
||||||
SharedFile loadedFile
|
SharedFile loadedFile
|
||||||
|
InfoHash infoHash
|
||||||
|
String source
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ class FileManager {
|
|||||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||||
final Map<String, Set<File>> commentToFile = new HashMap<>()
|
final Map<String, Set<File>> commentToFile = new HashMap<>()
|
||||||
final SearchIndex index = new SearchIndex()
|
final SearchIndex index = new SearchIndex()
|
||||||
final FileTree negativeTree = new FileTree()
|
final FileTree<Void> negativeTree = new FileTree<>()
|
||||||
final Set<File> sideCarFiles = new HashSet<>()
|
final Set<File> sideCarFiles = new HashSet<>()
|
||||||
|
|
||||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||||
@@ -32,7 +32,7 @@ class FileManager {
|
|||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
|
|
||||||
for (String negative : settings.negativeFileTree) {
|
for (String negative : settings.negativeFileTree) {
|
||||||
negativeTree.add(new File(negative))
|
negativeTree.add(new File(negative), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ class FileManager {
|
|||||||
|
|
||||||
private void addToIndex(SharedFile sf) {
|
private void addToIndex(SharedFile sf) {
|
||||||
log.info("Adding shared file " + sf.getFile())
|
log.info("Adding shared file " + sf.getFile())
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = new InfoHash(sf.getRoot())
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
log.info("adding new root")
|
log.info("adding new root")
|
||||||
@@ -88,7 +88,7 @@ class FileManager {
|
|||||||
negativeTree.remove(sf.file)
|
negativeTree.remove(sf.file)
|
||||||
String parent = sf.getFile().getParent()
|
String parent = sf.getFile().getParent()
|
||||||
if (parent != null && settings.watchedDirectories.contains(parent)) {
|
if (parent != null && settings.watchedDirectories.contains(parent)) {
|
||||||
negativeTree.add(sf.file.getParentFile())
|
negativeTree.add(sf.file.getParentFile(),null)
|
||||||
}
|
}
|
||||||
saveNegativeTree()
|
saveNegativeTree()
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ class FileManager {
|
|||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
SharedFile sf = e.unsharedFile
|
SharedFile sf = e.unsharedFile
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = new InfoHash(sf.getRoot())
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.remove(sf)
|
existing.remove(sf)
|
||||||
@@ -128,7 +128,7 @@ class FileManager {
|
|||||||
|
|
||||||
fileToSharedFile.remove(sf.file)
|
fileToSharedFile.remove(sf.file)
|
||||||
if (!e.deleted && negativeTree.fileToNode.containsKey(sf.file.getParentFile())) {
|
if (!e.deleted && negativeTree.fileToNode.containsKey(sf.file.getParentFile())) {
|
||||||
negativeTree.add(sf.file)
|
negativeTree.add(sf.file,null)
|
||||||
saveNegativeTree()
|
saveNegativeTree()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,12 +2,12 @@ package com.muwire.core.files
|
|||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class FileTree {
|
class FileTree<T> {
|
||||||
|
|
||||||
private final TreeNode root = new TreeNode()
|
private final TreeNode root = new TreeNode()
|
||||||
private final Map<File, TreeNode> fileToNode = new ConcurrentHashMap<>()
|
private final Map<File, TreeNode> fileToNode = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
synchronized void add(File file) {
|
synchronized void add(File file, T value) {
|
||||||
List<File> path = new ArrayList<>()
|
List<File> path = new ArrayList<>()
|
||||||
path.add(file)
|
path.add(file)
|
||||||
while (file.getParentFile() != null) {
|
while (file.getParentFile() != null) {
|
||||||
@@ -29,6 +29,7 @@ class FileTree {
|
|||||||
}
|
}
|
||||||
current = existing
|
current = existing
|
||||||
}
|
}
|
||||||
|
current.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean remove(File file) {
|
synchronized boolean remove(File file) {
|
||||||
@@ -45,13 +46,63 @@ class FileTree {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TreeNode {
|
synchronized void traverse(FileTreeCallback<T> callback) {
|
||||||
|
doTraverse(root, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void traverse(File from, FileTreeCallback<T> callback) {
|
||||||
|
if (from == null) {
|
||||||
|
doTraverse(root, callback);
|
||||||
|
} else {
|
||||||
|
TreeNode node = fileToNode.get(from);
|
||||||
|
if (node == null)
|
||||||
|
return
|
||||||
|
doTraverse(node, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTraverse(TreeNode<T> node, FileTreeCallback<T> callback) {
|
||||||
|
boolean leave = false
|
||||||
|
if (node.file != null) {
|
||||||
|
if (node.file.isFile())
|
||||||
|
callback.onFile(node.file, node.value)
|
||||||
|
else {
|
||||||
|
leave = true
|
||||||
|
callback.onDirectoryEnter(node.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.children.each {
|
||||||
|
doTraverse(it, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leave)
|
||||||
|
callback.onDirectoryLeave()
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void list(File parent, FileListCallback<T> callback) {
|
||||||
|
TreeNode<T> node
|
||||||
|
if (parent == null)
|
||||||
|
node = root
|
||||||
|
else
|
||||||
|
node = fileToNode.get(parent)
|
||||||
|
|
||||||
|
node.children.each {
|
||||||
|
if (it.file.isFile())
|
||||||
|
callback.onFile(it.file, it.value)
|
||||||
|
else
|
||||||
|
callback.onDirectory(it.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TreeNode<T> {
|
||||||
TreeNode parent
|
TreeNode parent
|
||||||
File file
|
File file
|
||||||
|
T value;
|
||||||
final Set<TreeNode> children = new HashSet<>()
|
final Set<TreeNode> children = new HashSet<>()
|
||||||
|
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
file.hashCode()
|
Objects.hash(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.files;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public interface FileTreeCallback<T> {
|
||||||
|
public void onDirectoryEnter(File file);
|
||||||
|
public void onDirectoryLeave();
|
||||||
|
public void onFile(File file, T value);
|
||||||
|
}
|
@@ -65,7 +65,8 @@ class HasherService {
|
|||||||
} else {
|
} else {
|
||||||
eventBus.publish new FileHashingEvent(hashingFile: f)
|
eventBus.publish new FileHashingEvent(hashingFile: f)
|
||||||
def hash = hasher.hashFile f
|
def hash = hasher.hashFile f
|
||||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash.getRoot(), FileHasher.getPieceSize(f.length())),
|
||||||
|
infoHash : hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be triggered by the old PersisterService
|
||||||
|
* once it has finished reading the old file
|
||||||
|
*
|
||||||
|
* @see PersisterService
|
||||||
|
*/
|
||||||
|
class PersisterDoneEvent extends Event{
|
||||||
|
}
|
@@ -0,0 +1,180 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.*
|
||||||
|
import groovy.json.JsonOutput
|
||||||
|
import groovy.json.JsonSlurper
|
||||||
|
import groovy.util.logging.Log
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A persister that stores information about the files shared using
|
||||||
|
* individual JSON files in directories.
|
||||||
|
*
|
||||||
|
* The absolute path's 32bit hash to the shared file is used
|
||||||
|
* to build the directory and filename.
|
||||||
|
*
|
||||||
|
* This persister only starts working once the old persister has finished loading
|
||||||
|
* @see PersisterFolderService#getJsonPath
|
||||||
|
*/
|
||||||
|
@Log
|
||||||
|
class PersisterFolderService extends BasePersisterService {
|
||||||
|
|
||||||
|
final static int CUT_LENGTH = 6
|
||||||
|
|
||||||
|
private final Core core;
|
||||||
|
final File location
|
||||||
|
final EventBus listener
|
||||||
|
final int interval
|
||||||
|
final Timer timer
|
||||||
|
final ExecutorService persisterExecutor = Executors.newSingleThreadExecutor({ r ->
|
||||||
|
new Thread(r, "file persister")
|
||||||
|
} as ThreadFactory)
|
||||||
|
|
||||||
|
PersisterFolderService(Core core, File location, EventBus listener) {
|
||||||
|
this.core = core;
|
||||||
|
this.location = location
|
||||||
|
this.listener = listener
|
||||||
|
this.interval = interval
|
||||||
|
timer = new Timer("file-folder persister timer", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
timer.cancel()
|
||||||
|
persisterExecutor.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPersisterDoneEvent(PersisterDoneEvent persisterDoneEvent) {
|
||||||
|
log.info("Old persister done")
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileHashedEvent(FileHashedEvent hashedEvent) {
|
||||||
|
persistFile(hashedEvent.sharedFile, hashedEvent.infoHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileDownloadedEvent(FileDownloadedEvent downloadedEvent) {
|
||||||
|
if (core.getMuOptions().getShareDownloadedFiles()) {
|
||||||
|
persistFile(downloadedEvent.downloadedFile, downloadedEvent.infoHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get rid of the json and hashlists of unshared files
|
||||||
|
* @param unsharedEvent
|
||||||
|
*/
|
||||||
|
void onFileUnsharedEvent(FileUnsharedEvent unsharedEvent) {
|
||||||
|
def jsonPath = getJsonPath(unsharedEvent.unsharedFile)
|
||||||
|
def jsonFile = jsonPath.toFile()
|
||||||
|
if(jsonFile.isFile()){
|
||||||
|
jsonFile.delete()
|
||||||
|
}
|
||||||
|
def hashListPath = getHashListPath(unsharedEvent.unsharedFile)
|
||||||
|
def hashListFile = hashListPath.toFile()
|
||||||
|
if (hashListFile.isFile())
|
||||||
|
hashListFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileLoadedEvent(FileLoadedEvent loadedEvent) {
|
||||||
|
if(loadedEvent.source == "PersisterService"){
|
||||||
|
log.info("Migrating persisted file from PersisterService: "
|
||||||
|
+ loadedEvent.loadedFile.file.absolutePath.toString())
|
||||||
|
persistFile(loadedEvent.loadedFile, loadedEvent.infoHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUICommentEvent(UICommentEvent e) {
|
||||||
|
persistFile(e.sharedFile,null)
|
||||||
|
}
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
log.fine("Loading...")
|
||||||
|
Thread.currentThread().setPriority(Thread.MIN_PRIORITY)
|
||||||
|
|
||||||
|
if (location.exists() && location.isDirectory()) {
|
||||||
|
try {
|
||||||
|
_load()
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
log.log(Level.WARNING, "couldn't load files", e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
location.mkdirs()
|
||||||
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
|
}
|
||||||
|
loaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads every JSON into memory
|
||||||
|
*/
|
||||||
|
private void _load() {
|
||||||
|
int loaded = 0
|
||||||
|
def slurper = new JsonSlurper()
|
||||||
|
Files.walk(location.toPath())
|
||||||
|
.filter({
|
||||||
|
it.getFileName().toString().endsWith(".json")
|
||||||
|
})
|
||||||
|
.forEach({
|
||||||
|
def parsed = slurper.parse it.toFile()
|
||||||
|
def event = fromJsonLite parsed
|
||||||
|
if (event == null) return
|
||||||
|
|
||||||
|
log.fine("loaded file $event.loadedFile.file")
|
||||||
|
listener.publish event
|
||||||
|
loaded++
|
||||||
|
if (loaded % 10 == 0)
|
||||||
|
Thread.sleep(20)
|
||||||
|
|
||||||
|
})
|
||||||
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistFile(SharedFile sf, InfoHash ih) {
|
||||||
|
persisterExecutor.submit({
|
||||||
|
def jsonPath = getJsonPath(sf)
|
||||||
|
|
||||||
|
def startTime = System.currentTimeMillis()
|
||||||
|
jsonPath.parent.toFile().mkdirs()
|
||||||
|
jsonPath.toFile().withPrintWriter { writer ->
|
||||||
|
def json = toJson sf
|
||||||
|
json = JsonOutput.toJson(json)
|
||||||
|
writer.println json
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ih != null) {
|
||||||
|
def hashListPath = getHashListPath(sf)
|
||||||
|
hashListPath.toFile().bytes = ih.hashList
|
||||||
|
}
|
||||||
|
log.fine("Time(ms) to write json+hashList: " + (System.currentTimeMillis() - startTime))
|
||||||
|
} as Runnable)
|
||||||
|
}
|
||||||
|
private Path getJsonPath(SharedFile sf){
|
||||||
|
def pathHash = sf.getB64PathHash()
|
||||||
|
return Paths.get(
|
||||||
|
location.getAbsolutePath(),
|
||||||
|
pathHash.substring(0, CUT_LENGTH),
|
||||||
|
pathHash.substring(CUT_LENGTH) + ".json"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getHashListPath(SharedFile sf) {
|
||||||
|
def pathHash = sf.getB64PathHash()
|
||||||
|
return Paths.get(
|
||||||
|
location.getAbsolutePath(),
|
||||||
|
pathHash.substring(0, CUT_LENGTH),
|
||||||
|
pathHash.substring(CUT_LENGTH) + ".hashlist"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoHash loadInfoHash(SharedFile sf) {
|
||||||
|
def path = getHashListPath(sf)
|
||||||
|
InfoHash.fromHashList(path.toFile().bytes)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,40 +1,24 @@
|
|||||||
package com.muwire.core.files
|
package com.muwire.core.files
|
||||||
|
|
||||||
import java.nio.file.CopyOption
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.StandardCopyOption
|
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import com.muwire.core.DownloadedFile
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.InfoHash
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
import com.muwire.core.Service
|
|
||||||
import com.muwire.core.SharedFile
|
|
||||||
import com.muwire.core.UILoadedEvent
|
import com.muwire.core.UILoadedEvent
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Base64
|
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class PersisterService extends Service {
|
class PersisterService extends BasePersisterService {
|
||||||
|
|
||||||
final File location
|
final File location
|
||||||
final EventBus listener
|
final EventBus listener
|
||||||
final int interval
|
final int interval
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final ExecutorService persisterExecutor = Executors.newSingleThreadExecutor({ r ->
|
|
||||||
new Thread(r, "file persister")
|
|
||||||
} as ThreadFactory)
|
|
||||||
|
|
||||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||||
this.location = location
|
this.location = location
|
||||||
@@ -51,10 +35,6 @@ class PersisterService extends Service {
|
|||||||
void onUILoadedEvent(UILoadedEvent e) {
|
void onUILoadedEvent(UILoadedEvent e) {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUIPersistFilesEvent(UIPersistFilesEvent e) {
|
|
||||||
persistFiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
Thread.currentThread().setPriority(Thread.MIN_PRIORITY)
|
Thread.currentThread().setPriority(Thread.MIN_PRIORITY)
|
||||||
@@ -69,6 +49,7 @@ class PersisterService extends Service {
|
|||||||
def event = fromJson parsed
|
def event = fromJson parsed
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
log.fine("loaded file $event.loadedFile.file")
|
log.fine("loaded file $event.loadedFile.file")
|
||||||
|
event.source = "PersisterService"
|
||||||
listener.publish event
|
listener.publish event
|
||||||
loaded++
|
loaded++
|
||||||
if (loaded % 10 == 0)
|
if (loaded % 10 == 0)
|
||||||
@@ -76,126 +57,18 @@ class PersisterService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
// Backup the old hashes
|
||||||
} catch (IllegalArgumentException|NumberFormatException e) {
|
location.renameTo(
|
||||||
|
new File(location.absolutePath + ".bak")
|
||||||
|
)
|
||||||
|
listener.publish(new PersisterDoneEvent())
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
log.log(Level.WARNING, "couldn't load files",e)
|
log.log(Level.WARNING, "couldn't load files",e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new PersisterDoneEvent())
|
||||||
}
|
}
|
||||||
timer.schedule({persistFiles()} as TimerTask, 1000, interval)
|
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileLoadedEvent fromJson(def json) {
|
|
||||||
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
|
||||||
throw new IllegalArgumentException()
|
|
||||||
if (!(json.hashList instanceof List))
|
|
||||||
throw new IllegalArgumentException()
|
|
||||||
|
|
||||||
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
|
||||||
file = file.getCanonicalFile()
|
|
||||||
if (!file.exists() || file.isDirectory())
|
|
||||||
return null
|
|
||||||
long length = Long.valueOf(json.length)
|
|
||||||
if (length != file.length())
|
|
||||||
return null
|
|
||||||
|
|
||||||
List hashList = (List) json.hashList
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
|
||||||
hashList.each {
|
|
||||||
byte [] hash = Base64.decode it.toString()
|
|
||||||
if (hash == null)
|
|
||||||
throw new IllegalArgumentException()
|
|
||||||
baos.write hash
|
|
||||||
}
|
|
||||||
byte[] hashListBytes = baos.toByteArray()
|
|
||||||
|
|
||||||
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
|
||||||
byte [] root = Base64.decode(json.infoHash.toString())
|
|
||||||
if (root == null)
|
|
||||||
throw new IllegalArgumentException()
|
|
||||||
if (!Arrays.equals(root, ih.getRoot()))
|
|
||||||
return null
|
|
||||||
|
|
||||||
int pieceSize = 0
|
|
||||||
if (json.pieceSize != null)
|
|
||||||
pieceSize = json.pieceSize
|
|
||||||
|
|
||||||
if (json.sources != null) {
|
|
||||||
List sources = (List)json.sources
|
|
||||||
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
|
||||||
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
|
||||||
df.setComment(json.comment)
|
|
||||||
return new FileLoadedEvent(loadedFile : df)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
|
||||||
sf.setComment(json.comment)
|
|
||||||
if (json.downloaders != null)
|
|
||||||
sf.getDownloaders().addAll(json.downloaders)
|
|
||||||
if (json.searchers != null) {
|
|
||||||
json.searchers.each {
|
|
||||||
Persona searcher = null
|
|
||||||
if (it.searcher != null)
|
|
||||||
searcher = new Persona(new ByteArrayInputStream(Base64.decode(it.searcher)))
|
|
||||||
long timestamp = it.timestamp
|
|
||||||
String query = it.query
|
|
||||||
sf.hit(searcher, timestamp, query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new FileLoadedEvent(loadedFile: sf)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void persistFiles() {
|
|
||||||
persisterExecutor.submit( {
|
|
||||||
def sharedFiles = fileManager.getSharedFiles()
|
|
||||||
|
|
||||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
|
||||||
tmp.deleteOnExit()
|
|
||||||
tmp.withPrintWriter { writer ->
|
|
||||||
sharedFiles.each { k, v ->
|
|
||||||
def json = toJson(k,v)
|
|
||||||
json = JsonOutput.toJson(json)
|
|
||||||
writer.println json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
|
||||||
tmp.delete()
|
|
||||||
} as Runnable)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def toJson(File f, SharedFile sf) {
|
|
||||||
def json = [:]
|
|
||||||
json.file = sf.getB64EncodedFileName()
|
|
||||||
json.length = sf.getCachedLength()
|
|
||||||
InfoHash ih = sf.getInfoHash()
|
|
||||||
json.infoHash = sf.getB64EncodedHashRoot()
|
|
||||||
json.pieceSize = sf.getPieceSize()
|
|
||||||
json.hashList = sf.getB64EncodedHashList()
|
|
||||||
json.comment = sf.getComment()
|
|
||||||
json.hits = sf.getHits()
|
|
||||||
json.downloaders = sf.getDownloaders()
|
|
||||||
|
|
||||||
if (!sf.searches.isEmpty()) {
|
|
||||||
Set searchers = new HashSet<>()
|
|
||||||
sf.searches.each {
|
|
||||||
def search = [:]
|
|
||||||
if (it.searcher != null)
|
|
||||||
search.searcher = it.searcher.toBase64()
|
|
||||||
search.timestamp = it.timestamp
|
|
||||||
search.query = it.query
|
|
||||||
searchers.add(search)
|
|
||||||
}
|
|
||||||
json.searchers = searchers
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sf instanceof DownloadedFile) {
|
|
||||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
json
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
package com.muwire.core.files
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class UIPersistFilesEvent extends Event {
|
|
||||||
}
|
|
@@ -1,5 +1,7 @@
|
|||||||
package com.muwire.core.hostcache
|
package com.muwire.core.hostcache
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.connection.ConnectionManager
|
import com.muwire.core.connection.ConnectionManager
|
||||||
@@ -27,6 +29,7 @@ class CacheClient {
|
|||||||
final long interval
|
final long interval
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Timer timer
|
final Timer timer
|
||||||
|
private final AtomicBoolean stopped = new AtomicBoolean();
|
||||||
|
|
||||||
public CacheClient(EventBus eventBus, HostCache cache,
|
public CacheClient(EventBus eventBus, HostCache cache,
|
||||||
ConnectionManager manager, I2PSession session,
|
ConnectionManager manager, I2PSession session,
|
||||||
@@ -47,9 +50,12 @@ class CacheClient {
|
|||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
|
stopped.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void queryIfNeeded() {
|
private void queryIfNeeded() {
|
||||||
|
if (stopped.get())
|
||||||
|
return
|
||||||
if (!manager.getConnections().isEmpty())
|
if (!manager.getConnections().isEmpty())
|
||||||
return
|
return
|
||||||
if (!cache.getHosts(1).isEmpty())
|
if (!cache.getHosts(1).isEmpty())
|
||||||
@@ -65,7 +71,12 @@ class CacheClient {
|
|||||||
options.setSendLeaseSet(true)
|
options.setSendLeaseSet(true)
|
||||||
CacheServers.getCacheServers().each {
|
CacheServers.getCacheServers().each {
|
||||||
log.info "Querying hostcache ${it.toBase32()}"
|
log.info "Querying hostcache ${it.toBase32()}"
|
||||||
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
try {
|
||||||
|
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!stopped.get())
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,7 +35,7 @@ class BrowseManager {
|
|||||||
browserThread.execute({
|
browserThread.execute({
|
||||||
Endpoint endpoint = null
|
Endpoint endpoint = null
|
||||||
try {
|
try {
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.CONNECTING))
|
eventBus.publish(new BrowseStatusEvent(host : e.host, status : BrowseStatus.CONNECTING))
|
||||||
endpoint = connector.connect(e.host.destination)
|
endpoint = connector.connect(e.host.destination)
|
||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
os.write("BROWSE\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("BROWSE\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
@@ -55,8 +55,10 @@ class BrowseManager {
|
|||||||
|
|
||||||
int results = Integer.parseInt(headers['Count'])
|
int results = Integer.parseInt(headers['Count'])
|
||||||
|
|
||||||
|
boolean chat = headers.containsKey("Chat") && Boolean.parseBoolean(headers['Chat'])
|
||||||
|
|
||||||
// at this stage, start pulling the results
|
// at this stage, start pulling the results
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FETCHING, totalResults : results))
|
eventBus.publish(new BrowseStatusEvent(host: e.host, status : BrowseStatus.FETCHING, totalResults : results))
|
||||||
|
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
DataInputStream dis = new DataInputStream(new GZIPInputStream(is))
|
DataInputStream dis = new DataInputStream(new GZIPInputStream(is))
|
||||||
@@ -67,14 +69,15 @@ class BrowseManager {
|
|||||||
dis.readFully(tmp)
|
dis.readFully(tmp)
|
||||||
def json = slurper.parse(tmp)
|
def json = slurper.parse(tmp)
|
||||||
UIResultEvent result = ResultsParser.parse(e.host, uuid, json)
|
UIResultEvent result = ResultsParser.parse(e.host, uuid, json)
|
||||||
|
result.chat = chat
|
||||||
eventBus.publish(result)
|
eventBus.publish(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FINISHED))
|
eventBus.publish(new BrowseStatusEvent(host: e.host, status : BrowseStatus.FINISHED))
|
||||||
|
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.WARNING, "browse failed", bad)
|
log.log(Level.WARNING, "browse failed", bad)
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FAILED))
|
eventBus.publish(new BrowseStatusEvent(host: e.host, status : BrowseStatus.FAILED))
|
||||||
} finally {
|
} finally {
|
||||||
endpoint?.close()
|
endpoint?.close()
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
package com.muwire.core.search
|
package com.muwire.core.search
|
||||||
|
|
||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
class BrowseStatusEvent extends Event {
|
class BrowseStatusEvent extends Event {
|
||||||
|
Persona host
|
||||||
BrowseStatus status
|
BrowseStatus status
|
||||||
int totalResults
|
int totalResults
|
||||||
}
|
}
|
||||||
|
@@ -77,11 +77,11 @@ class ResultsSender {
|
|||||||
if (it.getComment() != null) {
|
if (it.getComment() != null) {
|
||||||
comment = DataUtil.readi18nString(Base64.decode(it.getComment()))
|
comment = DataUtil.readi18nString(Base64.decode(it.getComment()))
|
||||||
}
|
}
|
||||||
int certificates = certificateManager.getByInfoHash(it.getInfoHash()).size()
|
int certificates = certificateManager.getByInfoHash(new InfoHash(it.getRoot())).size()
|
||||||
def uiResultEvent = new UIResultEvent( sender : me,
|
def uiResultEvent = new UIResultEvent( sender : me,
|
||||||
name : it.getFile().getName(),
|
name : it.getFile().getName(),
|
||||||
size : length,
|
size : length,
|
||||||
infohash : it.getInfoHash(),
|
infohash : new InfoHash(it.getRoot()),
|
||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
uuid : uuid,
|
uuid : uuid,
|
||||||
browse : settings.browseFiles,
|
browse : settings.browseFiles,
|
||||||
@@ -119,7 +119,7 @@ class ResultsSender {
|
|||||||
me.write(os)
|
me.write(os)
|
||||||
os.writeShort((short)results.length)
|
os.writeShort((short)results.length)
|
||||||
results.each {
|
results.each {
|
||||||
int certificates = certificateManager.getByInfoHash(it.getInfoHash()).size()
|
int certificates = certificateManager.getByInfoHash(new InfoHash(it.getRoot())).size()
|
||||||
def obj = sharedFileToObj(it, settings.browseFiles, certificates)
|
def obj = sharedFileToObj(it, settings.browseFiles, certificates)
|
||||||
def json = jsonOutput.toJson(obj)
|
def json = jsonOutput.toJson(obj)
|
||||||
os.writeShort((short)json.length())
|
os.writeShort((short)json.length())
|
||||||
@@ -141,7 +141,7 @@ class ResultsSender {
|
|||||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
||||||
results.each {
|
results.each {
|
||||||
int certificates = certificateManager.getByInfoHash(it.getInfoHash()).size()
|
int certificates = certificateManager.getByInfoHash(new InfoHash(it.getRoot())).size()
|
||||||
def obj = sharedFileToObj(it, settings.browseFiles, certificates)
|
def obj = sharedFileToObj(it, settings.browseFiles, certificates)
|
||||||
def json = jsonOutput.toJson(obj)
|
def json = jsonOutput.toJson(obj)
|
||||||
dos.writeShort((short)json.length())
|
dos.writeShort((short)json.length())
|
||||||
@@ -170,7 +170,7 @@ class ResultsSender {
|
|||||||
obj.type = "Result"
|
obj.type = "Result"
|
||||||
obj.version = 2
|
obj.version = 2
|
||||||
obj.name = encodedName
|
obj.name = encodedName
|
||||||
obj.infohash = Base64.encode(sf.getInfoHash().getRoot())
|
obj.infohash = Base64.encode(sf.getRoot())
|
||||||
obj.size = sf.getCachedLength()
|
obj.size = sf.getCachedLength()
|
||||||
obj.pieceSize = sf.getPieceSize()
|
obj.pieceSize = sf.getPieceSize()
|
||||||
|
|
||||||
|
@@ -10,8 +10,8 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
class RemoteTrustList {
|
class RemoteTrustList {
|
||||||
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
||||||
|
|
||||||
private final Persona persona
|
final Persona persona
|
||||||
private final Set<TrustEntry> good, bad
|
final Set<TrustEntry> good, bad
|
||||||
volatile long timestamp
|
volatile long timestamp
|
||||||
volatile boolean forceUpdate
|
volatile boolean forceUpdate
|
||||||
Status status = Status.NEW
|
Status status = Status.NEW
|
||||||
|
@@ -130,8 +130,8 @@ class TrustService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class TrustEntry {
|
public static class TrustEntry {
|
||||||
private final Persona persona
|
final Persona persona
|
||||||
private final String reason
|
final String reason
|
||||||
TrustEntry(Persona persona, String reason) {
|
TrustEntry(Persona persona, String reason) {
|
||||||
this.persona = persona
|
this.persona = persona
|
||||||
this.reason = reason
|
this.reason = reason
|
||||||
|
@@ -26,7 +26,7 @@ class TrustSubscriber {
|
|||||||
private final I2PConnector i2pConnector
|
private final I2PConnector i2pConnector
|
||||||
private final MuWireSettings settings
|
private final MuWireSettings settings
|
||||||
|
|
||||||
private final Map<Destination, RemoteTrustList> remoteTrustLists = new ConcurrentHashMap<>()
|
final Map<Destination, RemoteTrustList> remoteTrustLists = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
private final Object waitLock = new Object()
|
private final Object waitLock = new Object()
|
||||||
private volatile boolean shutdown
|
private volatile boolean shutdown
|
||||||
@@ -50,7 +50,7 @@ class TrustSubscriber {
|
|||||||
thread?.interrupt()
|
thread?.interrupt()
|
||||||
updateThreads.shutdownNow()
|
updateThreads.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
|
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
|
||||||
if (!e.subscribe) {
|
if (!e.subscribe) {
|
||||||
remoteTrustLists.remove(e.persona.destination)
|
remoteTrustLists.remove(e.persona.destination)
|
||||||
@@ -62,6 +62,10 @@ class TrustSubscriber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSubscribed(Persona p) {
|
||||||
|
remoteTrustLists.containsKey(p.destination)
|
||||||
|
}
|
||||||
|
|
||||||
private void checkLoop() {
|
private void checkLoop() {
|
||||||
try {
|
try {
|
||||||
|
@@ -83,7 +83,7 @@ class UpdateClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
if (e.downloadedFile.infoHash != updateInfoHash)
|
if (e.infoHash != updateInfoHash)
|
||||||
return
|
return
|
||||||
updateDownloading = false
|
updateDownloading = false
|
||||||
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer, text : text))
|
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer, text : text))
|
||||||
|
@@ -12,6 +12,7 @@ import com.muwire.core.download.DownloadManager
|
|||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
import com.muwire.core.download.SourceDiscoveredEvent
|
import com.muwire.core.download.SourceDiscoveredEvent
|
||||||
import com.muwire.core.files.FileManager
|
import com.muwire.core.files.FileManager
|
||||||
|
import com.muwire.core.files.PersisterFolderService
|
||||||
import com.muwire.core.mesh.Mesh
|
import com.muwire.core.mesh.Mesh
|
||||||
import com.muwire.core.mesh.MeshManager
|
import com.muwire.core.mesh.MeshManager
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import net.i2p.data.Base64
|
|||||||
public class UploadManager {
|
public class UploadManager {
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
|
private final PersisterFolderService persisterService
|
||||||
private final MeshManager meshManager
|
private final MeshManager meshManager
|
||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
private final MuWireSettings props
|
private final MuWireSettings props
|
||||||
@@ -34,9 +36,11 @@ public class UploadManager {
|
|||||||
|
|
||||||
public UploadManager(EventBus eventBus, FileManager fileManager,
|
public UploadManager(EventBus eventBus, FileManager fileManager,
|
||||||
MeshManager meshManager, DownloadManager downloadManager,
|
MeshManager meshManager, DownloadManager downloadManager,
|
||||||
|
PersisterFolderService persisterService,
|
||||||
MuWireSettings props) {
|
MuWireSettings props) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
|
this.persisterService = persisterService
|
||||||
this.meshManager = meshManager
|
this.meshManager = meshManager
|
||||||
this.downloadManager = downloadManager
|
this.downloadManager = downloadManager
|
||||||
this.props = props
|
this.props = props
|
||||||
@@ -162,7 +166,7 @@ public class UploadManager {
|
|||||||
|
|
||||||
InfoHash fullInfoHash
|
InfoHash fullInfoHash
|
||||||
if (downloader == null) {
|
if (downloader == null) {
|
||||||
fullInfoHash = sharedFiles.iterator().next().infoHash
|
fullInfoHash = persisterService.loadInfoHash(sharedFiles.iterator().next())
|
||||||
} else {
|
} else {
|
||||||
byte [] hashList = downloader.getInfoHash().getHashList()
|
byte [] hashList = downloader.getInfoHash().getHashList()
|
||||||
if (hashList != null && hashList.length > 0)
|
if (hashList != null && hashList.length > 0)
|
||||||
|
@@ -10,9 +10,9 @@ public class DownloadedFile extends SharedFile {
|
|||||||
|
|
||||||
private final Set<Destination> sources;
|
private final Set<Destination> sources;
|
||||||
|
|
||||||
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
public DownloadedFile(File file, byte[] root, int pieceSize, Set<Destination> sources)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
super(file, infoHash, pieceSize);
|
super(file, root, pieceSize);
|
||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -68,11 +68,19 @@ public class Persona {
|
|||||||
humanReadableName = name.getName() + "@" + destination.toBase32().substring(0,32);
|
humanReadableName = name.getName() + "@" + destination.toBase32().substring(0,32);
|
||||||
return humanReadableName;
|
return humanReadableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Destination getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
public String toBase64() throws DataFormatException, IOException {
|
public String toBase64() {
|
||||||
if (base64 == null) {
|
if (base64 == null) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
write(baos);
|
try {
|
||||||
|
write(baos);
|
||||||
|
} catch (Exception impossible) {
|
||||||
|
throw new RuntimeException(impossible);
|
||||||
|
}
|
||||||
base64 = Base64.encode(baos.toByteArray());
|
base64 = Base64.encode(baos.toByteArray());
|
||||||
}
|
}
|
||||||
return base64;
|
return base64;
|
||||||
|
@@ -2,7 +2,10 @@ package com.muwire.core;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -16,44 +19,47 @@ import net.i2p.data.Base64;
|
|||||||
public class SharedFile {
|
public class SharedFile {
|
||||||
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final InfoHash infoHash;
|
private final byte[] root;
|
||||||
private final int pieceSize;
|
private final int pieceSize;
|
||||||
|
|
||||||
private final String cachedPath;
|
private final String cachedPath;
|
||||||
private final long cachedLength;
|
private final long cachedLength;
|
||||||
|
|
||||||
|
private String b64PathHash;
|
||||||
private final String b64EncodedFileName;
|
private final String b64EncodedFileName;
|
||||||
private final String b64EncodedHashRoot;
|
|
||||||
private final List<String> b64EncodedHashList;
|
|
||||||
|
|
||||||
private volatile String comment;
|
private volatile String comment;
|
||||||
private final Set<String> downloaders = Collections.synchronizedSet(new HashSet<>());
|
private final Set<String> downloaders = Collections.synchronizedSet(new HashSet<>());
|
||||||
private final Set<SearchEntry> searches = Collections.synchronizedSet(new HashSet<>());
|
private final Set<SearchEntry> searches = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
public SharedFile(File file, byte[] root, int pieceSize) throws IOException {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.infoHash = infoHash;
|
this.root = root;
|
||||||
this.pieceSize = pieceSize;
|
this.pieceSize = pieceSize;
|
||||||
this.cachedPath = file.getAbsolutePath();
|
this.cachedPath = file.getAbsolutePath();
|
||||||
this.cachedLength = file.length();
|
this.cachedLength = file.length();
|
||||||
this.b64EncodedFileName = Base64.encode(DataUtil.encodei18nString(file.toString()));
|
this.b64EncodedFileName = Base64.encode(DataUtil.encodei18nString(file.toString()));
|
||||||
this.b64EncodedHashRoot = Base64.encode(infoHash.getRoot());
|
|
||||||
|
|
||||||
List<String> b64List = new ArrayList<String>();
|
|
||||||
byte[] tmp = new byte[32];
|
|
||||||
for (int i = 0; i < infoHash.getHashList().length / 32; i++) {
|
|
||||||
System.arraycopy(infoHash.getHashList(), i * 32, tmp, 0, 32);
|
|
||||||
b64List.add(Base64.encode(tmp));
|
|
||||||
}
|
|
||||||
this.b64EncodedHashList = b64List;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash getInfoHash() {
|
public byte[] getPathHash() throws NoSuchAlgorithmException {
|
||||||
return infoHash;
|
MessageDigest digester = MessageDigest.getInstance("SHA-256");
|
||||||
|
digester.update(file.getAbsolutePath().getBytes());
|
||||||
|
return digester.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getB64PathHash() throws NoSuchAlgorithmException {
|
||||||
|
if(b64PathHash == null){
|
||||||
|
b64PathHash = Base64.encode(getPathHash());
|
||||||
|
}
|
||||||
|
return b64PathHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRoot() {
|
||||||
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPieceSize() {
|
public int getPieceSize() {
|
||||||
@@ -73,14 +79,6 @@ public class SharedFile {
|
|||||||
return b64EncodedFileName;
|
return b64EncodedFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getB64EncodedHashRoot() {
|
|
||||||
return b64EncodedHashRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getB64EncodedHashList() {
|
|
||||||
return b64EncodedHashList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCachedPath() {
|
public String getCachedPath() {
|
||||||
return cachedPath;
|
return cachedPath;
|
||||||
}
|
}
|
||||||
@@ -119,7 +117,7 @@ public class SharedFile {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return file.hashCode() ^ infoHash.hashCode();
|
return file.hashCode() ^ Arrays.hashCode(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,7 +125,7 @@ public class SharedFile {
|
|||||||
if (!(o instanceof SharedFile))
|
if (!(o instanceof SharedFile))
|
||||||
return false;
|
return false;
|
||||||
SharedFile other = (SharedFile)o;
|
SharedFile other = (SharedFile)o;
|
||||||
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
return file.equals(other.file) && Arrays.equals(root, other.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SearchEntry {
|
public static class SearchEntry {
|
||||||
|
@@ -63,7 +63,7 @@ public class DataUtil {
|
|||||||
((int)header[2] & 0xFF);
|
((int)header[2] & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String readi18nString(byte [] encoded) {
|
public static String readi18nString(byte [] encoded) {
|
||||||
if (encoded.length < 2)
|
if (encoded.length < 2)
|
||||||
throw new IllegalArgumentException("encoding too short $encoded.length");
|
throw new IllegalArgumentException("encoding too short $encoded.length");
|
||||||
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF);
|
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF);
|
||||||
|
8
core/src/main/resources/defaults/i2p.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
inbound.nickname=MuWire
|
||||||
|
outbound.nickname=MuWire
|
||||||
|
inbound.length=3
|
||||||
|
inbound.quantity=4
|
||||||
|
outbound.length=3
|
||||||
|
outbound.quantity=4
|
||||||
|
i2cp.tcp.host=127.0.0.1
|
||||||
|
i2cp.tcp.port=7654
|
@@ -95,7 +95,7 @@ class ConnectionAcceptorTest {
|
|||||||
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
||||||
|
|
||||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||||
hostCache, trustService, searchManager, uploadManager, null, connectionEstablisher, null)
|
hostCache, trustService, searchManager, uploadManager, null, connectionEstablisher, null, null)
|
||||||
acceptor.start()
|
acceptor.start()
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@ class FileTreeTest {
|
|||||||
File b = new File(a, "b")
|
File b = new File(a, "b")
|
||||||
File c = new File(b, "c")
|
File c = new File(b, "c")
|
||||||
|
|
||||||
FileTree tree = new FileTree()
|
FileTree<Void> tree = new FileTree<>()
|
||||||
tree.add(c)
|
tree.add(c,null)
|
||||||
|
|
||||||
assert tree.root.children.size() == 1
|
assert tree.root.children.size() == 1
|
||||||
assert tree.fileToNode.size() == 3
|
assert tree.fileToNode.size() == 3
|
||||||
@@ -28,15 +28,110 @@ class FileTreeTest {
|
|||||||
File c = new File(b, "c")
|
File c = new File(b, "c")
|
||||||
File d = new File(b, "d")
|
File d = new File(b, "d")
|
||||||
|
|
||||||
FileTree tree = new FileTree()
|
FileTree<Void> tree = new FileTree<>()
|
||||||
tree.add(c)
|
tree.add(c,null)
|
||||||
|
|
||||||
assert tree.fileToNode.size() == 3
|
assert tree.fileToNode.size() == 3
|
||||||
|
|
||||||
tree.add(d)
|
tree.add(d, null)
|
||||||
assert tree.fileToNode.size() == 4
|
assert tree.fileToNode.size() == 4
|
||||||
|
|
||||||
tree.remove(d)
|
tree.remove(d)
|
||||||
assert tree.fileToNode.size() == 3
|
assert tree.fileToNode.size() == 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTraverse() {
|
||||||
|
Stack stack = new Stack()
|
||||||
|
Set<String> values = new HashSet<>()
|
||||||
|
StringBuilder sb = new StringBuilder()
|
||||||
|
def cb = new FileTreeCallback<String>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDirectoryEnter(File file) {
|
||||||
|
stack.push(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDirectoryLeave() {
|
||||||
|
stack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFile(File file, String value) {
|
||||||
|
values.add(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File a = new File("a")
|
||||||
|
a.createNewFile()
|
||||||
|
File b = new File("b")
|
||||||
|
b.mkdir()
|
||||||
|
File c = new File(b, "c")
|
||||||
|
c.createNewFile()
|
||||||
|
File d = new File(b, "d")
|
||||||
|
d.mkdir()
|
||||||
|
File e = new File(d, "e")
|
||||||
|
e.createNewFile()
|
||||||
|
FileTree<String> tree = new FileTree<>()
|
||||||
|
|
||||||
|
tree.add(a, "a")
|
||||||
|
tree.add(b, "b")
|
||||||
|
tree.add(c, "c")
|
||||||
|
tree.add(d, "d")
|
||||||
|
tree.add(e, "e")
|
||||||
|
|
||||||
|
tree.traverse(cb)
|
||||||
|
|
||||||
|
assert stack.isEmpty()
|
||||||
|
assert values.size() == 3
|
||||||
|
assert values.contains("a")
|
||||||
|
assert values.contains("c")
|
||||||
|
assert values.contains("e")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testList() {
|
||||||
|
Set<File> directories = new HashSet<>()
|
||||||
|
Set<String> values = new HashSet<>()
|
||||||
|
def cb = new FileListCallback<String>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDirectory(File file) {
|
||||||
|
directories.add(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFile(File file, String value) {
|
||||||
|
values.add(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File a = new File("a")
|
||||||
|
a.createNewFile()
|
||||||
|
File b = new File("b")
|
||||||
|
b.mkdir()
|
||||||
|
File c = new File(b, "c")
|
||||||
|
c.createNewFile()
|
||||||
|
|
||||||
|
FileTree<String> tree = new FileTree<>()
|
||||||
|
|
||||||
|
tree.add(a, "a")
|
||||||
|
tree.add(b, "b")
|
||||||
|
tree.add(c, "c")
|
||||||
|
|
||||||
|
tree.list(null, cb)
|
||||||
|
|
||||||
|
assert directories.size() == 1
|
||||||
|
assert directories.contains(b)
|
||||||
|
assert values.size() == 1
|
||||||
|
assert values.contains("a")
|
||||||
|
|
||||||
|
directories.clear()
|
||||||
|
values.clear()
|
||||||
|
tree.list(b, cb)
|
||||||
|
assert directories.isEmpty()
|
||||||
|
assert values.size() == 1
|
||||||
|
assert values.contains("c")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
26
docker/rootfs/etc/cont-init.d/00-app-user-map.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/with-contenv sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add the app user to the password and group databases. This is needed just to
|
||||||
|
# make sure that mapping between the user/group ID and its name is possible.
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e # Exit immediately if a command exits with a non-zero status.
|
||||||
|
set -u # Treat unset variables as an error.
|
||||||
|
|
||||||
|
cp /defaults/passwd /etc/passwd
|
||||||
|
cp /defaults/group /etc/group
|
||||||
|
cp /defaults/shadow /etc/shadow
|
||||||
|
chown root:shadow /etc/shadow
|
||||||
|
chmod 640 /etc/shadow
|
||||||
|
|
||||||
|
echo "$APP_USER:x:$USER_ID:$GROUP_ID::${APP_HOME:-/dev/null}:/sbin/nologin" >> /etc/passwd
|
||||||
|
echo "$APP_USER:x:$GROUP_ID:" >> /etc/group
|
||||||
|
|
||||||
|
# Make sure APP_HOME is editable by the user
|
||||||
|
if [[ -n "$APP_HOME" ]] ; then
|
||||||
|
chown -R "$APP_USER" "$APP_HOME"
|
||||||
|
chmod -R u+rw "$APP_HOME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# vim:ft=sh:ts=4:sw=4:et:sts=4
|
34
docker/rootfs/muwire/.MuWire/MuWire.properties
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#This file is UTF-8
|
||||||
|
#Tue Jan 14 12:08:47 GMT 2020
|
||||||
|
meshExpiration=60
|
||||||
|
autoDownloadUpdate=true
|
||||||
|
hostHopelessInterval=1440
|
||||||
|
uploadSlotsPerUser=-1
|
||||||
|
downloadLocation=/output
|
||||||
|
allowTrustLists=true
|
||||||
|
embeddedRouter=false
|
||||||
|
incompleteLocation=/incompletes
|
||||||
|
outBw=128
|
||||||
|
searchExtraHop=false
|
||||||
|
shareHiddenFiles=false
|
||||||
|
advertiseChat=true
|
||||||
|
totalUploadSlots=-1
|
||||||
|
hostClearInterval=15
|
||||||
|
searchComments=true
|
||||||
|
downloadSequentialRatio=0.8
|
||||||
|
maxChatConnectios=-1
|
||||||
|
trustListInterval=1
|
||||||
|
crawlerResponse=REGISTERED
|
||||||
|
browseFiles=true
|
||||||
|
lastUpdateCheck=1579003533112
|
||||||
|
hostRejectInterval=1
|
||||||
|
inBw=256
|
||||||
|
leaf=false
|
||||||
|
updateCheckInterval=24
|
||||||
|
plugin=false
|
||||||
|
downloadRetryInterval=60
|
||||||
|
speedSmoothSeconds=60
|
||||||
|
allowUntrusted=true
|
||||||
|
shareDownloadedFiles=true
|
||||||
|
startChatServer=false
|
||||||
|
updateType=jar
|
1
docker/rootfs/muwire/.MuWire/i2p.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
i2cp.tcp.host=172.17.0.1
|
7
docker/rootfs/startapp.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Explicitly define HOME otherwise it might not have been set
|
||||||
|
export HOME=/muwire
|
||||||
|
|
||||||
|
echo "Starting MuWire"
|
||||||
|
exec /muwire/bin/MuWire
|
@@ -1,6 +1,6 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.6.7
|
version = 0.6.10
|
||||||
i2pVersion = 0.9.43
|
i2pVersion = 0.9.44
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
spockVersion = 1.1-groovy-2.4
|
spockVersion = 1.1-groovy-2.4
|
||||||
@@ -16,6 +16,6 @@ author = zab@mail.i2p
|
|||||||
signer = zab@mail.i2p
|
signer = zab@mail.i2p
|
||||||
keystorePassword=changeit
|
keystorePassword=changeit
|
||||||
websiteURL=http://muwire.i2p
|
websiteURL=http://muwire.i2p
|
||||||
updateURLsu3=http://muwire.i2p/MuWire.su3
|
updateURLsu3=http://muwire.i2p/MuWire-update.su3
|
||||||
|
|
||||||
pack200=true
|
pack200=true
|
||||||
|
@@ -47,6 +47,7 @@ class BrowseController {
|
|||||||
|
|
||||||
void onUIResultEvent(UIResultEvent e) {
|
void onUIResultEvent(UIResultEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
|
model.chatActionEnabled = e.chat
|
||||||
model.results << e
|
model.results << e
|
||||||
model.resultCount = model.results.size()
|
model.resultCount = model.results.size()
|
||||||
view.resultsTable.model.fireTableDataChanged()
|
view.resultsTable.model.fireTableDataChanged()
|
||||||
@@ -116,4 +117,13 @@ class BrowseController {
|
|||||||
params['core'] = core
|
params['core'] = core
|
||||||
mvcGroup.createMVCGroup("fetch-certificates", params)
|
mvcGroup.createMVCGroup("fetch-certificates", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void chat() {
|
||||||
|
dismiss()
|
||||||
|
def mainFrameGroup = application.mvcGroupManager.getGroups()['MainFrame']
|
||||||
|
|
||||||
|
mainFrameGroup.controller.startChat(model.host)
|
||||||
|
mainFrameGroup.view.showChatWindow.call()
|
||||||
|
}
|
||||||
}
|
}
|
@@ -23,6 +23,8 @@ class I2PStatusController {
|
|||||||
Router router = core.router
|
Router router = core.router
|
||||||
model.networkStatus = router._context.commSystem().status.toStatusString()
|
model.networkStatus = router._context.commSystem().status.toStatusString()
|
||||||
model.floodfill = router._context.netDb().floodfillEnabled()
|
model.floodfill = router._context.netDb().floodfillEnabled()
|
||||||
|
model.myCountry = router._context.commSystem().getOurCountry()
|
||||||
|
model.strictCountry = router._context.commSystem().isInStrictCountry()
|
||||||
model.ntcpConnections = router._context.commSystem().getTransports()["NTCP"].countPeers()
|
model.ntcpConnections = router._context.commSystem().getTransports()["NTCP"].countPeers()
|
||||||
model.ssuConnections = router._context.commSystem().getTransports()["SSU"].countPeers()
|
model.ssuConnections = router._context.commSystem().getTransports()["SSU"].countPeers()
|
||||||
model.participatingTunnels = router._context.tunnelManager().getParticipatingCount()
|
model.participatingTunnels = router._context.tunnelManager().getParticipatingCount()
|
||||||
|
@@ -3,17 +3,15 @@ package com.muwire.gui
|
|||||||
import griffon.core.GriffonApplication
|
import griffon.core.GriffonApplication
|
||||||
import griffon.core.artifact.GriffonController
|
import griffon.core.artifact.GriffonController
|
||||||
import griffon.core.controller.ControllerAction
|
import griffon.core.controller.ControllerAction
|
||||||
import griffon.core.mvc.MVCGroup
|
|
||||||
import griffon.core.mvc.MVCGroupConfiguration
|
|
||||||
import griffon.inject.MVCMember
|
import griffon.inject.MVCMember
|
||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
import groovy.json.StringEscapeUtils
|
|
||||||
import net.i2p.crypto.DSAEngine
|
import net.i2p.crypto.DSAEngine
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
import net.i2p.data.Signature
|
import net.i2p.data.Signature
|
||||||
import net.i2p.data.SigningPrivateKey
|
|
||||||
|
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
|
import java.awt.Toolkit
|
||||||
|
import java.awt.datatransfer.StringSelection
|
||||||
import java.awt.event.ActionEvent
|
import java.awt.event.ActionEvent
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
@@ -28,15 +26,11 @@ import com.muwire.core.Persona
|
|||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.SplitPattern
|
import com.muwire.core.SplitPattern
|
||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
import com.muwire.core.download.DownloadStartedEvent
|
|
||||||
import com.muwire.core.download.UIDownloadCancelledEvent
|
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||||
import com.muwire.core.download.UIDownloadEvent
|
|
||||||
import com.muwire.core.download.UIDownloadPausedEvent
|
import com.muwire.core.download.UIDownloadPausedEvent
|
||||||
import com.muwire.core.download.UIDownloadResumedEvent
|
import com.muwire.core.download.UIDownloadResumedEvent
|
||||||
import com.muwire.core.filecert.UICreateCertificateEvent
|
import com.muwire.core.filecert.UICreateCertificateEvent
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
|
||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
import com.muwire.core.files.UIPersistFilesEvent
|
|
||||||
import com.muwire.core.search.QueryEvent
|
import com.muwire.core.search.QueryEvent
|
||||||
import com.muwire.core.search.SearchEvent
|
import com.muwire.core.search.SearchEvent
|
||||||
import com.muwire.core.trust.RemoteTrustList
|
import com.muwire.core.trust.RemoteTrustList
|
||||||
@@ -87,8 +81,19 @@ class MainFrameController {
|
|||||||
search = search.trim()
|
search = search.trim()
|
||||||
if (search.length() == 0)
|
if (search.length() == 0)
|
||||||
return
|
return
|
||||||
if (search.length() > 128)
|
if (search.length() > 128) {
|
||||||
search = search.substring(0,128)
|
try {
|
||||||
|
Persona p = new Persona(new ByteArrayInputStream(Base64.decode(search)))
|
||||||
|
String groupId = p.getHumanReadableName() + "-browse"
|
||||||
|
def params = [:]
|
||||||
|
params['host'] = p
|
||||||
|
params['core'] = model.core
|
||||||
|
mvcGroup.createMVCGroup("browse",groupId,params)
|
||||||
|
return
|
||||||
|
} catch (Exception notPersona) {
|
||||||
|
search = search.substring(0,128)
|
||||||
|
}
|
||||||
|
}
|
||||||
def uuid = UUID.randomUUID()
|
def uuid = UUID.randomUUID()
|
||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params["search-terms"] = search
|
params["search-terms"] = search
|
||||||
@@ -358,7 +363,6 @@ class MainFrameController {
|
|||||||
sf.each {
|
sf.each {
|
||||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : it))
|
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : it))
|
||||||
}
|
}
|
||||||
core.eventBus.publish(new UIPersistFilesEvent())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
@@ -485,6 +489,22 @@ class MainFrameController {
|
|||||||
startChat(p)
|
startChat(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void copyShort() {
|
||||||
|
copy(model.core.me.getHumanReadableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void copyFull() {
|
||||||
|
copy(model.core.me.toBase64())
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(String s) {
|
||||||
|
StringSelection selection = new StringSelection(s)
|
||||||
|
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
|
clipboard.setContents(selection, null)
|
||||||
|
}
|
||||||
|
|
||||||
void startChat(Persona p) {
|
void startChat(Persona p) {
|
||||||
if (!mvcGroup.getChildrenGroups().containsKey(p.getHumanReadableName())) {
|
if (!mvcGroup.getChildrenGroups().containsKey(p.getHumanReadableName())) {
|
||||||
def params = [:]
|
def params = [:]
|
||||||
@@ -505,4 +525,4 @@ class MainFrameController {
|
|||||||
core = e.getNewValue()
|
core = e.getNewValue()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,8 @@ class Ready extends AbstractLifecycleHandler {
|
|||||||
propsFile.withReader("UTF-8", {
|
propsFile.withReader("UTF-8", {
|
||||||
props.load(it)
|
props.load(it)
|
||||||
})
|
})
|
||||||
|
if (!props.containsKey("nickname"))
|
||||||
|
props.setProperty("nickname", selectNickname())
|
||||||
props = new MuWireSettings(props)
|
props = new MuWireSettings(props)
|
||||||
if (props.incompleteLocation == null)
|
if (props.incompleteLocation == null)
|
||||||
props.incompleteLocation = new File(home, "incompletes")
|
props.incompleteLocation = new File(home, "incompletes")
|
||||||
@@ -53,25 +55,7 @@ class Ready extends AbstractLifecycleHandler {
|
|||||||
props.incompleteLocation = new File(home, "incompletes")
|
props.incompleteLocation = new File(home, "incompletes")
|
||||||
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
||||||
props.updateType = System.getProperty("updateType","jar")
|
props.updateType = System.getProperty("updateType","jar")
|
||||||
def nickname
|
props.setNickname(selectNickname())
|
||||||
while (true) {
|
|
||||||
nickname = JOptionPane.showInputDialog(null,
|
|
||||||
"Your nickname is displayed when you send search results so other MuWire users can choose to trust you",
|
|
||||||
"Please choose a nickname", JOptionPane.PLAIN_MESSAGE)
|
|
||||||
if (nickname == null || nickname.trim().length() == 0) {
|
|
||||||
JOptionPane.showMessageDialog(null, "Nickname cannot be empty", "Select another nickname",
|
|
||||||
JOptionPane.WARNING_MESSAGE)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (nickname.contains("@")) {
|
|
||||||
JOptionPane.showMessageDialog(null, "Nickname cannot contain @, choose another",
|
|
||||||
"Select another nickname", JOptionPane.WARNING_MESSAGE)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
nickname = nickname.trim()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
props.setNickname(nickname)
|
|
||||||
|
|
||||||
|
|
||||||
def portableDownloads = System.getProperty("portable.downloads")
|
def portableDownloads = System.getProperty("portable.downloads")
|
||||||
@@ -116,5 +100,31 @@ class Ready extends AbstractLifecycleHandler {
|
|||||||
|
|
||||||
core.eventBus.publish(new UILoadedEvent())
|
core.eventBus.publish(new UILoadedEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String selectNickname() {
|
||||||
|
String nickname
|
||||||
|
while (true) {
|
||||||
|
nickname = JOptionPane.showInputDialog(null,
|
||||||
|
"Your nickname is displayed when you send search results so other MuWire users can choose to trust you",
|
||||||
|
"Please choose a nickname", JOptionPane.PLAIN_MESSAGE)
|
||||||
|
if (nickname == null) {
|
||||||
|
JOptionPane.showMessageDialog(null, "MuWire cannot start without a nickname and will now exit", JOptionPane.PLAIN_MESSAGE)
|
||||||
|
System.exit(0)
|
||||||
|
}
|
||||||
|
if (nickname.trim().length() == 0) {
|
||||||
|
JOptionPane.showMessageDialog(null, "Nickname cannot be empty", "Select another nickname",
|
||||||
|
JOptionPane.WARNING_MESSAGE)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (nickname.contains("@")) {
|
||||||
|
JOptionPane.showMessageDialog(null, "Nickname cannot contain @, choose another",
|
||||||
|
"Select another nickname", JOptionPane.WARNING_MESSAGE)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nickname = nickname.trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nickname
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ class BrowseModel {
|
|||||||
@Observable boolean downloadActionEnabled
|
@Observable boolean downloadActionEnabled
|
||||||
@Observable boolean viewCommentActionEnabled
|
@Observable boolean viewCommentActionEnabled
|
||||||
@Observable boolean viewCertificatesActionEnabled
|
@Observable boolean viewCertificatesActionEnabled
|
||||||
|
@Observable boolean chatActionEnabled
|
||||||
@Observable int totalResults
|
@Observable int totalResults
|
||||||
@Observable int resultCount
|
@Observable int resultCount
|
||||||
|
|
||||||
|
@@ -16,6 +16,8 @@ class I2PStatusModel {
|
|||||||
@Observable int ssuConnections
|
@Observable int ssuConnections
|
||||||
@Observable String networkStatus
|
@Observable String networkStatus
|
||||||
@Observable boolean floodfill
|
@Observable boolean floodfill
|
||||||
|
@Observable String myCountry
|
||||||
|
@Observable boolean strictCountry
|
||||||
@Observable int participatingTunnels
|
@Observable int participatingTunnels
|
||||||
@Observable int activePeers
|
@Observable int activePeers
|
||||||
@Observable int receiveBps
|
@Observable int receiveBps
|
||||||
|
@@ -363,6 +363,8 @@ class MainFrameModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
|
if (e.source == "PersisterService")
|
||||||
|
return
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared << e.loadedFile
|
shared << e.loadedFile
|
||||||
loadedFiles = shared.size()
|
loadedFiles = shared.size()
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.muwire.gui
|
package com.muwire.gui
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonModel
|
import griffon.core.artifact.GriffonModel
|
||||||
@@ -21,6 +22,6 @@ class SharedFileModel {
|
|||||||
public void mvcGroupInit(Map<String,String> args) {
|
public void mvcGroupInit(Map<String,String> args) {
|
||||||
searchers.addAll(sf.getSearches())
|
searchers.addAll(sf.getSearches())
|
||||||
downloaders.addAll(sf.getDownloaders())
|
downloaders.addAll(sf.getDownloaders())
|
||||||
certificates.addAll(core.certificateManager.byInfoHash.getOrDefault(sf.infoHash,[]))
|
certificates.addAll(core.certificateManager.byInfoHash.getOrDefault(new InfoHash(sf.getRoot()),[]))
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -68,6 +68,7 @@ class BrowseView {
|
|||||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||||
button(text : "View Comment", enabled : bind{model.viewCommentActionEnabled}, viewCommentAction)
|
button(text : "View Comment", enabled : bind{model.viewCommentActionEnabled}, viewCommentAction)
|
||||||
button(text : "View Certificates", enabled : bind{model.viewCertificatesActionEnabled}, viewCertificatesAction)
|
button(text : "View Certificates", enabled : bind{model.viewCertificatesActionEnabled}, viewCertificatesAction)
|
||||||
|
button(text : "Chat", enabled : bind {model.chatActionEnabled}, chatAction)
|
||||||
button(text : "Dismiss", dismissAction)
|
button(text : "Dismiss", dismissAction)
|
||||||
label(text : "Download sequentially")
|
label(text : "Download sequentially")
|
||||||
sequentialDownloadCheckbox = checkBox()
|
sequentialDownloadCheckbox = checkBox()
|
||||||
|
@@ -5,6 +5,7 @@ import griffon.inject.MVCMember
|
|||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
import javax.swing.JDialog
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JFrame
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
@@ -20,18 +21,15 @@ class ChatMonitorView {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
ChatMonitorModel model
|
ChatMonitorModel model
|
||||||
|
|
||||||
def mainFrame
|
def window
|
||||||
def dialog
|
|
||||||
def panel
|
|
||||||
def roomsTable
|
def roomsTable
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
mainFrame = application.windowManager.findWindow("main-frame")
|
|
||||||
int rowHeight = application.context.getAsInt("row-height")
|
int rowHeight = application.context.getAsInt("row-height")
|
||||||
dialog = new JDialog(mainFrame, "Chat Monitor", false)
|
|
||||||
dialog.setResizable(true)
|
|
||||||
|
|
||||||
panel = builder.panel {
|
window = builder.frame (visible : false, locationRelativeTo : null,
|
||||||
|
defaultCloseOperation : JFrame.DISPOSE_ON_CLOSE,
|
||||||
|
iconImage : builder.imageIcon("/MuWire-48x48.png").image){
|
||||||
borderLayout()
|
borderLayout()
|
||||||
panel(constraints : BorderLayout.NORTH) {
|
panel(constraints : BorderLayout.NORTH) {
|
||||||
label("Chat rooms with unread messages")
|
label("Chat rooms with unread messages")
|
||||||
@@ -53,15 +51,12 @@ class ChatMonitorView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void mvcGroupInit(Map<String,String> args) {
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
dialog.getContentPane().add(panel)
|
window.addWindowListener(new WindowAdapter() {
|
||||||
dialog.pack()
|
|
||||||
dialog.setLocationRelativeTo(mainFrame)
|
|
||||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
|
||||||
dialog.addWindowListener(new WindowAdapter() {
|
|
||||||
public void windowClosed(WindowEvent e) {
|
public void windowClosed(WindowEvent e) {
|
||||||
mvcGroup.destroy()
|
mvcGroup.destroy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
dialog.show()
|
window.pack()
|
||||||
|
window.setVisible(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -45,6 +45,10 @@ class I2PStatusView {
|
|||||||
label(text : bind {model.floodfill}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
label(text : bind {model.floodfill}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||||
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:2, anchor : GridBagConstraints.LINE_END))
|
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:2, anchor : GridBagConstraints.LINE_END))
|
||||||
|
label(text : "Our Country", constraints : gbc(gridx: 0, gridy: 3, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
|
label(text : bind {model.myCountry}, constraints : gbc(gridx : 1, gridy: 3, anchor : GridBagConstraints.LINE_END))
|
||||||
|
label(text : "Strict Country", constraints : gbc(gridx:0, gridy:4, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
|
label(text : bind {model.strictCountry}, constraints : gbc(gridx : 1, gridy : 4, anchor : GridBagConstraints.LINE_END))
|
||||||
}
|
}
|
||||||
panel(border : titledBorder(title : "Connections", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
panel(border : titledBorder(title : "Connections", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
constraints : gbc(gridx: 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
constraints : gbc(gridx: 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||||
|
@@ -35,6 +35,7 @@ import javax.swing.tree.TreePath
|
|||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
@@ -289,7 +290,7 @@ class MainFrameView {
|
|||||||
closureColumn(header : "Comments", preferredWidth : 50, type : Boolean, read : {it.getComment() != null})
|
closureColumn(header : "Comments", preferredWidth : 50, type : Boolean, read : {it.getComment() != null})
|
||||||
closureColumn(header : "Certified", preferredWidth : 50, type : Boolean, read : {
|
closureColumn(header : "Certified", preferredWidth : 50, type : Boolean, read : {
|
||||||
Core core = application.context.get("core")
|
Core core = application.context.get("core")
|
||||||
core.certificateManager.hasLocalCertificate(it.getInfoHash())
|
core.certificateManager.hasLocalCertificate(new InfoHash(it.getRoot()))
|
||||||
})
|
})
|
||||||
closureColumn(header : "Search Hits", preferredWidth: 50, type : Integer, read : {it.getHits()})
|
closureColumn(header : "Search Hits", preferredWidth: 50, type : Integer, read : {it.getHits()})
|
||||||
closureColumn(header : "Downloaders", preferredWidth: 50, type : Integer, read : {it.getDownloaders().size()})
|
closureColumn(header : "Downloaders", preferredWidth: 50, type : Integer, read : {it.getDownloaders().size()})
|
||||||
@@ -499,7 +500,11 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
panel (border: etchedBorder(), constraints : BorderLayout.SOUTH) {
|
panel (border: etchedBorder(), constraints : BorderLayout.SOUTH) {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
label(text : bind {model.me}, constraints: BorderLayout.CENTER)
|
panel (constraints : BorderLayout.WEST) {
|
||||||
|
label(text : bind {model.me})
|
||||||
|
button(text : "Copy Short", copyShortAction)
|
||||||
|
button(text : "Copy Full", copyFullAction)
|
||||||
|
}
|
||||||
panel (constraints : BorderLayout.EAST) {
|
panel (constraints : BorderLayout.EAST) {
|
||||||
label("Connections:")
|
label("Connections:")
|
||||||
label(text : bind {model.connections})
|
label(text : bind {model.connections})
|
||||||
@@ -907,7 +912,7 @@ class MainFrameView {
|
|||||||
String roots = ""
|
String roots = ""
|
||||||
for (Iterator<SharedFile> iterator = selectedFiles.iterator(); iterator.hasNext(); ) {
|
for (Iterator<SharedFile> iterator = selectedFiles.iterator(); iterator.hasNext(); ) {
|
||||||
SharedFile selected = iterator.next()
|
SharedFile selected = iterator.next()
|
||||||
String root = Base64.encode(selected.infoHash.getRoot())
|
String root = Base64.encode(selected.getRoot())
|
||||||
roots += root
|
roots += root
|
||||||
if (iterator.hasNext())
|
if (iterator.hasNext())
|
||||||
roots += "\n"
|
roots += "\n"
|
||||||
|
@@ -1,183 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonView
|
|
||||||
import griffon.inject.MVCMember
|
|
||||||
import griffon.metadata.ArtifactProviderFor
|
|
||||||
|
|
||||||
import javax.swing.JDialog
|
|
||||||
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
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonView)
|
|
||||||
class OptionsView {
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
FactoryBuilderSupport builder
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
OptionsModel model
|
|
||||||
|
|
||||||
def d
|
|
||||||
def p
|
|
||||||
def i
|
|
||||||
def u
|
|
||||||
def bandwidth
|
|
||||||
def trust
|
|
||||||
|
|
||||||
def retryField
|
|
||||||
def updateField
|
|
||||||
def autoDownloadUpdateCheckbox
|
|
||||||
def shareDownloadedCheckbox
|
|
||||||
|
|
||||||
def inboundLengthField
|
|
||||||
def inboundQuantityField
|
|
||||||
def outboundLengthField
|
|
||||||
def outboundQuantityField
|
|
||||||
def i2pUDPPortField
|
|
||||||
def i2pNTCPPortField
|
|
||||||
|
|
||||||
def lnfField
|
|
||||||
def monitorCheckbox
|
|
||||||
def fontField
|
|
||||||
def clearCancelledDownloadsCheckbox
|
|
||||||
def clearFinishedDownloadsCheckbox
|
|
||||||
def excludeLocalResultCheckbox
|
|
||||||
def showSearchHashesCheckbox
|
|
||||||
|
|
||||||
def inBwField
|
|
||||||
def outBwField
|
|
||||||
|
|
||||||
def allowUntrustedCheckbox
|
|
||||||
def allowTrustListsCheckbox
|
|
||||||
def trustListIntervalField
|
|
||||||
|
|
||||||
def buttonsPanel
|
|
||||||
|
|
||||||
def mainFrame
|
|
||||||
|
|
||||||
void initUI() {
|
|
||||||
mainFrame = application.windowManager.findWindow("main-frame")
|
|
||||||
d = new JDialog(mainFrame, "Options", true)
|
|
||||||
d.setResizable(false)
|
|
||||||
p = builder.panel {
|
|
||||||
gridBagLayout()
|
|
||||||
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 0))
|
|
||||||
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 0))
|
|
||||||
label(text : "minutes", constraints : gbc(gridx : 2, gridy: 0))
|
|
||||||
|
|
||||||
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
|
|
||||||
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
|
|
||||||
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
|
|
||||||
|
|
||||||
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
|
|
||||||
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
|
|
||||||
|
|
||||||
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 {
|
|
||||||
gridBagLayout()
|
|
||||||
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
|
||||||
label(text : "Inbound Length", constraints : gbc(gridx:0, gridy:1))
|
|
||||||
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:1))
|
|
||||||
label(text : "Inbound Quantity", constraints : gbc(gridx:0, gridy:2))
|
|
||||||
inboundQuantityField = textField(text : bind {model.inboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:2))
|
|
||||||
label(text : "Outbound Length", constraints : gbc(gridx:0, gridy:3))
|
|
||||||
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:3))
|
|
||||||
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
|
|
||||||
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
|
|
||||||
|
|
||||||
Core core = application.context.get("core")
|
|
||||||
if (core.router != null) {
|
|
||||||
label(text : "TCP Port", constraints : gbc(gridx :0, gridy: 5))
|
|
||||||
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:5))
|
|
||||||
label(text : "UDP Port", constraints : gbc(gridx :0, gridy: 6))
|
|
||||||
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:6))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
u = builder.panel {
|
|
||||||
gridBagLayout()
|
|
||||||
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
|
||||||
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
|
|
||||||
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 : "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))
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
trust = builder.panel {
|
|
||||||
gridBagLayout()
|
|
||||||
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
|
|
||||||
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
|
|
||||||
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 1))
|
|
||||||
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 1))
|
|
||||||
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:2))
|
|
||||||
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:2))
|
|
||||||
label(text : "hours", constraints : gbc(gridx: 2, gridy:2))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
buttonsPanel = builder.panel {
|
|
||||||
gridBagLayout()
|
|
||||||
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
|
|
||||||
button(text : "Cancel", constraints : gbc(gridx : 2, gridy: 2), cancelAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mvcGroupInit(Map<String,String> args) {
|
|
||||||
def tabbedPane = new JTabbedPane()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
tabbedPane.addTab("Trust", trust)
|
|
||||||
|
|
||||||
JPanel panel = new JPanel()
|
|
||||||
panel.setLayout(new BorderLayout())
|
|
||||||
panel.add(tabbedPane, BorderLayout.CENTER)
|
|
||||||
panel.add(buttonsPanel, BorderLayout.SOUTH)
|
|
||||||
|
|
||||||
d.getContentPane().add(panel)
|
|
||||||
d.pack()
|
|
||||||
d.setLocationRelativeTo(mainFrame)
|
|
||||||
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
|
||||||
d.addWindowListener(new WindowAdapter() {
|
|
||||||
public void windowClosed(WindowEvent e) {
|
|
||||||
mvcGroup.destroy()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
d.show()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,6 +3,7 @@ mainClassName = 'com.muwire.hostcache.HostCache'
|
|||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile "net.i2p:i2p:${i2pVersion}"
|
||||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
BIN
images/i2cp_config.png
Normal file
After Width: | Height: | Size: 13 KiB |
@@ -1,2 +1,5 @@
|
|||||||
apply plugin: 'application'
|
apply plugin: 'application'
|
||||||
mainClassName = 'com.muwire.pinger.Pinger'
|
mainClassName = 'com.muwire.pinger.Pinger'
|
||||||
|
dependencies {
|
||||||
|
compile "net.i2p:i2p:${i2pVersion}"
|
||||||
|
}
|
||||||
|
@@ -47,6 +47,17 @@ task pluginConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task clientsConfig {
|
||||||
|
doLast {
|
||||||
|
def binding = [ "libname" : libDirPath(), "version" : project.version ]
|
||||||
|
def templateFile = new File("$projectDir/templates/clients.config.template")
|
||||||
|
def engine = new groovy.text.SimpleTemplateEngine()
|
||||||
|
def output = engine.createTemplate(templateFile).make(binding)
|
||||||
|
def outputFile = new File(zipDir, "clients.config")
|
||||||
|
outputFile.text = output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
task pluginDir {
|
task pluginDir {
|
||||||
dependsOn ':webui:assemble'
|
dependsOn ':webui:assemble'
|
||||||
doLast { task ->
|
doLast { task ->
|
||||||
@@ -59,10 +70,13 @@ task pluginDir {
|
|||||||
java.nio.file.Files.copy(it.toPath(), dest.toPath())
|
java.nio.file.Files.copy(it.toPath(), dest.toPath())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
def jarFile = webapp.configurations.jarArtifact.getAllArtifacts().file[0]
|
||||||
|
def dest = new File(libDir, jarFile.getName())
|
||||||
|
java.nio.file.Files.copy(jarFile.toPath(), dest.toPath())
|
||||||
|
|
||||||
webAppDir.mkdirs()
|
webAppDir.mkdirs()
|
||||||
def warFile = webapp.configurations.warArtifact.getAllArtifacts().file[0]
|
def warFile = webapp.configurations.warArtifact.getAllArtifacts().file[0]
|
||||||
def dest = new File(webAppDir, "MuWire.war")
|
dest = new File(webAppDir, "MuWire.war")
|
||||||
java.nio.file.Files.copy(warFile.toPath(), dest.toPath())
|
java.nio.file.Files.copy(warFile.toPath(), dest.toPath())
|
||||||
"zip -d ${dest.toString()} *.jar".execute()
|
"zip -d ${dest.toString()} *.jar".execute()
|
||||||
}
|
}
|
||||||
@@ -80,7 +94,7 @@ task pack {
|
|||||||
doLast {
|
doLast {
|
||||||
if (project.pack200 == "true") {
|
if (project.pack200 == "true") {
|
||||||
libDir.listFiles().stream().filter( { it.getName().endsWith(".jar") } ).
|
libDir.listFiles().stream().filter( { it.getName().endsWith(".jar") } ).
|
||||||
filter({it.length() > 512*1024}).forEach {
|
forEach {
|
||||||
println "packing $it"
|
println "packing $it"
|
||||||
def name = it.toString()
|
def name = it.toString()
|
||||||
println "pack200 --no-gzip ${name}.pack $name".execute().text
|
println "pack200 --no-gzip ${name}.pack $name".execute().text
|
||||||
@@ -117,6 +131,7 @@ task sign {
|
|||||||
}
|
}
|
||||||
|
|
||||||
webappConfig.dependsOn pluginDir
|
webappConfig.dependsOn pluginDir
|
||||||
|
clientsConfig.dependsOn webappConfig
|
||||||
pack.dependsOn webappConfig
|
pack.dependsOn webappConfig
|
||||||
pluginZip.dependsOn(webappConfig,pluginConfig,pack)
|
pluginZip.dependsOn(webappConfig,pluginConfig,pack)
|
||||||
sign.dependsOn pluginZip
|
sign.dependsOn pluginZip
|
||||||
|
6
plug/templates/clients.config.template
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
clientApp.0.main=com.muwire.webui.MuWireClient
|
||||||
|
clientApp.0.name=MuWire
|
||||||
|
clientApp.0.startOnLoad=true
|
||||||
|
clientApp.0.delay=5
|
||||||
|
clientApp.0.classpath=${libname}
|
||||||
|
clientApp.0.args=version=${version} home="\$PLUGIN"
|
@@ -7,8 +7,9 @@ signer=${signer}
|
|||||||
websiteURL=${websiteURL}
|
websiteURL=${websiteURL}
|
||||||
updateURL.su3=${updateURLsu3}
|
updateURL.su3=${updateURLsu3}
|
||||||
min-i2p-version=${i2pVersion}
|
min-i2p-version=${i2pVersion}
|
||||||
min-java-version=1.8
|
min-java-version=8
|
||||||
consoleLinkName=MuWire
|
consoleLinkName=MuWire
|
||||||
consoleLinkTooltip=Anonymous File Sharing
|
consoleLinkTooltip=Anonymous File Sharing
|
||||||
consoleLinkURL=/MuWire
|
consoleLinkURL=/MuWire/
|
||||||
|
console-icon=images/muwire.png
|
||||||
<% println "date="+System.currentTimeMillis() %>
|
<% println "date="+System.currentTimeMillis() %>
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
apply plugin : 'application'
|
apply plugin : 'application'
|
||||||
mainClassName = 'com.muwire.update.UpdateServer'
|
mainClassName = 'com.muwire.update.UpdateServer'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
|
dependencies {
|
||||||
|
compile "net.i2p:i2p:${i2pVersion}"
|
||||||
|
}
|
||||||
|
12
webui/.gitignore
vendored
@@ -1,12 +0,0 @@
|
|||||||
Thumbs.db
|
|
||||||
.DS_Store
|
|
||||||
.gradle
|
|
||||||
build/
|
|
||||||
out/
|
|
||||||
.idea
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.classpath
|
|
@@ -1,109 +1,117 @@
|
|||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
maven { url "https://repo.grails.org/grails/core" }
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
|
|
||||||
classpath "org.grails.plugins:hibernate5:7.0.0"
|
|
||||||
classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:2.0"
|
|
||||||
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:3.0.10"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
version "0.1"
|
|
||||||
group "webui"
|
|
||||||
|
|
||||||
apply plugin:"eclipse"
|
|
||||||
apply plugin:"idea"
|
|
||||||
apply plugin:"war"
|
|
||||||
apply plugin:"org.grails.grails-web"
|
|
||||||
apply plugin:"com.github.erdi.webdriver-binaries"
|
|
||||||
apply plugin:"org.grails.grails-gsp"
|
|
||||||
apply plugin:"com.bertramlabs.asset-pipeline"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url "https://repo.grails.org/grails/core" }
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
developmentOnly
|
|
||||||
runtimeClasspath {
|
|
||||||
extendsFrom developmentOnly
|
|
||||||
}
|
|
||||||
warArtifact
|
warArtifact
|
||||||
|
jarArtifact
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin : 'war'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(":core")
|
providedCompile(project(':core')) {
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
transitive = false
|
||||||
compile "org.springframework.boot:spring-boot-starter-logging"
|
}
|
||||||
compile "org.springframework.boot:spring-boot-autoconfigure"
|
compile fileTree("../i2pjars") { include '*.jar' }
|
||||||
compile "org.grails:grails-core"
|
|
||||||
compile "org.springframework.boot:spring-boot-starter-actuator"
|
|
||||||
provided "org.springframework.boot:spring-boot-starter-tomcat"
|
|
||||||
compile "org.grails:grails-web-boot"
|
|
||||||
compile "org.grails:grails-logging"
|
|
||||||
compile "org.grails:grails-plugin-rest"
|
|
||||||
compile "org.grails:grails-plugin-databinding"
|
|
||||||
compile "org.grails:grails-plugin-i18n"
|
|
||||||
compile "org.grails:grails-plugin-services"
|
|
||||||
compile "org.grails:grails-plugin-url-mappings"
|
|
||||||
compile "org.grails:grails-plugin-interceptors"
|
|
||||||
compile "org.grails.plugins:cache"
|
|
||||||
compile "org.grails.plugins:async"
|
|
||||||
compile "org.grails.plugins:scaffolding"
|
|
||||||
compile "org.grails.plugins:events"
|
|
||||||
compile "org.grails.plugins:hibernate5"
|
|
||||||
compile "org.hibernate:hibernate-core:5.4.0.Final"
|
|
||||||
compile "org.grails.plugins:gsp"
|
|
||||||
compileOnly "io.micronaut:micronaut-inject-groovy"
|
|
||||||
console "org.grails:grails-console"
|
|
||||||
profile "org.grails.profiles:web"
|
|
||||||
runtime "org.glassfish.web:el-impl:2.1.2-b03"
|
|
||||||
runtime "com.h2database:h2"
|
|
||||||
runtime "org.apache.tomcat:tomcat-jdbc"
|
|
||||||
runtime "javax.xml.bind:jaxb-api:2.3.0"
|
|
||||||
runtime "com.bertramlabs.plugins:asset-pipeline-grails:3.0.10"
|
|
||||||
testCompile "org.grails:grails-gorm-testing-support"
|
|
||||||
testCompile "org.mockito:mockito-core"
|
|
||||||
testCompile "org.grails:grails-web-testing-support"
|
|
||||||
testCompile "org.grails.plugins:geb"
|
|
||||||
testCompile "org.seleniumhq.selenium:selenium-remote-driver:3.14.0"
|
|
||||||
testCompile "org.seleniumhq.selenium:selenium-api:3.14.0"
|
|
||||||
testCompile "org.seleniumhq.selenium:selenium-support:3.14.0"
|
|
||||||
testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:3.14.0"
|
|
||||||
testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:3.14.0"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bootRun {
|
war {
|
||||||
jvmArgs(
|
from 'src/main/css'
|
||||||
'-Dspring.output.ansi.enabled=always',
|
from ('src/main/images', {
|
||||||
'-noverify',
|
into "images"
|
||||||
'-XX:TieredStopAtLevel=1',
|
})
|
||||||
'-Xmx1024m')
|
from ('src/main/js', {
|
||||||
sourceResources sourceSets.main
|
into "js"
|
||||||
String springProfilesActive = 'spring.profiles.active'
|
})
|
||||||
systemProperty springProfilesActive, System.getProperty(springProfilesActive)
|
from ('src/main/resources', {
|
||||||
|
into "WEB-INF/classes/com/muwire/webui"
|
||||||
|
})
|
||||||
|
webInf {
|
||||||
|
from "$buildDir/compiledJsps"
|
||||||
|
into "classes"
|
||||||
|
}
|
||||||
|
excludes = new HashSet(['**/*.jsp', '**/*.jsi'])
|
||||||
|
webXml = file("$buildDir/tmp_jsp/web.xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
webdriverBinaries {
|
task precompileJsp {
|
||||||
chromedriver '2.45.0'
|
doLast {
|
||||||
geckodriver '0.24.0'
|
ant.taskdef (name : 'jasper',
|
||||||
|
classname: 'org.apache.jasper.JspC',
|
||||||
|
classpath: configurations.compile.asPath)
|
||||||
|
def generated = new File("$buildDir/tmp_jsp")
|
||||||
|
generated.mkdirs()
|
||||||
|
ant.jasper(package: 'com.muwire.webui',
|
||||||
|
classPath : sourceSets.main.runtimeClasspath.asPath,
|
||||||
|
uriroot: webAppDir,
|
||||||
|
outputDir: "$buildDir/tmp_jsp",
|
||||||
|
compilerSourceVM: project.sourceCompatibility,
|
||||||
|
compilerTargetVM: project.targetCompatibility,
|
||||||
|
webXmlFragment: "$buildDir/tmp_jsp/web.xml.jasper")
|
||||||
|
def output = new File("$buildDir/compiledJsps")
|
||||||
|
output.mkdirs()
|
||||||
|
ant.javac(srcDir: 'build/tmp_jsp',
|
||||||
|
classPath : sourceSets.main.runtimeClasspath.asPath,
|
||||||
|
debug : true,
|
||||||
|
includeAntRuntime : false,
|
||||||
|
deprecation : "on",
|
||||||
|
source: project.sourceCompatibility,
|
||||||
|
target: project.targetCompatibility,
|
||||||
|
destDir:file("$buildDir/compiledJsps"))
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(Test) {
|
task generateWebXML {
|
||||||
systemProperty "geb.env", System.getProperty('geb.env')
|
doLast {
|
||||||
systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest")
|
def template = new File("$projectDir/templates/web.xml.template")
|
||||||
systemProperty "webdriver.chrome.driver", System.getProperty('webdriver.chrome.driver')
|
def templateText = template.text
|
||||||
systemProperty "webdriver.gecko.driver", System.getProperty('webdriver.gecko.driver')
|
def jasper = new File("$buildDir/tmp_jsp/web.xml.jasper")
|
||||||
|
templateText = templateText.replaceAll("__JASPER__", jasper.text)
|
||||||
|
templateText = templateText.replaceAll("__VERSION__", project.version)
|
||||||
|
def webXml = new File("$buildDir/tmp_jsp/web.xml")
|
||||||
|
webXml.text = templateText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compile the po files and put them in the jar
|
||||||
assets {
|
task bundle {
|
||||||
minifyJs = true
|
doLast {
|
||||||
minifyCss = true
|
// run bundle-messages.sh
|
||||||
|
println 'starting bundle-messages'
|
||||||
|
println "webui/bundle-messages.sh".execute().text
|
||||||
|
println 'finished bundle-messages'
|
||||||
|
// compile java files in build/messages-src
|
||||||
|
ant.mkdir(dir: "$buildDir/compiledMessages")
|
||||||
|
ant.javac(srcDir: "$buildDir/messages-src",
|
||||||
|
classPath : sourceSets.main.runtimeClasspath.asPath,
|
||||||
|
debug : false,
|
||||||
|
includeAntRuntime : false,
|
||||||
|
source: project.sourceCompatibility,
|
||||||
|
target: project.targetCompatibility,
|
||||||
|
destDir:file("$buildDir/compiledMessages"))
|
||||||
|
// add resulting classes to build/libs/webui-(version).jar
|
||||||
|
ant.jar(destfile: "$buildDir/libs/webui-${version}.jar",
|
||||||
|
basedir: "$buildDir/compiledMessages",
|
||||||
|
includes: '**/messages_*.class',
|
||||||
|
update: 'true')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rebuild the english po file for uploading to transifex
|
||||||
|
task poupdate {
|
||||||
|
doLast {
|
||||||
|
// run bundle-messages.sh
|
||||||
|
println 'starting bundle-messages -p'
|
||||||
|
println "webui/bundle-messages.sh -p".execute().text
|
||||||
|
println 'finished bundle-messages -p'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
precompileJsp.dependsOn compileJava
|
||||||
|
generateWebXML.dependsOn precompileJsp
|
||||||
|
bundle.dependsOn precompileJsp
|
||||||
|
poupdate.dependsOn precompileJsp
|
||||||
|
war.dependsOn generateWebXML, bundle
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
warArtifact war
|
warArtifact war
|
||||||
|
jarArtifact jar
|
||||||
}
|
}
|
||||||
|
143
webui/bundle-messages.sh
Executable file
@@ -0,0 +1,143 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Update messages_xx.po and messages_xx.class files,
|
||||||
|
# from both java and jsp sources.
|
||||||
|
# Requires installed programs xgettext, msgfmt, msgmerge, and find.
|
||||||
|
#
|
||||||
|
# usage:
|
||||||
|
# bundle-messages.sh (generates the resource bundle from the .po file)
|
||||||
|
# bundle-messages.sh -p (updates the .po file from the source tags, then generates the resource bundle)
|
||||||
|
#
|
||||||
|
# zzz - public domain
|
||||||
|
#
|
||||||
|
cd `dirname $0`
|
||||||
|
echo "bundle messages in $PWD"
|
||||||
|
CLASS=com.muwire.webui.messages
|
||||||
|
TMPFILE=build/javafiles.txt
|
||||||
|
export TZ=UTC
|
||||||
|
RC=0
|
||||||
|
|
||||||
|
if ! $(which javac > /dev/null 2>&1); then
|
||||||
|
export JAVAC=${JAVA_HOME}/../bin/javac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "-p" ]
|
||||||
|
then
|
||||||
|
POUPDATE=1
|
||||||
|
LG2=en
|
||||||
|
fi
|
||||||
|
|
||||||
|
# on windows, one must specify the path of commnad find
|
||||||
|
# since windows has its own version of find.
|
||||||
|
if which find|grep -q -i windows ; then
|
||||||
|
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||||
|
fi
|
||||||
|
# Fast mode - update ondemond
|
||||||
|
# set LG2 to the language you need in environment variables to enable this
|
||||||
|
|
||||||
|
# add ../src/ so the refs will work in the po file
|
||||||
|
JPATHS="src/main/java/ build/tmp_jsp"
|
||||||
|
for i in locale/messages_*.po
|
||||||
|
do
|
||||||
|
# get language
|
||||||
|
LG=${i#locale/messages_}
|
||||||
|
LG=${LG%.po}
|
||||||
|
|
||||||
|
# skip, if specified
|
||||||
|
if [ $LG2 ]; then
|
||||||
|
[ $LG != $LG2 ] && continue || echo INFO: Language update is set to [$LG2] only.
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$POUPDATE" = "1" ]
|
||||||
|
then
|
||||||
|
# make list of java files newer than the .po file
|
||||||
|
find $JPATHS -name *.java -newer $i > $TMPFILE
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -s build/compiledJsps/com/muwire/webui/messages/messages_$LG.class -a \
|
||||||
|
build/compiledJsps/com/muwire/webui/messages/messages_$LG.class -nt $i -a \
|
||||||
|
! -s $TMPFILE ]
|
||||||
|
then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$POUPDATE" = "1" ]
|
||||||
|
then
|
||||||
|
echo "Updating the $i file from the tags..."
|
||||||
|
# extract strings from java and jsp files, and update messages.po files
|
||||||
|
# translate calls must be one of the forms:
|
||||||
|
# _("foo")
|
||||||
|
# _t("foo")
|
||||||
|
# _x("foo")
|
||||||
|
# intl._t("foo")
|
||||||
|
# In a jsp, you must use a helper or handler that has the context set.
|
||||||
|
# To start a new translation, copy the header from an old translation to the new .po file,
|
||||||
|
# then ant distclean updater.
|
||||||
|
find $JPATHS -name *.java > $TMPFILE
|
||||||
|
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||||
|
--keyword=_ --keyword=_t --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||||
|
-o ${i}t
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "ERROR - xgettext failed on ${i}, not updating translations"
|
||||||
|
rm -f ${i}t
|
||||||
|
RC=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
msgmerge -U --backup=none $i ${i}t
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "ERROR - msgmerge failed on ${i}, not updating translations"
|
||||||
|
rm -f ${i}t
|
||||||
|
RC=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
rm -f ${i}t
|
||||||
|
# so we don't do this again
|
||||||
|
touch $i
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$LG" != "en" ]
|
||||||
|
then
|
||||||
|
# only generate for non-source language
|
||||||
|
echo "Generating ${CLASS}_$LG ResourceBundle..."
|
||||||
|
|
||||||
|
msgfmt -V | grep -q -E ' 0\.((19)|[2-9])'
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
# slow way
|
||||||
|
# convert to class files in WEB-INF/classes
|
||||||
|
msgfmt --java --statistics -r $CLASS -l $LG -d build/compiledJsps $i
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "ERROR - msgfmt failed on ${i}, not updating translations"
|
||||||
|
# msgfmt leaves the class file there so the build would work the next time
|
||||||
|
find build/compiledJsps -name messages_${LG}.class -exec rm -f {} \;
|
||||||
|
RC=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# fast way
|
||||||
|
# convert to java files in build/messages-src
|
||||||
|
TD=build/messages-src-tmp/
|
||||||
|
TDX=$TD/com/muwire/webui
|
||||||
|
TD2=build/messages-src
|
||||||
|
TDY=$TD2/com/muwire/webui
|
||||||
|
rm -rf $TD
|
||||||
|
mkdir -p $TD $TDY
|
||||||
|
msgfmt --java --statistics --source -r $CLASS -l $LG -d $TD $i
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "ERROR - msgfmt failed on ${i}, not updating translations"
|
||||||
|
# msgfmt leaves the class file there so the build would work the next time
|
||||||
|
find WEB-INF/classes -name messages_${LG}.class -exec rm -f {} \;
|
||||||
|
RC=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
mv $TDX/messages_$LG.java $TDY
|
||||||
|
rm -rf $TD
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
rm -f $TMPFILE
|
||||||
|
exit $RC
|
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.79" cy="46.789" r="45.374"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path fill="#FEB672" d="M71.126,29.576c0,0.414-0.337,0.75-0.75,0.75h-3.25v3.25c0,0.415-0.337,0.751-0.751,0.751h-1.499
|
|
||||||
c-0.415,0-0.75-0.336-0.75-0.751v-3.25h-3.251c-0.414,0-0.749-0.336-0.749-0.75v-1.498c0-0.416,0.335-0.752,0.749-0.752h3.251
|
|
||||||
v-3.249c0-0.414,0.335-0.75,0.75-0.75h1.499c0.414,0,0.751,0.336,0.751,0.75v3.249h3.25c0.413,0,0.75,0.336,0.75,0.752V29.576z"/>
|
|
||||||
</g>
|
|
||||||
<path fill="#FEB672" d="M50.42,60.386c0.554,1.467,0.855,1.951,1.493,3.44c0.271,0.627,0.523,1.228,0.649,1.518
|
|
||||||
c0.049,0.117,0.036,0.248-0.033,0.355c-0.172,0.259-0.552,0.747-1.181,1.086c-1.098,0.594-3.409,0.809-4.555,0.812h-0.006
|
|
||||||
c-1.146-0.004-3.457-0.219-4.558-0.812c-0.627-0.339-1.006-0.827-1.177-1.086c-0.07-0.107-0.083-0.238-0.032-0.355
|
|
||||||
c0.123-0.29,0.376-0.891,0.646-1.518c0.64-1.489,0.941-1.974,1.495-3.44c0.485-1.294,0.729-3.175,0.745-4.593
|
|
||||||
c0.006-0.604-0.03-1.122-0.106-1.476c-0.121-0.56-0.501-1.412-0.907-2.042c-0.548-0.849-1.527-1.583-2.157-1.919
|
|
||||||
c-0.475-0.254-1.984-0.817-2.576-1.146c-0.755-0.416-1.739-1.067-2.399-1.584c-0.735-0.574-2.182-1.992-2.746-2.695
|
|
||||||
c-1.084-1.344-2.083-2.922-2.565-4.62c-0.601-2.106-0.576-3.009-0.657-3.688c-0.014-0.117,0.075-0.222,0.191-0.227
|
|
||||||
c0.73-0.025,3.854-0.093,16.809-0.081c12.953-0.012,16.076,0.056,16.806,0.081c0.118,0.005,0.206,0.109,0.191,0.227
|
|
||||||
c-0.08,0.68-0.057,1.582-0.654,3.688c-0.486,1.698-1.483,3.276-2.567,4.62c-0.564,0.703-2.011,2.121-2.746,2.695
|
|
||||||
c-0.661,0.517-1.646,1.168-2.399,1.584c-0.594,0.328-2.102,0.892-2.576,1.146c-0.63,0.336-1.608,1.07-2.158,1.919
|
|
||||||
c-0.405,0.63-0.785,1.482-0.904,2.042c-0.079,0.354-0.112,0.872-0.107,1.476C49.69,57.211,49.935,59.092,50.42,60.386z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.88" cy="46.792" r="45.374"/>
|
|
||||||
</g>
|
|
||||||
<path fill="#FEB672" d="M64.379,40.958v24.062c0,1.208-0.979,2.188-2.188,2.188H31.567c-1.208,0-2.188-0.979-2.188-2.188V28.562
|
|
||||||
c0-1.208,0.98-2.188,2.188-2.188h18.229v12.396c0,1.208,0.979,2.188,2.188,2.188H64.379z M55.629,44.604
|
|
||||||
c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9
|
|
||||||
c0.41,0,0.729-0.319,0.729-0.729V44.604z M55.629,50.438c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729
|
|
||||||
v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9c0.41,0,0.729-0.319,0.729-0.729V50.438z M55.629,56.271
|
|
||||||
c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9
|
|
||||||
c0.41,0,0.729-0.319,0.729-0.729V56.271z M63.468,38.042H52.713V27.287c0.318,0.205,0.592,0.41,0.82,0.638l9.297,9.297
|
|
||||||
C63.059,37.449,63.264,37.723,63.468,38.042z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 5.4 KiB |
@@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="500">
|
|
||||||
<desc iVinci="yes" version="4.5" gridStep="20" showGrid="no" snapToGrid="no" codePlatform="0"/>
|
|
||||||
<g id="Layer1" opacity="1">
|
|
||||||
<g id="Shape1">
|
|
||||||
<desc shapeID="1" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-74.3391,-50.75,148.678,101.5)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(4.79624,0,0,4.79624,500,250)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
|
|
||||||
<path id="shapePath1" d="M527.264,491.011 C544.051,488.613 563.236,483.817 572.829,479.021 C582.421,474.224 589.615,467.03 589.615,462.234 C589.615,462.234 587.217,457.438 584.819,452.641 C580.023,445.447 575.227,435.854 563.236,409.475 C558.44,397.484 547.589,366.072 544.051,351.92 C540.386,330.773 540.051,308.254 544.051,287.171 C547.531,274.839 552.314,262.919 560.838,253.597 C570.402,240.945 581.622,228.467 596.81,222.422 C644.094,203.599 699.929,162.469 728.707,116.904 C738.299,100.117 742.876,92.923 746.372,83.3305 C755.023,59.5988 762.66,34.3876 762.28,8.98871 L762.28,6.59059 L498.487,6.59059 L232.295,6.59059 L232.295,11.3868 C231.901,74.2274 269.048,130.868 313.831,172.061 C337.813,193.644 366.59,210.431 400.164,222.422 C412.154,227.218 416.951,229.616 426.543,239.208 C438.534,253.597 448.126,270.384 452.923,289.569 C455.827,317.286 453.654,346.577 445.728,373.503 L440.932,387.892 C438.534,397.484 431.339,411.873 419.349,435.854 C407.358,459.836 407.358,462.234 407.358,464.632 C412.154,479.021 440.932,488.613 484.098,493.409 C493.691,493.409 508.079,493.409 527.264,491.011 M325.822,409.475 C342.609,407.077 356.998,402.281 361.794,395.086 L361.794,392.688 L359.396,385.494 C342.609,354.318 333.016,327.939 333.016,301.56 C333.016,287.171 335.415,279.977 340.211,267.986 C347.405,255.995 349.803,252.125 361.794,247.329 C366.59,244.876 372.313,243.95 374.711,242.478 C380.979,240.625 388.173,236.81 388.173,236.81 C388.173,236.81 383.868,235.884 379.016,233.486 C364.628,228.69 359.396,224.82 347.405,217.625 C309.035,196.042 285.054,174.459 261.073,143.284 C253.878,131.293 250.156,125.996 246.684,121.163 L244.286,116.904 C241.888,114.506 145.963,114.506 143.565,116.904 C141.939,150.478 158.03,180.057 179.536,205.635 C204.661,235.514 225.101,244.005 244.286,248.801 C261.073,253.597 263.471,255.995 270.665,265.588 C275.462,277.578 277.86,284.773 277.86,299.161 C280.258,320.745 273.063,342.328 258.675,373.503 C253.878,383.096 249.082,392.688 249.082,392.688 C249.082,395.086 253.878,399.883 258.675,402.281 C270.665,409.475 304.239,414.271 325.822,409.475 M716.716,409.475 C735.901,407.077 747.892,402.281 750.29,395.086 C750.29,392.688 750.29,390.29 743.095,375.901 C728.008,346.118 717.597,310.72 726.308,277.578 C731.287,264.162 737.689,250.182 752.688,247.852 C776.669,240.658 795.854,229.616 819.835,205.635 C834.224,191.246 847.61,166.971 851.369,152.876 C854.382,141.577 858.172,128.066 855.807,116.904 C853.409,114.506 755.086,114.506 752.688,116.904 C752.688,116.904 750.29,119.302 747.892,121.7 C745.493,128.895 735.901,143.284 728.707,150.478 C719.114,162.469 690.337,191.246 680.744,198.44 C663.057,216.559 629.114,228.768 611.199,236.81 C613.597,239.208 625.587,246.403 635.18,248.801 C654.365,255.995 654.365,255.995 661.559,267.986 C666.355,279.977 668.754,287.171 668.754,301.56 C670.08,334.844 653.109,365.67 639.976,392.688 C657.022,411.883 692.824,411.394 716.716,409.475 Z" style="stroke:none;fill-rule:evenodd;fill:#ffffff;fill-opacity:1;"/>
|
|
||||||
</g>
|
|
||||||
<g id="Shape2">
|
|
||||||
<desc shapeID="2" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-3.75,-28,7.5,56)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,417.25,99.5)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
|
|
||||||
<path id="shapePath2" d="M413.5,127.5 C414,126.5 416,123 416.5,122.5 C416,123 414,126.5 413.5,127.5 M421,71.5 " style="stroke:none;fill-rule:evenodd;fill:#669020;fill-opacity:1;"/>
|
|
||||||
</g>
|
|
||||||
<g id="Shape3">
|
|
||||||
<desc shapeID="3" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(0,0,0,0)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,0,0)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
|
|
||||||
<path id="shapePath3" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#4c4c4c;fill-opacity:1;"/>
|
|
||||||
</g>
|
|
||||||
<g id="Shape4">
|
|
||||||
<desc shapeID="4" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(0,0,0,0)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,0,0)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
|
|
||||||
<path id="shapePath4" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#000000;fill-opacity:1;"/>
|
|
||||||
</g>
|
|
||||||
<g id="Shape5">
|
|
||||||
<desc shapeID="5" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-84.6928,-47.6497,169.386,95.2993)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,90.9499,90.9738)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
|
|
||||||
<path id="shapePath5" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#0d0d0d;fill-opacity:1;"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 658 B |
Before Width: | Height: | Size: 659 B |
Before Width: | Height: | Size: 767 B |
Before Width: | Height: | Size: 755 B |
Before Width: | Height: | Size: 726 B |
Before Width: | Height: | Size: 701 B |
Before Width: | Height: | Size: 806 B |
Before Width: | Height: | Size: 778 B |
Before Width: | Height: | Size: 300 B |
Before Width: | Height: | Size: 835 B |
Before Width: | Height: | Size: 834 B |
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="45px" height="45px" viewBox="0 0 45 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: Sketch 52.1 (67048) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<title>slack_orange</title>
|
|
||||||
<desc>Created with Sketch.</desc>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<g id="slack_orange">
|
|
||||||
<path d="M22.502,0 C10.073,0 0,10.073 0,22.499 C0,34.927 10.073,45 22.502,45 C34.927,45 45,34.927 45,22.499 C45,10.073 34.927,0 22.502,0 Z" id="Shape" fill="#FEB672"></path>
|
|
||||||
<g id="Slack_Mark_Monochrome_White" transform="translate(11.000000, 11.000000)" fill="#FFFFFF">
|
|
||||||
<rect id="Rectangle-path" transform="translate(11.554441, 11.462704) rotate(-18.518296) translate(-11.554441, -11.462704) " x="9.94812935" y="9.91115274" width="3.21262291" height="3.10310168"></rect>
|
|
||||||
<g id="Group">
|
|
||||||
<rect id="Rectangle-path" transform="translate(11.554441, 11.462704) rotate(-18.518296) translate(-11.554441, -11.462704) " x="9.94812935" y="9.91115274" width="3.21262291" height="3.10310168"></rect>
|
|
||||||
<path d="M22.0142857,8.30555556 C19.6595238,0.456349206 16.2642857,-1.36904762 8.41507937,0.985714286 C0.565873016,3.34047619 -1.25952381,6.73571429 1.0952381,14.5849206 C3.45,22.434127 6.8452381,24.2595238 14.6944444,21.9047619 C22.5436508,19.55 24.3690476,16.1547619 22.0142857,8.30555556 Z M18.0531746,13.3984127 L16.5746032,13.8912698 L17.0857143,15.4246032 C17.2865079,16.0452381 16.9579365,16.7206349 16.3373016,16.9214286 C16.2095238,16.9579365 16.0634921,16.9944444 15.9357143,16.9761905 C15.4611111,16.9579365 15.0047619,16.647619 14.8404762,16.1730159 L14.3293651,14.6396825 L11.2809524,15.6619048 L11.7920635,17.1952381 C11.9928571,17.815873 11.6642857,18.4912698 11.0436508,18.6920635 C10.915873,18.7285714 10.7698413,18.7650794 10.6420635,18.7468254 C10.1674603,18.7285714 9.71111111,18.418254 9.5468254,17.9436508 L9.03571429,16.4103175 L7.55714286,16.9031746 C7.42936508,16.9396825 7.28333333,16.9761905 7.15555556,16.9579365 C6.68095238,16.9396825 6.22460317,16.6293651 6.06031746,16.1547619 C5.85952381,15.534127 6.18809524,14.8587302 6.80873016,14.6579365 L8.28730159,14.1650794 L7.3015873,11.2261905 L5.82301587,11.7190476 C5.6952381,11.7555556 5.54920635,11.7920635 5.42142857,11.7738095 C4.9468254,11.7555556 4.49047619,11.4452381 4.32619048,10.9706349 C4.12539683,10.35 4.45396825,9.67460317 5.07460317,9.47380952 L6.5531746,8.98095238 L6.04206349,7.44761905 C5.84126984,6.82698413 6.16984127,6.1515873 6.79047619,5.95079365 C7.41111111,5.75 8.08650794,6.07857143 8.28730159,6.69920635 L8.7984127,8.23253968 L11.8468254,7.21031746 L11.3357143,5.67698413 C11.1349206,5.05634921 11.4634921,4.38095238 12.084127,4.18015873 C12.7047619,3.97936508 13.3801587,4.30793651 13.5809524,4.92857143 L14.0920635,6.46190476 L15.5706349,5.96904762 C16.1912698,5.76825397 16.8666667,6.0968254 17.0674603,6.71746032 C17.268254,7.33809524 16.9396825,8.01349206 16.3190476,8.21428571 L14.8404762,8.70714286 L15.8261905,11.6460317 L17.3047619,11.1531746 C17.9253968,10.952381 18.6007937,11.2809524 18.8015873,11.9015873 C19.002381,12.5222222 18.6738095,13.197619 18.0531746,13.3984127 Z" id="Shape" fill-rule="nonzero"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,11 +0,0 @@
|
|||||||
// This is a manifest file that'll be compiled into application.js.
|
|
||||||
//
|
|
||||||
// Any JavaScript file within this directory can be referenced here using a relative path.
|
|
||||||
//
|
|
||||||
// You're free to add application-wide JavaScript to this file, but it's generally better
|
|
||||||
// to create separate JavaScript files as needed.
|
|
||||||
//
|
|
||||||
//= require jquery-3.3.1.min
|
|
||||||
//= require bootstrap
|
|
||||||
//= require popper.min
|
|
||||||
//= require_self
|
|