Compare commits
360 Commits
muwire-0.6
...
docker-0.6
Author | SHA1 | Date | |
---|---|---|---|
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
|
||||
.project
|
||||
.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=9
|
||||
|
||||
# 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"
|
79
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.
|
||||
|
||||
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
|
||||
|
||||
@ -21,25 +21,81 @@ 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
|
||||
|
||||
### 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`
|
||||
|
||||
[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]
|
||||
|
||||
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
|
||||
|
||||
The Docker image is based on the wonderful work in [jlesage/docker-baseimage-gui].
|
||||
You can refer to it for environment variables to pass to the container.
|
||||
|
||||
If you don't want to use the image on dockerhub, build an image yourself.
|
||||
```bash
|
||||
MUWIRE_VERSION=`awk -F "=" '/^version/ { gsub(" ","") ; print $2}' gradle.properties`
|
||||
docker build -t muwire:latest,muwire:${MUWIRE_VERSION} .
|
||||
```
|
||||
|
||||
**Necessary configuration**
|
||||
|
||||
Since MuWire will be running in a container, it won't have direct access to the host's localhost.
|
||||
By default, it will be configured to use `172.17.0.1` as the target host.
|
||||
You'll need to open the I2CP port on that interface.
|
||||
If you're running I2P on the localhost, navigate to http://localhost:7657/configi2cp and make the necessary changes.
|
||||
|
||||
![i2cp_config.png]
|
||||
|
||||
Should you be using a different interface write an `i2p.properties` and then put that into the shared docker volume.
|
||||
|
||||
Example configuration file:
|
||||
```properties
|
||||
i2cp.tcp.host=112.13.0.1
|
||||
```
|
||||
|
||||
**Running**
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-p 5800:5800 \
|
||||
-v config:/muwire/.MuWire \
|
||||
-v incompletes:/incompletes \
|
||||
-v output:/output \
|
||||
--name muwire \
|
||||
zlatinb/muwire
|
||||
```
|
||||
|
||||
You will then be able to access the muwire GUI over a browser at http://localhost:5800
|
||||
|
||||
**Options**
|
||||
|
||||
|
||||
| Option | Description |
|
||||
|--------------|--------------------------------------------|
|
||||
|`-v config:/muwire/.MuWire`| This is where the `i2p.properties` and possibly other config should go |
|
||||
|`-v incompletes:/incompletes`| The `/incompletes` volume should be used to store MuWire's **incomplete** download/upload data \*|
|
||||
|`-v output:/output`| The `/output` volume should be used to store MuWire's download/upload data |
|
||||
|
||||
## 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
|
||||
@ -53,3 +109,8 @@ You can find the full key at https://keybase.io/zlatinb
|
||||
[doc]: https://github.com/zlatinb/muwire/tree/master/doc
|
||||
[muwire-pkg]: https://github.com/zlatinb/muwire-pkg
|
||||
[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
|
||||
[i2cp_config.png]: ./images/i2cp_config.png
|
||||
[muwire_incompletes.png]: ./images/muwire_incompletes.png
|
||||
[jlesage/docker-baseimage-gui]: https://github.com/jlesage/docker-baseimage-gui
|
||||
|
42
TODO.md
@ -1,8 +1,6 @@
|
||||
# TODO List
|
||||
|
||||
Not in any particular order yet
|
||||
|
||||
### Big Items
|
||||
### Network
|
||||
|
||||
##### Bloom Filters
|
||||
|
||||
@ -12,15 +10,33 @@ This reduces query traffic by not sending last hop queries to peers that definit
|
||||
|
||||
This helps with scalability
|
||||
|
||||
##### Web UI, REST Interface, etc.
|
||||
### Core
|
||||
|
||||
Basically any non-gui non-cli user interface
|
||||
|
||||
##### 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
|
||||
* Metadata parsing and search
|
||||
* 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'
|
||||
|
||||
dependencies {
|
||||
compile "net.i2p:i2p:${i2pVersion}"
|
||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||
compile 'org.codehaus.groovy:groovy:2.4.15'
|
||||
compile 'org.codehaus.groovy:groovy-jsr223:2.4.15'
|
||||
compile 'org.codehaus.groovy:groovy-json:2.4.15'
|
||||
}
|
||||
|
||||
compileGroovy {
|
||||
|
@ -32,7 +32,7 @@ import com.muwire.core.UILoadedEvent
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
|
||||
class CliLanterna {
|
||||
private static final String MW_VERSION = "0.6.7"
|
||||
private static final String MW_VERSION = "0.6.8"
|
||||
|
||||
private static volatile Core core
|
||||
|
||||
|
@ -2,6 +2,7 @@ apply plugin : 'application'
|
||||
mainClassName = 'com.muwire.core.Core'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||
dependencies {
|
||||
compile "net.i2p:i2p:${i2pVersion}"
|
||||
compile "net.i2p:router:${i2pVersion}"
|
||||
compile "net.i2p.client:mstreaming:${i2pVersion}"
|
||||
compile "net.i2p.client:streaming:${i2pVersion}"
|
||||
|
@ -91,12 +91,14 @@ public class Core {
|
||||
|
||||
final EventBus eventBus
|
||||
final Persona me
|
||||
final String version;
|
||||
final File home
|
||||
final Properties i2pOptions
|
||||
final MuWireSettings muOptions
|
||||
|
||||
private final TrustService trustService
|
||||
private final TrustSubscriber trustSubscriber
|
||||
private final I2PSession i2pSession;
|
||||
final TrustService trustService
|
||||
final TrustSubscriber trustSubscriber
|
||||
private final PersisterService persisterService
|
||||
private final HostCache hostCache
|
||||
private final ConnectionManager connectionManager
|
||||
@ -122,26 +124,27 @@ public class Core {
|
||||
|
||||
public Core(MuWireSettings props, File home, String myVersion) {
|
||||
this.home = home
|
||||
this.version = myVersion
|
||||
this.muOptions = props
|
||||
|
||||
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()) {
|
||||
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
|
||||
|
||||
if (!i2pOptions.containsKey("inbound.nickname"))
|
||||
i2pOptions["inbound.nickname"] = "MuWire"
|
||||
if (!i2pOptions.containsKey("outbound.nickname"))
|
||||
i2pOptions["outbound.nickname"] = "MuWire"
|
||||
} else {
|
||||
i2pOptions["inbound.nickname"] = "MuWire"
|
||||
i2pOptions["outbound.nickname"] = "MuWire"
|
||||
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"
|
||||
if (!i2pOptions.containsKey("outbound.nickname"))
|
||||
i2pOptions["outbound.nickname"] = "MuWire"
|
||||
}
|
||||
if (!(i2pOptions.hasProperty("i2np.ntcp.port")
|
||||
&& i2pOptions.hasProperty("i2np.udp.port")
|
||||
)) {
|
||||
Random r = new Random()
|
||||
int port = r.nextInt(60000) + 4000
|
||||
i2pOptions["i2np.ntcp.port"] = String.valueOf(port)
|
||||
@ -150,15 +153,18 @@ public class Core {
|
||||
}
|
||||
|
||||
if (!props.embeddedRouter) {
|
||||
log.info "Initializing I2P context"
|
||||
I2PAppContext.getGlobalContext().logManager()
|
||||
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
|
||||
router = null
|
||||
if (!(I2PAppContext.getGlobalContext() instanceof RouterContext)) {
|
||||
log.info "Initializing I2P context"
|
||||
I2PAppContext.getGlobalContext().logManager()
|
||||
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
|
||||
router = null
|
||||
}
|
||||
} else {
|
||||
log.info("launching embedded router")
|
||||
Properties routerProps = new Properties()
|
||||
routerProps.setProperty("i2p.dir.base", 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("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
||||
routerProps.setProperty("i2np.outboundKBytesPerSecond", String.valueOf(props.outBw))
|
||||
@ -185,7 +191,6 @@ public class Core {
|
||||
|
||||
|
||||
// options like tunnel length and quantity
|
||||
I2PSession i2pSession
|
||||
I2PSocketManager socketManager
|
||||
keyDat.withInputStream {
|
||||
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
||||
@ -274,10 +279,13 @@ public class Core {
|
||||
log.info("initializing cache client")
|
||||
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
||||
|
||||
if (!props.plugin) {
|
||||
log.info("initializing update client")
|
||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me, spk)
|
||||
eventBus.register(FileDownloadedEvent.class, updateClient)
|
||||
eventBus.register(UIResultBatchEvent.class, updateClient)
|
||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me, spk)
|
||||
eventBus.register(FileDownloadedEvent.class, updateClient)
|
||||
eventBus.register(UIResultBatchEvent.class, updateClient)
|
||||
} else
|
||||
log.info("running as plugin, not initializing update client")
|
||||
|
||||
log.info("initializing connector")
|
||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||
@ -372,7 +380,7 @@ public class Core {
|
||||
connectionAcceptor.start()
|
||||
connectionEstablisher.start()
|
||||
hostCache.waitForLoad()
|
||||
updateClient.start()
|
||||
updateClient?.start()
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
@ -382,8 +390,14 @@ public class Core {
|
||||
}
|
||||
log.info("saving settings")
|
||||
saveMuSettings()
|
||||
log.info("shutting down host cache")
|
||||
hostCache.stop()
|
||||
log.info("shutting down trust subscriber")
|
||||
trustSubscriber.stop()
|
||||
log.info("shutting down trust service")
|
||||
trustService.stop()
|
||||
log.info("shutting down persister service")
|
||||
persisterService.stop()
|
||||
log.info("shutting down download manager")
|
||||
downloadManager.shutdown()
|
||||
log.info("shutting down connection acceptor")
|
||||
@ -400,10 +414,14 @@ public class Core {
|
||||
chatManager.shutdown()
|
||||
log.info("shutting down connection manager")
|
||||
connectionManager.shutdown()
|
||||
log.info("killing i2p session")
|
||||
i2pSession.destroySession()
|
||||
if (router != null) {
|
||||
log.info("shutting down embedded router")
|
||||
router.shutdown(0)
|
||||
}
|
||||
log.info("shutting down event bus");
|
||||
eventBus.shutdown()
|
||||
log.info("shutdown complete")
|
||||
}
|
||||
|
||||
@ -411,6 +429,11 @@ public class Core {
|
||||
File f = new File(home, "MuWire.properties")
|
||||
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) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
@ -436,7 +459,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.6.7")
|
||||
Core core = new Core(props, home, "0.6.8")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@ -2,6 +2,7 @@ package com.muwire.core
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.logging.Level
|
||||
|
||||
@ -12,7 +13,7 @@ import groovy.util.logging.Log
|
||||
class EventBus {
|
||||
|
||||
private Map handlers = new HashMap()
|
||||
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor {r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("event-bus")
|
||||
@ -53,4 +54,8 @@ class EventBus {
|
||||
log.info("Unregistering $handler for type $eventType")
|
||||
handlers[eventType]?.remove(handler)
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
executor.shutdownNow()
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ class MuWireSettings {
|
||||
int meshExpiration
|
||||
int speedSmoothSeconds
|
||||
boolean embeddedRouter
|
||||
boolean plugin
|
||||
int inBw, outBw
|
||||
Set<String> watchedKeywords
|
||||
Set<String> watchedRegexes
|
||||
@ -76,6 +77,7 @@ class MuWireSettings {
|
||||
hostRejectInterval = Integer.valueOf(props.getProperty("hostRejectInterval", "1"))
|
||||
meshExpiration = Integer.valueOf(props.getProperty("meshExpiration","60"))
|
||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||
plugin = Boolean.valueOf(props.getProperty("plugin","false"))
|
||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
||||
@ -130,6 +132,7 @@ class MuWireSettings {
|
||||
props.setProperty("hostRejectInterval", String.valueOf(hostRejectInterval))
|
||||
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
|
||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||
props.setProperty("plugin", String.valueOf(plugin))
|
||||
props.setProperty("inBw", String.valueOf(inBw))
|
||||
props.setProperty("outBw", String.valueOf(outBw))
|
||||
props.setProperty("searchComments", String.valueOf(searchComments))
|
||||
|
@ -369,7 +369,12 @@ class ConnectionAcceptor {
|
||||
|
||||
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))
|
||||
JsonOutput jsonOutput = new JsonOutput()
|
||||
|
@ -92,6 +92,22 @@ public class Downloader {
|
||||
public synchronized InfoHash getInfoHash() {
|
||||
infoHash
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
file
|
||||
}
|
||||
|
||||
public int getNPieces() {
|
||||
nPieces
|
||||
}
|
||||
|
||||
public int getPieceSize() {
|
||||
pieceSize
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
length
|
||||
}
|
||||
|
||||
private synchronized void setInfoHash(InfoHash infoHash) {
|
||||
this.infoHash = infoHash
|
||||
@ -249,6 +265,10 @@ public class Downloader {
|
||||
}
|
||||
active
|
||||
}
|
||||
|
||||
public int getTotalWorkers() {
|
||||
return activeWorkers.size();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
paused = false
|
||||
|
@ -10,17 +10,20 @@ import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.data.Signature
|
||||
import net.i2p.data.SigningPrivateKey
|
||||
import net.i2p.data.SigningPublicKey
|
||||
import net.i2p.data.Base64
|
||||
|
||||
class Certificate {
|
||||
private final byte version
|
||||
private final InfoHash infoHash
|
||||
private final Name name, comment
|
||||
private final long timestamp
|
||||
private final Persona issuer
|
||||
final Name name, comment
|
||||
final long timestamp
|
||||
final Persona issuer
|
||||
private final byte[] sig
|
||||
|
||||
private volatile byte [] payload
|
||||
|
||||
private String base64;
|
||||
|
||||
Certificate(InputStream is) {
|
||||
version = (byte) (is.read() & 0xFF)
|
||||
if (version > Constants.FILE_CERT_VERSION)
|
||||
@ -131,6 +134,15 @@ class Certificate {
|
||||
os.write(payload)
|
||||
}
|
||||
|
||||
public String toBase64() {
|
||||
if (base64 == null) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
write(baos)
|
||||
base64 = Base64.encode(baos.toByteArray())
|
||||
}
|
||||
return base64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
version.hashCode() ^ infoHash.hashCode() ^ timestamp.hashCode() ^ name.hashCode() ^ issuer.hashCode() ^ Objects.hashCode(comment)
|
||||
|
@ -32,7 +32,8 @@ class CertificateClient {
|
||||
fetcherThread.execute({
|
||||
Endpoint endpoint = null
|
||||
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)
|
||||
|
||||
String infoHashString = Base64.encode(e.infoHash.getRoot())
|
||||
@ -62,7 +63,8 @@ class CertificateClient {
|
||||
int count = Integer.parseInt(headers['Count'])
|
||||
|
||||
// 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)
|
||||
for (int i = 0; i < count; i++) {
|
||||
@ -77,11 +79,14 @@ class CertificateClient {
|
||||
continue
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
endpoint?.close()
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
package com.muwire.core.filecert
|
||||
|
||||
import com.muwire.core.Event
|
||||
import com.muwire.core.InfoHash
|
||||
import com.muwire.core.Persona
|
||||
|
||||
class CertificateFetchEvent extends Event {
|
||||
CertificateFetchStatus status
|
||||
int count
|
||||
Persona user
|
||||
InfoHash infoHash
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
package com.muwire.core.filecert
|
||||
|
||||
import com.muwire.core.Event
|
||||
import com.muwire.core.InfoHash
|
||||
import com.muwire.core.Persona
|
||||
|
||||
class CertificateFetchedEvent extends Event {
|
||||
Certificate certificate
|
||||
Persona user
|
||||
InfoHash infoHash
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ class CertificateManager {
|
||||
added
|
||||
}
|
||||
|
||||
boolean hasLocalCertificate(InfoHash infoHash) {
|
||||
public boolean hasLocalCertificate(InfoHash infoHash) {
|
||||
if (!byInfoHash.containsKey(infoHash))
|
||||
return false
|
||||
Set<Certificate> set = byInfoHash.get(infoHash)
|
||||
@ -130,6 +130,13 @@ class CertificateManager {
|
||||
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> rv = new HashSet<>()
|
||||
if (byInfoHash.containsKey(infoHash))
|
||||
|
@ -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);
|
||||
}
|
@ -24,7 +24,7 @@ class FileManager {
|
||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||
final Map<String, Set<File>> commentToFile = new HashMap<>()
|
||||
final SearchIndex index = new SearchIndex()
|
||||
final FileTree negativeTree = new FileTree()
|
||||
final FileTree<Void> negativeTree = new FileTree<>()
|
||||
final Set<File> sideCarFiles = new HashSet<>()
|
||||
|
||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||
@ -32,7 +32,7 @@ class FileManager {
|
||||
this.eventBus = eventBus
|
||||
|
||||
for (String negative : settings.negativeFileTree) {
|
||||
negativeTree.add(new File(negative))
|
||||
negativeTree.add(new File(negative), null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ class FileManager {
|
||||
negativeTree.remove(sf.file)
|
||||
String parent = sf.getFile().getParent()
|
||||
if (parent != null && settings.watchedDirectories.contains(parent)) {
|
||||
negativeTree.add(sf.file.getParentFile())
|
||||
negativeTree.add(sf.file.getParentFile(),null)
|
||||
}
|
||||
saveNegativeTree()
|
||||
|
||||
@ -128,7 +128,7 @@ class FileManager {
|
||||
|
||||
fileToSharedFile.remove(sf.file)
|
||||
if (!e.deleted && negativeTree.fileToNode.containsKey(sf.file.getParentFile())) {
|
||||
negativeTree.add(sf.file)
|
||||
negativeTree.add(sf.file,null)
|
||||
saveNegativeTree()
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,12 @@ package com.muwire.core.files
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class FileTree {
|
||||
class FileTree<T> {
|
||||
|
||||
private final TreeNode root = new TreeNode()
|
||||
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<>()
|
||||
path.add(file)
|
||||
while (file.getParentFile() != null) {
|
||||
@ -29,6 +29,7 @@ class FileTree {
|
||||
}
|
||||
current = existing
|
||||
}
|
||||
current.value = value;
|
||||
}
|
||||
|
||||
synchronized boolean remove(File file) {
|
||||
@ -45,13 +46,63 @@ class FileTree {
|
||||
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
|
||||
File file
|
||||
T value;
|
||||
final Set<TreeNode> children = new HashSet<>()
|
||||
|
||||
public int hashCode() {
|
||||
file.hashCode()
|
||||
Objects.hash(file)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.muwire.core.hostcache
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.connection.ConnectionManager
|
||||
@ -27,6 +29,7 @@ class CacheClient {
|
||||
final long interval
|
||||
final MuWireSettings settings
|
||||
final Timer timer
|
||||
private final AtomicBoolean stopped = new AtomicBoolean();
|
||||
|
||||
public CacheClient(EventBus eventBus, HostCache cache,
|
||||
ConnectionManager manager, I2PSession session,
|
||||
@ -47,9 +50,12 @@ class CacheClient {
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
stopped.set(true)
|
||||
}
|
||||
|
||||
private void queryIfNeeded() {
|
||||
if (stopped.get())
|
||||
return
|
||||
if (!manager.getConnections().isEmpty())
|
||||
return
|
||||
if (!cache.getHosts(1).isEmpty())
|
||||
@ -65,7 +71,12 @@ class CacheClient {
|
||||
options.setSendLeaseSet(true)
|
||||
CacheServers.getCacheServers().each {
|
||||
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({
|
||||
Endpoint endpoint = null
|
||||
try {
|
||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.CONNECTING))
|
||||
eventBus.publish(new BrowseStatusEvent(host : e.host, status : BrowseStatus.CONNECTING))
|
||||
endpoint = connector.connect(e.host.destination)
|
||||
OutputStream os = endpoint.getOutputStream()
|
||||
os.write("BROWSE\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
@ -55,8 +55,10 @@ class BrowseManager {
|
||||
|
||||
int results = Integer.parseInt(headers['Count'])
|
||||
|
||||
boolean chat = headers.containsKey("Chat") && Boolean.parseBoolean(headers['Chat'])
|
||||
|
||||
// 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()
|
||||
DataInputStream dis = new DataInputStream(new GZIPInputStream(is))
|
||||
@ -67,14 +69,15 @@ class BrowseManager {
|
||||
dis.readFully(tmp)
|
||||
def json = slurper.parse(tmp)
|
||||
UIResultEvent result = ResultsParser.parse(e.host, uuid, json)
|
||||
result.chat = chat
|
||||
eventBus.publish(result)
|
||||
}
|
||||
|
||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FINISHED))
|
||||
eventBus.publish(new BrowseStatusEvent(host: e.host, status : BrowseStatus.FINISHED))
|
||||
|
||||
} catch (Exception 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 {
|
||||
endpoint?.close()
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package com.muwire.core.search
|
||||
|
||||
import com.muwire.core.Event
|
||||
import com.muwire.core.Persona
|
||||
|
||||
class BrowseStatusEvent extends Event {
|
||||
Persona host
|
||||
BrowseStatus status
|
||||
int totalResults
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import net.i2p.util.ConcurrentHashSet
|
||||
class RemoteTrustList {
|
||||
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
||||
|
||||
private final Persona persona
|
||||
private final Set<TrustEntry> good, bad
|
||||
final Persona persona
|
||||
final Set<TrustEntry> good, bad
|
||||
volatile long timestamp
|
||||
volatile boolean forceUpdate
|
||||
Status status = Status.NEW
|
||||
|
@ -130,8 +130,8 @@ class TrustService extends Service {
|
||||
}
|
||||
|
||||
public static class TrustEntry {
|
||||
private final Persona persona
|
||||
private final String reason
|
||||
final Persona persona
|
||||
final String reason
|
||||
TrustEntry(Persona persona, String reason) {
|
||||
this.persona = persona
|
||||
this.reason = reason
|
||||
|
@ -26,7 +26,7 @@ class TrustSubscriber {
|
||||
private final I2PConnector i2pConnector
|
||||
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 volatile boolean shutdown
|
||||
@ -50,7 +50,7 @@ class TrustSubscriber {
|
||||
thread?.interrupt()
|
||||
updateThreads.shutdownNow()
|
||||
}
|
||||
|
||||
|
||||
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
|
||||
if (!e.subscribe) {
|
||||
remoteTrustLists.remove(e.persona.destination)
|
||||
@ -62,6 +62,10 @@ class TrustSubscriber {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSubscribed(Persona p) {
|
||||
remoteTrustLists.containsKey(p.destination)
|
||||
}
|
||||
|
||||
private void checkLoop() {
|
||||
try {
|
||||
|
@ -68,11 +68,19 @@ public class Persona {
|
||||
humanReadableName = name.getName() + "@" + destination.toBase32().substring(0,32);
|
||||
return humanReadableName;
|
||||
}
|
||||
|
||||
public Destination getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public String toBase64() throws DataFormatException, IOException {
|
||||
public String toBase64() {
|
||||
if (base64 == null) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
write(baos);
|
||||
try {
|
||||
write(baos);
|
||||
} catch (Exception impossible) {
|
||||
throw new RuntimeException(impossible);
|
||||
}
|
||||
base64 = Base64.encode(baos.toByteArray());
|
||||
}
|
||||
return base64;
|
||||
|
@ -63,7 +63,7 @@ public class DataUtil {
|
||||
((int)header[2] & 0xFF);
|
||||
}
|
||||
|
||||
static String readi18nString(byte [] encoded) {
|
||||
public static String readi18nString(byte [] encoded) {
|
||||
if (encoded.length < 2)
|
||||
throw new IllegalArgumentException("encoding too short $encoded.length");
|
||||
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()
|
||||
|
||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||
hostCache, trustService, searchManager, uploadManager, null, connectionEstablisher, null)
|
||||
hostCache, trustService, searchManager, uploadManager, null, connectionEstablisher, null, null)
|
||||
acceptor.start()
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ class FileTreeTest {
|
||||
File b = new File(a, "b")
|
||||
File c = new File(b, "c")
|
||||
|
||||
FileTree tree = new FileTree()
|
||||
tree.add(c)
|
||||
FileTree<Void> tree = new FileTree<>()
|
||||
tree.add(c,null)
|
||||
|
||||
assert tree.root.children.size() == 1
|
||||
assert tree.fileToNode.size() == 3
|
||||
@ -28,15 +28,110 @@ class FileTreeTest {
|
||||
File c = new File(b, "c")
|
||||
File d = new File(b, "d")
|
||||
|
||||
FileTree tree = new FileTree()
|
||||
tree.add(c)
|
||||
FileTree<Void> tree = new FileTree<>()
|
||||
tree.add(c,null)
|
||||
|
||||
assert tree.fileToNode.size() == 3
|
||||
|
||||
tree.add(d)
|
||||
tree.add(d, null)
|
||||
assert tree.fileToNode.size() == 4
|
||||
|
||||
tree.remove(d)
|
||||
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
|
||||
version = 0.6.7
|
||||
i2pVersion = 0.9.43
|
||||
version = 0.6.8
|
||||
i2pVersion = 0.9.44
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
@ -16,6 +16,6 @@ author = zab@mail.i2p
|
||||
signer = zab@mail.i2p
|
||||
keystorePassword=changeit
|
||||
websiteURL=http://muwire.i2p
|
||||
updateURLsu3=http://muwire.i2p/MuWire.su3
|
||||
updateURLsu3=http://muwire.i2p/MuWire-update.su3
|
||||
|
||||
pack200=true
|
||||
|
@ -47,6 +47,7 @@ class BrowseController {
|
||||
|
||||
void onUIResultEvent(UIResultEvent e) {
|
||||
runInsideUIAsync {
|
||||
model.chatActionEnabled = e.chat
|
||||
model.results << e
|
||||
model.resultCount = model.results.size()
|
||||
view.resultsTable.model.fireTableDataChanged()
|
||||
@ -116,4 +117,13 @@ class BrowseController {
|
||||
params['core'] = core
|
||||
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
|
||||
model.networkStatus = router._context.commSystem().status.toStatusString()
|
||||
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.ssuConnections = router._context.commSystem().getTransports()["SSU"].countPeers()
|
||||
model.participatingTunnels = router._context.tunnelManager().getParticipatingCount()
|
||||
|
@ -14,6 +14,8 @@ import net.i2p.data.Signature
|
||||
import net.i2p.data.SigningPrivateKey
|
||||
|
||||
import java.awt.Desktop
|
||||
import java.awt.Toolkit
|
||||
import java.awt.datatransfer.StringSelection
|
||||
import java.awt.event.ActionEvent
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@ -87,8 +89,19 @@ class MainFrameController {
|
||||
search = search.trim()
|
||||
if (search.length() == 0)
|
||||
return
|
||||
if (search.length() > 128)
|
||||
search = search.substring(0,128)
|
||||
if (search.length() > 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()
|
||||
Map<String, Object> params = new HashMap<>()
|
||||
params["search-terms"] = search
|
||||
@ -485,6 +498,22 @@ class MainFrameController {
|
||||
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) {
|
||||
if (!mvcGroup.getChildrenGroups().containsKey(p.getHumanReadableName())) {
|
||||
def params = [:]
|
||||
|
@ -44,6 +44,8 @@ class Ready extends AbstractLifecycleHandler {
|
||||
propsFile.withReader("UTF-8", {
|
||||
props.load(it)
|
||||
})
|
||||
if (!props.containsKey("nickname"))
|
||||
props.setProperty("nickname", selectNickname())
|
||||
props = new MuWireSettings(props)
|
||||
if (props.incompleteLocation == null)
|
||||
props.incompleteLocation = new File(home, "incompletes")
|
||||
@ -53,25 +55,7 @@ class Ready extends AbstractLifecycleHandler {
|
||||
props.incompleteLocation = new File(home, "incompletes")
|
||||
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
||||
props.updateType = System.getProperty("updateType","jar")
|
||||
def 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 || 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)
|
||||
props.setNickname(selectNickname())
|
||||
|
||||
|
||||
def portableDownloads = System.getProperty("portable.downloads")
|
||||
@ -116,5 +100,31 @@ class Ready extends AbstractLifecycleHandler {
|
||||
|
||||
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 viewCommentActionEnabled
|
||||
@Observable boolean viewCertificatesActionEnabled
|
||||
@Observable boolean chatActionEnabled
|
||||
@Observable int totalResults
|
||||
@Observable int resultCount
|
||||
|
||||
|
@ -16,6 +16,8 @@ class I2PStatusModel {
|
||||
@Observable int ssuConnections
|
||||
@Observable String networkStatus
|
||||
@Observable boolean floodfill
|
||||
@Observable String myCountry
|
||||
@Observable boolean strictCountry
|
||||
@Observable int participatingTunnels
|
||||
@Observable int activePeers
|
||||
@Observable int receiveBps
|
||||
|
@ -68,6 +68,7 @@ class BrowseView {
|
||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||
button(text : "View Comment", enabled : bind{model.viewCommentActionEnabled}, viewCommentAction)
|
||||
button(text : "View Certificates", enabled : bind{model.viewCertificatesActionEnabled}, viewCertificatesAction)
|
||||
button(text : "Chat", enabled : bind {model.chatActionEnabled}, chatAction)
|
||||
button(text : "Dismiss", dismissAction)
|
||||
label(text : "Download sequentially")
|
||||
sequentialDownloadCheckbox = checkBox()
|
||||
|
@ -5,6 +5,7 @@ import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.SwingConstants
|
||||
|
||||
import java.awt.BorderLayout
|
||||
@ -20,18 +21,15 @@ class ChatMonitorView {
|
||||
@MVCMember @Nonnull
|
||||
ChatMonitorModel model
|
||||
|
||||
def mainFrame
|
||||
def dialog
|
||||
def panel
|
||||
def window
|
||||
def roomsTable
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
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()
|
||||
panel(constraints : BorderLayout.NORTH) {
|
||||
label("Chat rooms with unread messages")
|
||||
@ -53,15 +51,12 @@ class ChatMonitorView {
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
dialog.getContentPane().add(panel)
|
||||
dialog.pack()
|
||||
dialog.setLocationRelativeTo(mainFrame)
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
window.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
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 : "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 : "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),
|
||||
constraints : gbc(gridx: 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||
|
@ -499,7 +499,11 @@ class MainFrameView {
|
||||
}
|
||||
panel (border: etchedBorder(), constraints : BorderLayout.SOUTH) {
|
||||
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) {
|
||||
label("Connections:")
|
||||
label(text : bind {model.connections})
|
||||
|
@ -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']
|
||||
|
||||
dependencies {
|
||||
compile "net.i2p:i2p:${i2pVersion}"
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
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'
|
||||
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 {
|
||||
dependsOn ':webui:assemble'
|
||||
doLast { task ->
|
||||
@ -59,10 +70,13 @@ task pluginDir {
|
||||
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()
|
||||
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())
|
||||
"zip -d ${dest.toString()} *.jar".execute()
|
||||
}
|
||||
@ -80,7 +94,7 @@ task pack {
|
||||
doLast {
|
||||
if (project.pack200 == "true") {
|
||||
libDir.listFiles().stream().filter( { it.getName().endsWith(".jar") } ).
|
||||
filter({it.length() > 512*1024}).forEach {
|
||||
forEach {
|
||||
println "packing $it"
|
||||
def name = it.toString()
|
||||
println "pack200 --no-gzip ${name}.pack $name".execute().text
|
||||
@ -117,6 +131,7 @@ task sign {
|
||||
}
|
||||
|
||||
webappConfig.dependsOn pluginDir
|
||||
clientsConfig.dependsOn webappConfig
|
||||
pack.dependsOn webappConfig
|
||||
pluginZip.dependsOn(webappConfig,pluginConfig,pack)
|
||||
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}
|
||||
updateURL.su3=${updateURLsu3}
|
||||
min-i2p-version=${i2pVersion}
|
||||
min-java-version=1.8
|
||||
min-java-version=8
|
||||
consoleLinkName=MuWire
|
||||
consoleLinkTooltip=Anonymous File Sharing
|
||||
consoleLinkURL=/MuWire
|
||||
consoleLinkURL=/MuWire/
|
||||
console-icon=images/muwire.png
|
||||
<% println "date="+System.currentTimeMillis() %>
|
||||
|
@ -1,3 +1,6 @@
|
||||
apply plugin : 'application'
|
||||
mainClassName = 'com.muwire.update.UpdateServer'
|
||||
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 {
|
||||
developmentOnly
|
||||
runtimeClasspath {
|
||||
extendsFrom developmentOnly
|
||||
}
|
||||
warArtifact
|
||||
jarArtifact
|
||||
}
|
||||
|
||||
apply plugin : 'war'
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||
compile "org.springframework.boot:spring-boot-starter-logging"
|
||||
compile "org.springframework.boot:spring-boot-autoconfigure"
|
||||
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"
|
||||
providedCompile(project(':core')) {
|
||||
transitive = false
|
||||
}
|
||||
compile fileTree("../i2pjars") { include '*.jar' }
|
||||
}
|
||||
|
||||
bootRun {
|
||||
jvmArgs(
|
||||
'-Dspring.output.ansi.enabled=always',
|
||||
'-noverify',
|
||||
'-XX:TieredStopAtLevel=1',
|
||||
'-Xmx1024m')
|
||||
sourceResources sourceSets.main
|
||||
String springProfilesActive = 'spring.profiles.active'
|
||||
systemProperty springProfilesActive, System.getProperty(springProfilesActive)
|
||||
war {
|
||||
from 'src/main/css'
|
||||
from ('src/main/images', {
|
||||
into "images"
|
||||
})
|
||||
from ('src/main/js', {
|
||||
into "js"
|
||||
})
|
||||
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 {
|
||||
chromedriver '2.45.0'
|
||||
geckodriver '0.24.0'
|
||||
task precompileJsp {
|
||||
doLast {
|
||||
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) {
|
||||
systemProperty "geb.env", System.getProperty('geb.env')
|
||||
systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest")
|
||||
systemProperty "webdriver.chrome.driver", System.getProperty('webdriver.chrome.driver')
|
||||
systemProperty "webdriver.gecko.driver", System.getProperty('webdriver.gecko.driver')
|
||||
task generateWebXML {
|
||||
doLast {
|
||||
def template = new File("$projectDir/templates/web.xml.template")
|
||||
def templateText = template.text
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
assets {
|
||||
minifyJs = true
|
||||
minifyCss = true
|
||||
// compile the po files and put them in the jar
|
||||
task bundle {
|
||||
doLast {
|
||||
// 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 {
|
||||
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
|
6461
webui/grails-app/assets/javascripts/bootstrap.bundle.js
vendored
3944
webui/grails-app/assets/javascripts/bootstrap.js
vendored
@ -1,15 +0,0 @@
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS file within this directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
||||
* compiled file, but it's generally better to create a new file per style scope.
|
||||
*
|
||||
*= require bootstrap
|
||||
*= require grails
|
||||
*= require main
|
||||
*= require mobile
|
||||
*= require_self
|
||||
*/
|
1912
webui/grails-app/assets/stylesheets/bootstrap-grid.css
vendored
@ -1,331 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.1.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2018 The Bootstrap Authors
|
||||
* Copyright 2011-2018 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"] {
|
||||
-webkit-appearance: listbox;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
9030
webui/grails-app/assets/stylesheets/bootstrap.css
vendored
@ -1,109 +0,0 @@
|
||||
h1, h2 {
|
||||
margin: 10px 25px 5px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.filename {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.exceptionMessage {
|
||||
margin: 10px;
|
||||
border: 1px solid #000;
|
||||
padding: 5px;
|
||||
background-color: #E9E9E9;
|
||||
}
|
||||
|
||||
.stack,
|
||||
.snippet {
|
||||
margin: 0 25px 10px;
|
||||
}
|
||||
|
||||
.stack,
|
||||
.snippet {
|
||||
border: 1px solid #ccc;
|
||||
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* error details */
|
||||
.error-details {
|
||||
border-top: 1px solid #FFAAAA;
|
||||
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
border-bottom: 1px solid #FFAAAA;
|
||||
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||||
background-color:#FFF3F3;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
padding: 5px;
|
||||
padding-left:25px;
|
||||
}
|
||||
|
||||
.error-details dt {
|
||||
clear: left;
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.error-details dt:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
.error-details dd {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* stack trace */
|
||||
.stack {
|
||||
padding: 5px;
|
||||
overflow: auto;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
/* code snippet */
|
||||
.snippet {
|
||||
background-color: #fff;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.snippet .line {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.snippet .lineNumber {
|
||||
background-color: #ddd;
|
||||
color: #999;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
padding: 0 3px;
|
||||
text-align: right;
|
||||
width: 3em;
|
||||
}
|
||||
|
||||
.snippet .error {
|
||||
background-color: #fff3f3;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.snippet .error .lineNumber {
|
||||
background-color: #faa;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.snippet .line:first-child .lineNumber {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.snippet .line:last-child .lineNumber {
|
||||
padding-bottom: 5px;
|
||||
}
|