Compare commits

..

121 Commits

Author SHA1 Message Date
Jakob Borg
f2f9113fd9 nothing: Dummy commit 2016-05-14 08:02:02 +02:00
Jakob Borg
5d2414dfa9 lib/config: Bump config version to 14
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3092
2016-05-13 14:13:24 +00:00
Jakob Borg
bef2425025 cmd/syncthing: Set User-Agent on upgrade checks
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3093
2016-05-13 14:11:59 +00:00
Jakob Borg
e8b4286c93 lib/config: Change upgrade check URL (fixes #3086)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3089
2016-05-13 09:17:10 +00:00
Jakob Borg
2e9bf0b67c lib/upgrade: Increase size limits, send version header
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3088
2016-05-13 09:01:31 +00:00
Lars K.W. Gohlke
935c273c8f cleanup: removed deadcode in connection/tcp_listen.go
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3084
2016-05-12 20:43:11 +00:00
Jakob Borg
b993b41847 lib/config: Minor attribute updates
As discussed in
https://github.com/syncthing/docs/pull/169

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3082
2016-05-12 08:23:18 +00:00
Jakob Borg
1be40cc4fa lib/ignore: Revert comma handling, upgrade globbing package
This was fixed upstream due to our ticket, so we no longer need the
manual handling of commas. Keep the tests and better debug output around
though.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3081
2016-05-12 07:11:16 +00:00
Lars K.W. Gohlke
d628b731d1 build: Remove unused code
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3079
2016-05-11 06:21:30 +00:00
Jakob Borg
21e116aa45 lib/scanner: Refactor scanner.Walk API
The old usage pattern was to create a Walker with a bunch of attributes,
then call Walk() on it and nothing else. This extracts the attributes
into a Config struct and exposes a Walk(cfg Config) method instead, as
there was no reason to expose the state-holding walker type.

Also creates a few no-op implementations of the necessary interfaces
so that we can skip nil checks and simiplify things here and there.

Definitely look at this diff without whitespace.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3060
2016-05-09 18:25:39 +00:00
Jakob Borg
d77d8ff803 lib/connections: Don't look at devices that are already optimally connected
Just an optimization. Required exposing the priority from the factory,
so made that an interface with an extra method instead of just a func
type.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3071
2016-05-09 15:33:25 +00:00
Jakob Borg
31f64186ae lib/connections: More fine grained locking (fixes #3066)
This fixes the deadlock by reducing where we hold the various locks. To
start with it splits up the existing "mut" into a "listenersMut" and a
"curConMut" as these are the two things being protected and I can see no
relation between them that requires a shared lock. It also moves all
model calls outside of the lock, as I see no reason to hold the lock
while calling the model (and it's risky, as proven).

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3069
2016-05-09 15:03:12 +00:00
Jakob Borg
1a703efa78 lib/model: Fix accounting error in rescan with multiple subs (fixes #3028)
When doing prefix scans in the database, "foo" should not be considered
a prefix of "foo2". Instead, it should match "foo" exactly and also
strings with the prefix "foo/". This is more restrictive than what the
standard leveldb prefix scan does so we add some code to enforce it.

Also exposes the initialScanCompleted on the rwfolder for testing, and
change it to be a channel (so we can wait for it from another
goroutine). Otherwise we can't be sure when the initial scan has
completed, and we need to wait for that or it might pick up changes
we're doing at an unexpected time.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3067
2016-05-09 12:56:21 +00:00
Jakob Borg
8b7b0a03eb lib/config: Don't require restart when adding folders/devices or changing listen address
The VersioningConfig change is because it defaults to nil but gets
deserialized to map[string]string{}. Now prepare() enforces a single
representation of the empty map.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3065
2016-05-09 11:30:19 +00:00
Jakob Borg
0761d804a4 cmd/syncthing: Use random folder ID for default folder, limit random charset
This uses the same charset as the Javascript code, excluding confusing
characters like 0, O, I, 1, l etc.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3064
2016-05-09 09:43:40 +00:00
Jakob Borg
3ad42d9279 lib/util: Should seed random number generator on startup
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3063
2016-05-09 09:36:42 +00:00
klemens
bd41e21c26 all: Correct spelling in comments
Skip-check: authors pr-build-mac

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3056
2016-05-08 10:54:22 +00:00
Jakob Borg
10fe23b8f2 script: Don't verify authors on commits tagged 'Skip-check: authors'
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3057
2016-05-08 10:47:57 +00:00
Jakob Borg
39899e40bf cmd/syncthing: Use ReadAll + json.Unmarshal in places were we care about consuming the reader
Because json.NewDecoder(r).Decode(&v) doesn't necessarily consume all
data on the reader, that means an HTTP connection can't be reused. We
don't do a lot of HTTP traffic where we read JSON responses, but the
discovery is one such place. The other two are for POSTs from the GUI,
where it's not exactly critical but still nice if the connection still
can be keep-alive'd after the request as well.

Also ensure that we call req.Body.Close() for clarity, even though this
should by all accounts not really be necessary.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3050
2016-05-06 22:01:56 +00:00
Jakob Borg
5d337bb24f lib/ignore: Handle bare commas in ignore patterns (fixes #3042)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3048
2016-05-06 15:45:11 +00:00
Jakob Borg
dd5909568f lib/upgrade: Don't attempt processing files larger than expected max binary size (ref #3045)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3047
2016-05-06 14:14:19 +00:00
Jakob Borg
38166e976f lib/upgrade: Enforce limits on download archives (fixes #3045)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3046
2016-05-06 13:58:34 +00:00
Jakob Borg
d6a7ffe0d4 lib/upgrade: Auto upgrade signature should cover version & arch (fixes #3044)
New signature is the HMAC of archive name (which includes the release
version and architecture) plus the contents of the binary. This is
expected in a new file "release.sig" which may be present in a
subdirectory. The new release tools put this in [.]metadata/release.sig.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3043
2016-05-06 13:30:35 +00:00
Jakob Borg
2ebc6996a2 cmd/stsigtool: Sign stdin when not given a file to sign, or when given "-"
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3041
2016-05-05 19:05:45 +00:00
Jakob Borg
2e840134d2 lib/protocol: Add Request benchmarks over raw and TLS encrypted TCP channels
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3040
2016-05-04 23:07:07 +00:00
Jakob Borg
66e1be33cf lib/protocol: Delete erroneously checked in test binary
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3039
2016-05-04 22:09:07 +00:00
Jakob Borg
591959261c gui: Fix comparison operator in expression (ref #3035)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3036
2016-05-04 20:30:18 +00:00
Jakob Borg
459930df09 gui, man: Update docs & translations
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3034
2016-05-04 19:47:08 +00:00
Audrius Butkevicius
674fc566bb lib/connections: Refactor
1. Removes separate relay lists and relay clients/services, just makes it a listen address
2. Easier plugging-in of other transports
3. Allows "hot" disabling and enabling NAT services
4. Allows "hot" listen address changes
5. Changes listen address list with a preferable "default" value just like for discovery
6. Debounces global discovery announcements as external addresses change (which it might alot upon starting)
7. Stops this whole "pick other peers relay by latency". This information is no longer available,
   but I don't think it matters as most of the time other peer only has one relay.
8. Rename ListenAddress to ListenAddresses, as well as in javascript land.
9. Stop serializing deprecated values to JSON

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2982
2016-05-04 19:38:12 +00:00
Jakob Borg
09832abe50 lib/config: Change folder type attribute to a FolderType type
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3032
2016-05-04 11:26:36 +00:00
Audrius Butkevicius
eabd2fc936 lib/model: Use factories for creating folders
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3029
2016-05-04 10:47:33 +00:00
Jakob Borg
6720906ee5 lib/ignore: Refactor: notMatched should be one of the constants
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3031
2016-05-04 07:15:56 +00:00
Audrius Butkevicius
abb96802cb lib/ignores: Use bitmask for result
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3030
2016-05-01 15:58:23 +00:00
Audrius Butkevicius
29fa05ae05 lib/model: Discard download progress upon receiving an index update (fixes #2993)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3023
2016-05-01 06:49:29 +00:00
Audrius Butkevicius
49387f9494 lib/protocol: Clean up error values, unused flags
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3025
2016-04-30 04:35:38 +00:00
Alex
953482de53 cmd/stvanity: x509.GenerateCertificate requires pointer for public key
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3022
2016-04-28 22:22:33 +00:00
Audrius Butkevicius
8cf3a7aeda lib/model: Prettify tests (fixes #3014)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3016
2016-04-26 20:19:30 +00:00
Jakob Borg
b8c5cf1142 lib/model: Refactor: complete renaming of p -> f
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3015
2016-04-26 15:11:19 +00:00
Lars K.W. Gohlke
236f121c4e lib/model: Refactor out folder and folderscan types, simplify somewhat
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3007
2016-04-26 14:01:46 +00:00
Audrius Butkevicius
2467678bd4 lib/dialer: Add env var to disable proxy fallback (fixes #3006)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3009
2016-04-24 16:30:20 +00:00
Lars K.W. Gohlke
e87c1abd4e all: Clean up dead code
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3005
2016-04-22 21:15:36 +00:00
Lars K.W. Gohlke
dffc34559b lib/config: Remove dead code
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3004
2016-04-22 20:30:58 +00:00
Lars K.W. Gohlke
80f2a9a6bf readme: Remove Appveyor icon
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3003
2016-04-22 20:30:37 +00:00
Audrius Butkevicius
4aa6ecb122 lib/model: Do not use WRONLY (ref #2584)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2994
2016-04-22 08:12:10 +00:00
Jakob Borg
ccfcdf7f48 cmd/syncthing: Don't compact database at startup
This happens automatically in the background anyway, and it can take a
long time on low powered devices at an inconvenient time. We just want
to get up and running as quickly as possible.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3000
2016-04-22 07:34:11 +00:00
Jakob Borg
4eb23a38b1 cmd/stvanity: Use Go 1.3 compatible interface
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2998
2016-04-20 08:16:42 +00:00
Jakob Borg
cb38213444 build: Remove Appveyor and old CircleCI stuff
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2997
2016-04-20 07:12:01 +00:00
Jakob Borg
842b6111db vendor: Update github.com/gobwas/glob to solve 1.3 build issue
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2996
2016-04-20 07:05:08 +00:00
Audrius Butkevicius
ea54525a33 lib/connections: Try not to deadlock (fixes #2987)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2991
2016-04-18 20:25:31 +00:00
Jakob Borg
893cc025f9 cmd/syncthing: Accept ISO-8859-1 and UTF-8 in HTTP BasicAuth header (fixes #2779)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2989
2016-04-18 20:24:38 +00:00
Jakob Borg
b81c8d2e1b lib/model: Drop incoming updates for ignored items (fixes #1701)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2975
2016-04-18 18:35:31 +00:00
Jakob Borg
4b07535e86 gui: Downgrade Angular 1.5.3 -> 1.2.9 (fixes #2961)
I haven't been able to figure out the problem, despite a lot of
experimenting with this...

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2988
2016-04-18 12:34:29 +00:00
Jakob Borg
0d2fe320a7 cmd/stvanity: New utility to create vanity device IDs
A potential practical use is to encode a short version of the hostname
at the beginning of the device ID.

For example:

	jb@syno:~/s/g/s/s/c/stvanity $ stvanity abc
	Want 15 bits for prefix "ABC", about 3.3e+04 certs to test (statistically speaking)
	Found ABCFPWS-JKDIFV3-E5IUAQW-DK53WVR-HY7XWBS-56H33GR-CJQI67Q-VGXRMAW
	Saved to cert.pem, key.pem

	jb@syno:~/s/g/s/s/c/stvanity $ stvanity $(hostname)
	Want 20 bits for prefix "SYNO", about 1e+06 certs to test (statistically speaking)
	Trying 554 certs/s, tested 8307 so far in 15s, expect ~32m total time to complete
	Trying 543 certs/s, tested 16277 so far in 30s, expect ~32m total time to complete
	...

The rest is just a matter of patience.

	jb@syno:~/s/g/s/s/c/stvanity $ stvanity syncthing
	Want 50 bits for prefix "SYNCTHI-NG", about 1.1e+15 certs to test (statistically speaking)
	Trying 529 certs/s, tested 7941 so far in 15s, expect ~67443 years total time to complete
	...

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2986
2016-04-17 20:42:26 +00:00
Jakob Borg
f294113d01 cmd/stdisco: New utility to debug local discovery
When run without parameters, attempts to listen for local discovery
announcements just like Syncthing, and prints them.

With -send, it also sends fake discovery packets. This can be used on
two or more computers simultaneously to verify that they can see each
other.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2985
2016-04-17 18:47:38 +00:00
Audrius Butkevicius
1c7af1a72e lib/upnp: Fix port order
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2980
2016-04-16 22:44:07 +00:00
Audrius Butkevicius
e61f424ade lib/{nat,pmp}: Fix shadowing and nil IPs
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2979
2016-04-16 16:48:07 +00:00
Jakob Borg
fa1cfd94d0 lib/versioner: Refactor for testing, speed up test
Test now takes <1 second instead of 100 seconds

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2978
2016-04-15 14:26:39 +00:00
Jakob Borg
0155b6f841 Update docs & translations
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2976
2016-04-15 11:47:33 +00:00
Jakob Borg
f6953624dd lib/model: Test should pass go vet inspections
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2977
2016-04-15 11:41:18 +00:00
AudriusButkevicius
1a5f524ae4 lib/model, lib/protocol: Implement temporary indexes (fixes #950)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2252
2016-04-15 10:59:41 +00:00
Jakob Borg
a4cd4cc253 build: Clean up "go vet" and "go lint" steps
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2972
2016-04-15 07:26:25 +00:00
Audrius Butkevicius
c49453c519 lib/pmp: Add NAT-PMP support (ref #698)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2968
2016-04-13 18:50:40 +00:00
Jakob Borg
52c7804f32 lib/connections: Silence vet and lint warnings
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2971
2016-04-13 11:50:51 +00:00
Audrius Butkevicius
19b4f3bfb4 lib/nat: Add a nat package and service to track mappings on multiple IGDs 2016-04-10 19:36:38 +00:00
Jakob Borg
f3ac421266 lib/protocol: Comment the bit numbers for flags in IndexMessage 2016-04-10 10:47:30 +00:00
Michael Ploujnikov
7533a61203 unifySubs: add two trivial test cases 2016-04-10 02:41:28 +00:00
Jakob Borg
6355a7019b gui: Update translations for previous commit 2016-04-09 12:23:13 +00:00
Audrius Butkevicius
490464e170 gui: Some browsers force lowercase attributes 2016-04-09 12:20:56 +00:00
Michael Ploujnikov
467d338fe4 lib/model: Scanning unknown items is OK as long as the parent is known (fixes #2915) 2016-04-09 11:25:06 +00:00
Audrius Butkevicius
6130578d18 lib/db: Empty slice is not nil (fixes #2872) 2016-04-09 07:46:19 +00:00
Audrius Butkevicius
4389bb037d lib/model: Add option for overwriting names on connect (fixes #2912) 2016-04-09 07:43:47 +00:00
Audrius Butkevicius
2eb8a9ef56 all: Dead code cleanup 2016-04-09 01:10:31 +00:00
Audrius Butkevicius
393798098c cmd/syncthing: Listening on a 0 port is not valid (fixes #2926) 2016-04-09 01:06:55 +00:00
Jakob Borg
668eb7c398 build: Archives should have release name as first file name component 2016-04-08 10:53:29 +00:00
Jakob Borg
0937f85534 gui, man: Update docs & translations 2016-04-08 10:25:40 +00:00
Jakob Borg
cf64376dca build: go vet is now included in the distribution 2016-04-08 10:19:11 +00:00
Audrius Butkevicius
5a98af622d lib/ignore: Implement deletable ignores using (?d) prefix (fixes #1362) 2016-04-07 09:34:07 +00:00
Jakob Borg
4f5d0b46f7 build: Parameterize build targets 2016-04-06 22:18:30 +01:00
Lars K.W. Gohlke
492e92d65d gui: Show Javascript error indicator on dev builds, remove logging of missing translations 2016-04-05 06:36:53 +00:00
Jakob Borg
181939c841 lib/ignore: Correct case insensitive matching on Mac/Windows
There was a bug in that we only did the lowercase folding when the
pattern had an explicit (?i), which is not the case on Windows/Mac
necessarily.
2016-04-05 06:35:51 +00:00
Audrius Butkevicius
b678b4e048 cmd/syncthing: Skip a calculation if timediff is zero (fixes #2854) 2016-04-05 07:12:17 +02:00
Jakob Borg
1934b3a5b6 lib/ignore: Remove pattern for foo/** which is already covered by foo/
Actual speed difference according to benchmarks is hidden in the noise



Also make the "pattern" field for each entry match what is actually

evaluated.
2016-04-04 13:22:25 +01:00
Jakob Borg
cc1d122352 lib/model: Correctly detect deleted but previously ignored files as deleted 2016-04-04 11:53:55 +01:00
Jakob Borg
a4f0b85462 lib/config: Disable cacheIgnoredFiles, new default is disabled 2016-04-03 20:18:16 +01:00
Laurent Etiemble
7b4e1e9055 cmd/syncthing: Fix handler ordering so CORS middleware wraps all the others but the debug one 2016-04-03 13:24:55 +02:00
Jakob Borg
4c3cd4c9e3 lib/ignore: Replace lib/fnmatch with github.com/gobwas/glob
Because it's literally ten times faster:



	benchmark                  old ns/op     new ns/op     delta

	BenchmarkMatch-8           13842         1200          -91.33%

	BenchmarkMatchCached-8     139           147           +5.76%



	benchmark                  old allocs     new allocs     delta

	BenchmarkMatch-8           0              0              +0.00%

	BenchmarkMatchCached-8     0              0              +0.00%



	benchmark                  old bytes     new bytes     delta

	BenchmarkMatch-8           12            0             -100.00%

	BenchmarkMatchCached-8     0             0             +0.00%
2016-04-02 20:03:24 +01:00
Lars K.W. Gohlke
46e913dc23 gui: Improve header and footer layout on small screens 2016-04-02 10:31:16 +02:00
Jakob Borg
8f580b13df gui: Update translations and documentation 2016-04-01 07:33:42 +00:00
Jakob Borg
a551686d37 lib/discovery: Receiving a new announcement should be non-blocking
Pretty sure the intention of the select was for it to be non-blocking.
Not that it will matter almost ever.
2016-04-01 07:24:04 +00:00
Audrius Butkevicius
432c78079b lib/connections: Increase lock periods to prevent races (fixes #2899) 2016-04-01 07:23:11 +00:00
Jakob Borg
f5f0e46016 lib: Use bytes.Equal instead of bytes.Compare where possible 2016-03-31 15:12:46 +00:00
Jakob Borg
b6f32b6e45 build: Clean up environment handling
Don't set variables for cross compilation when building assets, cleaner
check for Go version.
2016-03-31 07:33:09 +00:00
Michael Ploujnikov
66f480519b lib/model: Refactor out scanning method from rwfolder.Serve loop 2016-03-30 06:53:47 +00:00
Jakob Borg
8044522691 vendor: Update calmh/xdr to avoid unexpected string behavior (fixes #2882) 2016-03-29 19:55:43 +00:00
Jakob Borg
c6a67bd203 gui: Update lang-en with new strings from GUI 2016-03-28 10:55:35 +00:00
Jakob Borg
c6881b6d02 gui: Update Bootstrap (v3.3.6), non-minified 2016-03-28 10:51:12 +00:00
Jakob Borg
4489bec6ef gui: Upgrade jQuery (v2.2.2), non-minified 2016-03-28 10:50:19 +00:00
Jakob Borg
3d71e68696 gui: Upgrade Angular (v1.5.3 plus various), non-minified 2016-03-28 10:46:51 +00:00
Jakob Borg
783d2da4a8 gui: Upgrade Font Awesome (v4.5.0), non-minified 2016-03-28 10:46:05 +00:00
Jakob Borg
6be4b49999 build: Generate gui.files.go on the fly, remove from repo 2016-03-28 10:03:13 +00:00
Audrius Butkevicius
68185dd93c gui: Remove bootswatch 2016-03-27 14:08:17 +00:00
Jakob Borg
d01ea9d6fb lib/discovery: Handle nil relayService (fixes #2890) 2016-03-27 11:37:43 +00:00
Jakob Borg
d91e6023eb lib/sync: Skip the timing tests if the host timer is flaky 2016-03-27 10:41:38 +00:00
Jakob Borg
17ed01a0c9 lib/connections: Rename makeTcp -> makeTCP according to go vet's wishes 2016-03-27 07:18:33 +00:00
Audrius Butkevicius
4b6c2d0d3d gui: Pretty theme names 2016-03-27 06:40:50 +00:00
norgeous
46c07bb207 gui: Clean up CSS 2016-03-27 00:43:07 +00:00
norgeous
eaa805b9f0 gui: add Bootswatch themes 2016-03-26 21:57:05 +00:00
Lars K.W. Gohlke
436fd0b88e pull_request_template: Add note about docs needing update 2016-03-26 07:00:12 +00:00
Jakob Borg
f706d3c393 cmd/stbench: Add utility to run benchmark tests 2016-03-25 20:52:20 +00:00
Jakob Borg
c58eb1d47a cmd/stgenfiles: Add utility for generating test data
I use this to generate ~40 gigs of random test data in 200k files:

    ~/stgenfiles -dir /data/benchdata -files 200000 -maxexp 22 -src /dev/urandom
2016-03-25 20:50:48 +00:00
Audrius Butkevicius
b4f9a55e6e protocol: Add "Hello" message at connection start, also for unauthed peers 2016-03-25 20:29:07 +00:00
Audrius Butkevicius
1d17891286 lib/upnp: Refactor out methods to util with tests, refactor IGD 2016-03-25 20:22:29 +00:00
Audrius Butkevicius
6a3f3f5577 gui: Add theme.css, move dark theme, adjust popover advanced folder settings colors (fixes #2878) 2016-03-25 16:55:53 +00:00
Audrius Butkevicius
29913dd1e4 lib/connections: Refactor address listing into connection service 2016-03-25 07:35:18 +00:00
Audrius Butkevicius
690837dbe5 lib/connections: Allow "tcp4" and "tcp6" addresses 2016-03-25 07:15:32 +00:00
norgeous
82e80a479a gui: Add bootstrap tooltip to existing tooltip on folders 2016-03-25 07:09:55 +00:00
norgeous
bc508aee7b gui: Docs links should be HTTPS 2016-03-25 07:02:29 +00:00
Jakob Borg
95247f7740 cmd/syncthing: Basic smoke test of all API endpoints
... except /rest/system/upgrade that requires a correct response from
Github, which we shouldn't depend on.
2016-03-24 10:17:04 +00:00
Jakob Borg
e5731229c7 cmd/syncthing: Add test for starting API service and requesting some URLs 2016-03-24 08:55:33 +00:00
Jakob Borg
52c74ad866 cmd/syncthing: Add mock types for API service testing 2016-03-24 08:09:13 +00:00
Jakob Borg
a28f890e83 issue_template: Add note about using forum for support 2016-03-23 20:50:52 +00:00
977 changed files with 97100 additions and 10214 deletions

3
.gitattributes vendored
View File

@@ -2,8 +2,7 @@
* text=auto
# Except the dependencies, which we leave alone
Godeps/** -text=auto
vendor/** -text=auto
# Diffs on these files are meaningless
gui.files.go -diff
*.svg -diff

2
.gitignore vendored
View File

@@ -1,5 +1,6 @@
syncthing
!gui/syncthing
!debian/syncthing
!Godeps/_workspace/src/github.com/syncthing
syncthing.exe
*.tar.gz
@@ -14,3 +15,4 @@ coverage.xml
syncthing.sig
RELEASE
deb
lib/auto/gui.files.go

View File

@@ -24,7 +24,9 @@ Chris Howie <me@chrishowie.com>
Chris Joel <chris@scriptolo.gy>
Colin Kennedy <moshen.colin@gmail.com>
Daniel Bergmann <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
Daniel Harte <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
Daniel Martí <mvdan@mvdan.cc>
David Rimmer <dinosore@dbrsoftware.co.uk>
Denis A. <denisva@gmail.com>
Dennis Wilson <dw@risu.io>
Dominik Heidler <dominik@heidler.eu>
@@ -47,8 +49,10 @@ Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
Jochen Voss <voss@seehuhn.de>
Johan Vromans <jvromans@squirrel.nl>
Karol Różycki <rozycki.karol@gmail.com>
Kelong Cong <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
Ken'ichi Kamada <kamada@nanohz.org>
Kevin Allen <kma1660@gmail.com>
Lars K.W. Gohlke <lkwg82@gmx.de>
Laurent Etiemble <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
Lode Hoste <zillode@zillode.be>
Lord Landon Agahnim <lordlandon@gmail.com>

90
Godeps/Godeps.json generated
View File

@@ -1,90 +0,0 @@
{
"ImportPath": "github.com/syncthing/syncthing",
"GoVersion": "go1.5.2",
"Packages": [
"./cmd/..."
],
"Deps": [
{
"ImportPath": "github.com/bkaradzic/go-lz4",
"Rev": "74ddf82598bc4745b965729e9c6a463bedd33049"
},
{
"ImportPath": "github.com/calmh/du",
"Rev": "3c0690cca16228b97741327b1b6781397afbdb24"
},
{
"ImportPath": "github.com/calmh/luhn",
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "9eb3e1a622d9364deb39c831f7e5f164393d7e37"
},
{
"ImportPath": "github.com/golang/snappy",
"Rev": "723cc1e459b8eea2dea4583200fd60757d40097a"
},
{
"ImportPath": "github.com/juju/ratelimit",
"Rev": "772f5c38e468398c4511514f4f6aa9a4185bc0a0"
},
{
"ImportPath": "github.com/kardianos/osext",
"Rev": "29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc"
},
{
"ImportPath": "github.com/rcrowley/go-metrics",
"Rev": "1ce93efbc8f9c568886b2ef85ce305b2217b3de3"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "1a9d62f03ea92815b46fcaab357cfd4df264b1a0"
},
{
"ImportPath": "github.com/thejerf/suture",
"Comment": "v1.0.1",
"Rev": "99c1f2d613756768fc4299acd9dc621e11ed3fd7"
},
{
"ImportPath": "github.com/vitrun/qart/coding",
"Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0"
},
{
"ImportPath": "github.com/vitrun/qart/gf256",
"Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0"
},
{
"ImportPath": "github.com/vitrun/qart/qr",
"Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0"
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "575fdbe86e5dd89229707ebec0575ce7d088a4a6"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "575fdbe86e5dd89229707ebec0575ce7d088a4a6"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "042ba42fa6633b34205efc66ba5719cd3afd8d38"
},
{
"ImportPath": "golang.org/x/net/ipv6",
"Rev": "042ba42fa6633b34205efc66ba5719cd3afd8d38"
},
{
"ImportPath": "golang.org/x/net/proxy",
"Rev": "042ba42fa6633b34205efc66ba5719cd3afd8d38"
},
{
"ImportPath": "golang.org/x/text/transform",
"Rev": "5eb8d4684c4796dd36c74f6452f2c0fa6c79597e"
},
{
"ImportPath": "golang.org/x/text/unicode/norm",
"Rev": "5eb8d4684c4796dd36c74f6452f2c0fa6c79597e"
}
]
}

5
Godeps/Readme generated
View File

@@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

2
Godeps/_workspace/.gitignore generated vendored
View File

@@ -1,2 +0,0 @@
/pkg
/bin

View File

@@ -1 +0,0 @@
/lz4-example/lz4-example

View File

@@ -1,9 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- 1.5
- tip

View File

@@ -1 +0,0 @@
coverage.out

View File

@@ -1,19 +0,0 @@
language: go
go:
- tip
install:
- export PATH=$PATH:$HOME/gopath/bin
- go get golang.org/x/tools/cover
- go get github.com/mattn/goveralls
script:
- ./generate.sh
- go test -coverprofile=coverage.out
after_success:
- goveralls -coverprofile=coverage.out -service=travis-ci -package=calmh/xdr -repotoken="$COVERALLS_TOKEN"
env:
global:
secure: SmgnrGfp2zLrA44ChRMpjPeujubt9veZ8Fx/OseMWECmacyV5N/TuDhzIbwo6QwV4xB0sBacoPzvxQbJRVjNKsPiSu72UbcQmQ7flN4Tf7nW09tSh1iW8NgrpBCq/3UYLoBu2iPBEBKm93IK0aGNAKs6oEkB0fU27iTVBwiTXOY=

View File

@@ -1,16 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
package xdr
import (
"log"
"os"
)
var (
debug = len(os.Getenv("XDRTRACE")) > 0
dl = log.New(os.Stdout, "xdr: ", log.Lshortfile|log.Ltime|log.Lmicroseconds)
)
const maxDebugBytes = 32

View File

@@ -1,10 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
// +build ipdr
package xdr
func pad(l int) int {
return 0
}

View File

@@ -1,14 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
// +build !ipdr
package xdr
func pad(l int) int {
d := l % 4
if d == 0 {
return 0
}
return 4 - d
}

View File

@@ -1,171 +0,0 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package xdr
import (
"fmt"
"io"
"reflect"
"unsafe"
)
type Reader struct {
r io.Reader
err error
b [8]byte
}
func NewReader(r io.Reader) *Reader {
return &Reader{
r: r,
}
}
func (r *Reader) ReadRaw(bs []byte) (int, error) {
if r.err != nil {
return 0, r.err
}
var n int
n, r.err = io.ReadFull(r.r, bs)
return n, r.err
}
func (r *Reader) ReadString() string {
return r.ReadStringMax(0)
}
func (r *Reader) ReadStringMax(max int) string {
buf := r.ReadBytesMaxInto(max, nil)
bh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh := reflect.StringHeader{
Data: bh.Data,
Len: bh.Len,
}
return *((*string)(unsafe.Pointer(&sh)))
}
func (r *Reader) ReadBytes() []byte {
return r.ReadBytesInto(nil)
}
func (r *Reader) ReadBytesMax(max int) []byte {
return r.ReadBytesMaxInto(max, nil)
}
func (r *Reader) ReadBytesInto(dst []byte) []byte {
return r.ReadBytesMaxInto(0, dst)
}
func (r *Reader) ReadBytesMaxInto(max int, dst []byte) []byte {
if r.err != nil {
return nil
}
l := int(r.ReadUint32())
if r.err != nil {
return nil
}
if l < 0 || max > 0 && l > max {
// l may be negative on 32 bit builds
r.err = ElementSizeExceeded("bytes field", l, max)
return nil
}
if fullLen := l + pad(l); fullLen > len(dst) {
dst = make([]byte, fullLen)
} else {
dst = dst[:fullLen]
}
var n int
n, r.err = io.ReadFull(r.r, dst)
if r.err != nil {
if debug {
dl.Printf("rd bytes (%d): %v", len(dst), r.err)
}
return nil
}
if debug {
if n > maxDebugBytes {
dl.Printf("rd bytes (%d): %x...", len(dst), dst[:maxDebugBytes])
} else {
dl.Printf("rd bytes (%d): %x", len(dst), dst)
}
}
return dst[:l]
}
func (r *Reader) ReadBool() bool {
return r.ReadUint8() != 0
}
func (r *Reader) ReadUint32() uint32 {
if r.err != nil {
return 0
}
_, r.err = io.ReadFull(r.r, r.b[:4])
if r.err != nil {
if debug {
dl.Printf("rd uint32: %v", r.err)
}
return 0
}
v := uint32(r.b[3]) | uint32(r.b[2])<<8 | uint32(r.b[1])<<16 | uint32(r.b[0])<<24
if debug {
dl.Printf("rd uint32=%d (0x%08x)", v, v)
}
return v
}
func (r *Reader) ReadUint64() uint64 {
if r.err != nil {
return 0
}
_, r.err = io.ReadFull(r.r, r.b[:8])
if r.err != nil {
if debug {
dl.Printf("rd uint64: %v", r.err)
}
return 0
}
v := uint64(r.b[7]) | uint64(r.b[6])<<8 | uint64(r.b[5])<<16 | uint64(r.b[4])<<24 |
uint64(r.b[3])<<32 | uint64(r.b[2])<<40 | uint64(r.b[1])<<48 | uint64(r.b[0])<<56
if debug {
dl.Printf("rd uint64=%d (0x%016x)", v, v)
}
return v
}
type XDRError struct {
op string
err error
}
func (e XDRError) Error() string {
return "xdr " + e.op + ": " + e.err.Error()
}
func (e XDRError) IsEOF() bool {
return e.err == io.EOF
}
func (r *Reader) Error() error {
if r.err == nil {
return nil
}
return XDRError{"read", r.err}
}
func ElementSizeExceeded(field string, size, limit int) error {
return fmt.Errorf("%s exceeds size limit; %d > %d", field, size, limit)
}

View File

@@ -1,49 +0,0 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build ipdr
package xdr
import "io"
func (r *Reader) ReadUint8() uint8 {
if r.err != nil {
return 0
}
_, r.err = io.ReadFull(r.r, r.b[:1])
if r.err != nil {
if debug {
dl.Printf("rd uint8: %v", r.err)
}
return 0
}
if debug {
dl.Printf("rd uint8=%d (0x%02x)", r.b[0], r.b[0])
}
return r.b[0]
}
func (r *Reader) ReadUint16() uint16 {
if r.err != nil {
return 0
}
_, r.err = io.ReadFull(r.r, r.b[:2])
if r.err != nil {
if debug {
dl.Printf("rd uint16: %v", r.err)
}
return 0
}
v := uint16(r.b[1]) | uint16(r.b[0])<<8
if debug {
dl.Printf("rd uint16=%d (0x%04x)", v, v)
}
return v
}

View File

@@ -1,15 +0,0 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build !ipdr
package xdr
func (r *Reader) ReadUint8() uint8 {
return uint8(r.ReadUint32())
}
func (r *Reader) ReadUint16() uint16 {
return uint16(r.ReadUint32())
}

View File

@@ -1,146 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
package xdr
import (
"io"
"reflect"
"unsafe"
)
var padBytes = []byte{0, 0, 0}
type Writer struct {
w io.Writer
tot int
err error
b [8]byte
}
type AppendWriter []byte
func (w *AppendWriter) Write(bs []byte) (int, error) {
*w = append(*w, bs...)
return len(bs), nil
}
func NewWriter(w io.Writer) *Writer {
return &Writer{
w: w,
}
}
func (w *Writer) WriteRaw(bs []byte) (int, error) {
if w.err != nil {
return 0, w.err
}
var n int
n, w.err = w.w.Write(bs)
return n, w.err
}
func (w *Writer) WriteString(s string) (int, error) {
sh := *((*reflect.StringHeader)(unsafe.Pointer(&s)))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return w.WriteBytes(*(*[]byte)(unsafe.Pointer(&bh)))
}
func (w *Writer) WriteBytes(bs []byte) (int, error) {
if w.err != nil {
return 0, w.err
}
w.WriteUint32(uint32(len(bs)))
if w.err != nil {
return 0, w.err
}
if debug {
if len(bs) > maxDebugBytes {
dl.Printf("wr bytes (%d): %x...", len(bs), bs[:maxDebugBytes])
} else {
dl.Printf("wr bytes (%d): %x", len(bs), bs)
}
}
var l, n int
n, w.err = w.w.Write(bs)
l += n
if p := pad(len(bs)); w.err == nil && p > 0 {
n, w.err = w.w.Write(padBytes[:p])
l += n
}
w.tot += l
return l, w.err
}
func (w *Writer) WriteBool(v bool) (int, error) {
if v {
return w.WriteUint8(1)
} else {
return w.WriteUint8(0)
}
}
func (w *Writer) WriteUint32(v uint32) (int, error) {
if w.err != nil {
return 0, w.err
}
if debug {
dl.Printf("wr uint32=%d", v)
}
w.b[0] = byte(v >> 24)
w.b[1] = byte(v >> 16)
w.b[2] = byte(v >> 8)
w.b[3] = byte(v)
var l int
l, w.err = w.w.Write(w.b[:4])
w.tot += l
return l, w.err
}
func (w *Writer) WriteUint64(v uint64) (int, error) {
if w.err != nil {
return 0, w.err
}
if debug {
dl.Printf("wr uint64=%d", v)
}
w.b[0] = byte(v >> 56)
w.b[1] = byte(v >> 48)
w.b[2] = byte(v >> 40)
w.b[3] = byte(v >> 32)
w.b[4] = byte(v >> 24)
w.b[5] = byte(v >> 16)
w.b[6] = byte(v >> 8)
w.b[7] = byte(v)
var l int
l, w.err = w.w.Write(w.b[:8])
w.tot += l
return l, w.err
}
func (w *Writer) Tot() int {
return w.tot
}
func (w *Writer) Error() error {
if w.err == nil {
return nil
}
return XDRError{"write", w.err}
}

View File

@@ -1,41 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
// +build ipdr
package xdr
func (w *Writer) WriteUint8(v uint8) (int, error) {
if w.err != nil {
return 0, w.err
}
if debug {
dl.Printf("wr uint8=%d", v)
}
w.b[0] = byte(v)
var l int
l, w.err = w.w.Write(w.b[:1])
w.tot += l
return l, w.err
}
func (w *Writer) WriteUint16(v uint16) (int, error) {
if w.err != nil {
return 0, w.err
}
if debug {
dl.Printf("wr uint8=%d", v)
}
w.b[0] = byte(v >> 8)
w.b[1] = byte(v)
var l int
l, w.err = w.w.Write(w.b[:2])
w.tot += l
return l, w.err
}

View File

@@ -1,14 +0,0 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
// +build !ipdr
package xdr
func (w *Writer) WriteUint8(v uint8) (int, error) {
return w.WriteUint32(uint32(v))
}
func (w *Writer) WriteUint16(v uint16) (int, error) {
return w.WriteUint32(uint32(v))
}

View File

@@ -1,254 +0,0 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package snappy
import (
"encoding/binary"
"io"
)
// We limit how far copy back-references can go, the same as the C++ code.
const maxOffset = 1 << 15
// emitLiteral writes a literal chunk and returns the number of bytes written.
func emitLiteral(dst, lit []byte) int {
i, n := 0, uint(len(lit)-1)
switch {
case n < 60:
dst[0] = uint8(n)<<2 | tagLiteral
i = 1
case n < 1<<8:
dst[0] = 60<<2 | tagLiteral
dst[1] = uint8(n)
i = 2
case n < 1<<16:
dst[0] = 61<<2 | tagLiteral
dst[1] = uint8(n)
dst[2] = uint8(n >> 8)
i = 3
case n < 1<<24:
dst[0] = 62<<2 | tagLiteral
dst[1] = uint8(n)
dst[2] = uint8(n >> 8)
dst[3] = uint8(n >> 16)
i = 4
case int64(n) < 1<<32:
dst[0] = 63<<2 | tagLiteral
dst[1] = uint8(n)
dst[2] = uint8(n >> 8)
dst[3] = uint8(n >> 16)
dst[4] = uint8(n >> 24)
i = 5
default:
panic("snappy: source buffer is too long")
}
if copy(dst[i:], lit) != len(lit) {
panic("snappy: destination buffer is too short")
}
return i + len(lit)
}
// emitCopy writes a copy chunk and returns the number of bytes written.
func emitCopy(dst []byte, offset, length int) int {
i := 0
for length > 0 {
x := length - 4
if 0 <= x && x < 1<<3 && offset < 1<<11 {
dst[i+0] = uint8(offset>>8)&0x07<<5 | uint8(x)<<2 | tagCopy1
dst[i+1] = uint8(offset)
i += 2
break
}
x = length
if x > 1<<6 {
x = 1 << 6
}
dst[i+0] = uint8(x-1)<<2 | tagCopy2
dst[i+1] = uint8(offset)
dst[i+2] = uint8(offset >> 8)
i += 3
length -= x
}
return i
}
// Encode returns the encoded form of src. The returned slice may be a sub-
// slice of dst if dst was large enough to hold the entire encoded block.
// Otherwise, a newly allocated slice will be returned.
// It is valid to pass a nil dst.
func Encode(dst, src []byte) []byte {
if n := MaxEncodedLen(len(src)); len(dst) < n {
dst = make([]byte, n)
}
// The block starts with the varint-encoded length of the decompressed bytes.
d := binary.PutUvarint(dst, uint64(len(src)))
// Return early if src is short.
if len(src) <= 4 {
if len(src) != 0 {
d += emitLiteral(dst[d:], src)
}
return dst[:d]
}
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
const maxTableSize = 1 << 14
shift, tableSize := uint(32-8), 1<<8
for tableSize < maxTableSize && tableSize < len(src) {
shift--
tableSize *= 2
}
var table [maxTableSize]int
// Iterate over the source bytes.
var (
s int // The iterator position.
t int // The last position with the same hash as s.
lit int // The start position of any pending literal bytes.
)
for s+3 < len(src) {
// Update the hash table.
b0, b1, b2, b3 := src[s], src[s+1], src[s+2], src[s+3]
h := uint32(b0) | uint32(b1)<<8 | uint32(b2)<<16 | uint32(b3)<<24
p := &table[(h*0x1e35a7bd)>>shift]
// We need to to store values in [-1, inf) in table. To save
// some initialization time, (re)use the table's zero value
// and shift the values against this zero: add 1 on writes,
// subtract 1 on reads.
t, *p = *p-1, s+1
// If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte.
if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] {
s++
continue
}
// Otherwise, we have a match. First, emit any pending literal bytes.
if lit != s {
d += emitLiteral(dst[d:], src[lit:s])
}
// Extend the match to be as long as possible.
s0 := s
s, t = s+4, t+4
for s < len(src) && src[s] == src[t] {
s++
t++
}
// Emit the copied bytes.
d += emitCopy(dst[d:], s-t, s-s0)
lit = s
}
// Emit any final pending literal bytes and return.
if lit != len(src) {
d += emitLiteral(dst[d:], src[lit:])
}
return dst[:d]
}
// MaxEncodedLen returns the maximum length of a snappy block, given its
// uncompressed length.
func MaxEncodedLen(srcLen int) int {
// Compressed data can be defined as:
// compressed := item* literal*
// item := literal* copy
//
// The trailing literal sequence has a space blowup of at most 62/60
// since a literal of length 60 needs one tag byte + one extra byte
// for length information.
//
// Item blowup is trickier to measure. Suppose the "copy" op copies
// 4 bytes of data. Because of a special check in the encoding code,
// we produce a 4-byte copy only if the offset is < 65536. Therefore
// the copy op takes 3 bytes to encode, and this type of item leads
// to at most the 62/60 blowup for representing literals.
//
// Suppose the "copy" op copies 5 bytes of data. If the offset is big
// enough, it will take 5 bytes to encode the copy op. Therefore the
// worst case here is a one-byte literal followed by a five-byte copy.
// That is, 6 bytes of input turn into 7 bytes of "compressed" data.
//
// This last factor dominates the blowup, so the final estimate is:
return 32 + srcLen + srcLen/6
}
// NewWriter returns a new Writer that compresses to w, using the framing
// format described at
// https://github.com/google/snappy/blob/master/framing_format.txt
func NewWriter(w io.Writer) *Writer {
return &Writer{
w: w,
enc: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)),
}
}
// Writer is an io.Writer than can write Snappy-compressed bytes.
type Writer struct {
w io.Writer
err error
enc []byte
buf [checksumSize + chunkHeaderSize]byte
wroteHeader bool
}
// Reset discards the writer's state and switches the Snappy writer to write to
// w. This permits reusing a Writer rather than allocating a new one.
func (w *Writer) Reset(writer io.Writer) {
w.w = writer
w.err = nil
w.wroteHeader = false
}
// Write satisfies the io.Writer interface.
func (w *Writer) Write(p []byte) (n int, errRet error) {
if w.err != nil {
return 0, w.err
}
if !w.wroteHeader {
copy(w.enc, magicChunk)
if _, err := w.w.Write(w.enc[:len(magicChunk)]); err != nil {
w.err = err
return n, err
}
w.wroteHeader = true
}
for len(p) > 0 {
var uncompressed []byte
if len(p) > maxUncompressedChunkLen {
uncompressed, p = p[:maxUncompressedChunkLen], p[maxUncompressedChunkLen:]
} else {
uncompressed, p = p, nil
}
checksum := crc(uncompressed)
// Compress the buffer, discarding the result if the improvement
// isn't at least 12.5%.
chunkType := uint8(chunkTypeCompressedData)
chunkBody := Encode(w.enc, uncompressed)
if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 {
chunkType, chunkBody = chunkTypeUncompressedData, uncompressed
}
chunkLen := 4 + len(chunkBody)
w.buf[0] = chunkType
w.buf[1] = uint8(chunkLen >> 0)
w.buf[2] = uint8(chunkLen >> 8)
w.buf[3] = uint8(chunkLen >> 16)
w.buf[4] = uint8(checksum >> 0)
w.buf[5] = uint8(checksum >> 8)
w.buf[6] = uint8(checksum >> 16)
w.buf[7] = uint8(checksum >> 24)
if _, err := w.w.Write(w.buf[:]); err != nil {
w.err = err
return n, err
}
if _, err := w.w.Write(chunkBody); err != nil {
w.err = err
return n, err
}
n += len(uncompressed)
}
return n, nil
}

View File

@@ -1,9 +0,0 @@
*.[68]
*.a
*.out
*.swp
_obj
_testmain.go
cmd/metrics-bench/metrics-bench
cmd/metrics-example/metrics-example
cmd/never-read/never-read

View File

@@ -1,13 +0,0 @@
language: go
go:
- 1.2
- 1.3
- 1.4
script:
- ./validate.sh
# this should give us faster builds according to
# http://docs.travis-ci.com/user/migrating-from-legacy/
sudo: false

View File

@@ -1,543 +0,0 @@
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
// All rights reservefs.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package storage
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/syndtr/goleveldb/leveldb/util"
)
var errFileOpen = errors.New("leveldb/storage: file still open")
type fileLock interface {
release() error
}
type fileStorageLock struct {
fs *fileStorage
}
func (lock *fileStorageLock) Release() {
fs := lock.fs
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.slock == lock {
fs.slock = nil
}
return
}
// fileStorage is a file-system backed storage.
type fileStorage struct {
path string
mu sync.Mutex
flock fileLock
slock *fileStorageLock
logw *os.File
buf []byte
// Opened file counter; if open < 0 means closed.
open int
day int
}
// OpenFile returns a new filesytem-backed storage implementation with the given
// path. This also hold a file lock, so any subsequent attempt to open the same
// path will fail.
//
// The storage must be closed after use, by calling Close method.
func OpenFile(path string) (Storage, error) {
if err := os.MkdirAll(path, 0755); err != nil {
return nil, err
}
flock, err := newFileLock(filepath.Join(path, "LOCK"))
if err != nil {
return nil, err
}
defer func() {
if err != nil {
flock.release()
}
}()
rename(filepath.Join(path, "LOG"), filepath.Join(path, "LOG.old"))
logw, err := os.OpenFile(filepath.Join(path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return nil, err
}
fs := &fileStorage{path: path, flock: flock, logw: logw}
runtime.SetFinalizer(fs, (*fileStorage).Close)
return fs, nil
}
func (fs *fileStorage) Lock() (util.Releaser, error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
return nil, ErrClosed
}
if fs.slock != nil {
return nil, ErrLocked
}
fs.slock = &fileStorageLock{fs: fs}
return fs.slock, nil
}
func itoa(buf []byte, i int, wid int) []byte {
var u uint = uint(i)
if u == 0 && wid <= 1 {
return append(buf, '0')
}
// Assemble decimal in reverse order.
var b [32]byte
bp := len(b)
for ; u > 0 || wid > 0; u /= 10 {
bp--
wid--
b[bp] = byte(u%10) + '0'
}
return append(buf, b[bp:]...)
}
func (fs *fileStorage) printDay(t time.Time) {
if fs.day == t.Day() {
return
}
fs.day = t.Day()
fs.logw.Write([]byte("=============== " + t.Format("Jan 2, 2006 (MST)") + " ===============\n"))
}
func (fs *fileStorage) doLog(t time.Time, str string) {
fs.printDay(t)
hour, min, sec := t.Clock()
msec := t.Nanosecond() / 1e3
// time
fs.buf = itoa(fs.buf[:0], hour, 2)
fs.buf = append(fs.buf, ':')
fs.buf = itoa(fs.buf, min, 2)
fs.buf = append(fs.buf, ':')
fs.buf = itoa(fs.buf, sec, 2)
fs.buf = append(fs.buf, '.')
fs.buf = itoa(fs.buf, msec, 6)
fs.buf = append(fs.buf, ' ')
// write
fs.buf = append(fs.buf, []byte(str)...)
fs.buf = append(fs.buf, '\n')
fs.logw.Write(fs.buf)
}
func (fs *fileStorage) Log(str string) {
t := time.Now()
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
return
}
fs.doLog(t, str)
}
func (fs *fileStorage) log(str string) {
fs.doLog(time.Now(), str)
}
func (fs *fileStorage) GetFile(num uint64, t FileType) File {
return &file{fs: fs, num: num, t: t}
}
func (fs *fileStorage) GetFiles(t FileType) (ff []File, err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
return nil, ErrClosed
}
dir, err := os.Open(fs.path)
if err != nil {
return
}
fnn, err := dir.Readdirnames(0)
// Close the dir first before checking for Readdirnames error.
if err := dir.Close(); err != nil {
fs.log(fmt.Sprintf("close dir: %v", err))
}
if err != nil {
return
}
f := &file{fs: fs}
for _, fn := range fnn {
if f.parse(fn) && (f.t&t) != 0 {
ff = append(ff, f)
f = &file{fs: fs}
}
}
return
}
func (fs *fileStorage) GetManifest() (f File, err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
return nil, ErrClosed
}
dir, err := os.Open(fs.path)
if err != nil {
return
}
fnn, err := dir.Readdirnames(0)
// Close the dir first before checking for Readdirnames error.
if err := dir.Close(); err != nil {
fs.log(fmt.Sprintf("close dir: %v", err))
}
if err != nil {
return
}
// Find latest CURRENT file.
var rem []string
var pend bool
var cerr error
for _, fn := range fnn {
if strings.HasPrefix(fn, "CURRENT") {
pend1 := len(fn) > 7
// Make sure it is valid name for a CURRENT file, otherwise skip it.
if pend1 {
if fn[7] != '.' || len(fn) < 9 {
fs.log(fmt.Sprintf("skipping %s: invalid file name", fn))
continue
}
if _, e1 := strconv.ParseUint(fn[8:], 10, 0); e1 != nil {
fs.log(fmt.Sprintf("skipping %s: invalid file num: %v", fn, e1))
continue
}
}
path := filepath.Join(fs.path, fn)
r, e1 := os.OpenFile(path, os.O_RDONLY, 0)
if e1 != nil {
return nil, e1
}
b, e1 := ioutil.ReadAll(r)
if e1 != nil {
r.Close()
return nil, e1
}
f1 := &file{fs: fs}
if len(b) < 1 || b[len(b)-1] != '\n' || !f1.parse(string(b[:len(b)-1])) {
fs.log(fmt.Sprintf("skipping %s: corrupted or incomplete", fn))
if pend1 {
rem = append(rem, fn)
}
if !pend1 || cerr == nil {
cerr = &ErrCorrupted{
File: fsParseName(filepath.Base(fn)),
Err: errors.New("leveldb/storage: corrupted or incomplete manifest file"),
}
}
} else if f != nil && f1.Num() < f.Num() {
fs.log(fmt.Sprintf("skipping %s: obsolete", fn))
if pend1 {
rem = append(rem, fn)
}
} else {
f = f1
pend = pend1
}
if err := r.Close(); err != nil {
fs.log(fmt.Sprintf("close %s: %v", fn, err))
}
}
}
// Don't remove any files if there is no valid CURRENT file.
if f == nil {
if cerr != nil {
err = cerr
} else {
err = os.ErrNotExist
}
return
}
// Rename pending CURRENT file to an effective CURRENT.
if pend {
path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), f.Num())
if err := rename(path, filepath.Join(fs.path, "CURRENT")); err != nil {
fs.log(fmt.Sprintf("CURRENT.%d -> CURRENT: %v", f.Num(), err))
}
}
// Remove obsolete or incomplete pending CURRENT files.
for _, fn := range rem {
path := filepath.Join(fs.path, fn)
if err := os.Remove(path); err != nil {
fs.log(fmt.Sprintf("remove %s: %v", fn, err))
}
}
return
}
func (fs *fileStorage) SetManifest(f File) (err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
return ErrClosed
}
f2, ok := f.(*file)
if !ok || f2.t != TypeManifest {
return ErrInvalidFile
}
defer func() {
if err != nil {
fs.log(fmt.Sprintf("CURRENT: %v", err))
}
}()
path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), f2.Num())
w, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
_, err = fmt.Fprintln(w, f2.name())
// Close the file first.
if err := w.Close(); err != nil {
fs.log(fmt.Sprintf("close CURRENT.%d: %v", f2.num, err))
}
if err != nil {
return err
}
return rename(path, filepath.Join(fs.path, "CURRENT"))
}
func (fs *fileStorage) Close() error {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
return ErrClosed
}
// Clear the finalizer.
runtime.SetFinalizer(fs, nil)
if fs.open > 0 {
fs.log(fmt.Sprintf("close: warning, %d files still open", fs.open))
}
fs.open = -1
e1 := fs.logw.Close()
err := fs.flock.release()
if err == nil {
err = e1
}
return err
}
type fileWrap struct {
*os.File
f *file
}
func (fw fileWrap) Sync() error {
if err := fw.File.Sync(); err != nil {
return err
}
if fw.f.Type() == TypeManifest {
// Also sync parent directory if file type is manifest.
// See: https://code.google.com/p/leveldb/issues/detail?id=190.
if err := syncDir(fw.f.fs.path); err != nil {
return err
}
}
return nil
}
func (fw fileWrap) Close() error {
f := fw.f
f.fs.mu.Lock()
defer f.fs.mu.Unlock()
if !f.open {
return ErrClosed
}
f.open = false
f.fs.open--
err := fw.File.Close()
if err != nil {
f.fs.log(fmt.Sprintf("close %s.%d: %v", f.Type(), f.Num(), err))
}
return err
}
type file struct {
fs *fileStorage
num uint64
t FileType
open bool
}
func (f *file) Open() (Reader, error) {
f.fs.mu.Lock()
defer f.fs.mu.Unlock()
if f.fs.open < 0 {
return nil, ErrClosed
}
if f.open {
return nil, errFileOpen
}
of, err := os.OpenFile(f.path(), os.O_RDONLY, 0)
if err != nil {
if f.hasOldName() && os.IsNotExist(err) {
of, err = os.OpenFile(f.oldPath(), os.O_RDONLY, 0)
if err == nil {
goto ok
}
}
return nil, err
}
ok:
f.open = true
f.fs.open++
return fileWrap{of, f}, nil
}
func (f *file) Create() (Writer, error) {
f.fs.mu.Lock()
defer f.fs.mu.Unlock()
if f.fs.open < 0 {
return nil, ErrClosed
}
if f.open {
return nil, errFileOpen
}
of, err := os.OpenFile(f.path(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return nil, err
}
f.open = true
f.fs.open++
return fileWrap{of, f}, nil
}
func (f *file) Replace(newfile File) error {
f.fs.mu.Lock()
defer f.fs.mu.Unlock()
if f.fs.open < 0 {
return ErrClosed
}
newfile2, ok := newfile.(*file)
if !ok {
return ErrInvalidFile
}
if f.open || newfile2.open {
return errFileOpen
}
return rename(newfile2.path(), f.path())
}
func (f *file) Type() FileType {
return f.t
}
func (f *file) Num() uint64 {
return f.num
}
func (f *file) Remove() error {
f.fs.mu.Lock()
defer f.fs.mu.Unlock()
if f.fs.open < 0 {
return ErrClosed
}
if f.open {
return errFileOpen
}
err := os.Remove(f.path())
if err != nil {
f.fs.log(fmt.Sprintf("remove %s.%d: %v", f.Type(), f.Num(), err))
}
// Also try remove file with old name, just in case.
if f.hasOldName() {
if e1 := os.Remove(f.oldPath()); !os.IsNotExist(e1) {
f.fs.log(fmt.Sprintf("remove %s.%d: %v (old name)", f.Type(), f.Num(), err))
err = e1
}
}
return err
}
func (f *file) hasOldName() bool {
return f.t == TypeTable
}
func (f *file) oldName() string {
switch f.t {
case TypeTable:
return fmt.Sprintf("%06d.sst", f.num)
}
return f.name()
}
func (f *file) oldPath() string {
return filepath.Join(f.fs.path, f.oldName())
}
func (f *file) name() string {
switch f.t {
case TypeManifest:
return fmt.Sprintf("MANIFEST-%06d", f.num)
case TypeJournal:
return fmt.Sprintf("%06d.log", f.num)
case TypeTable:
return fmt.Sprintf("%06d.ldb", f.num)
case TypeTemp:
return fmt.Sprintf("%06d.tmp", f.num)
default:
panic("invalid file type")
}
}
func (f *file) path() string {
return filepath.Join(f.fs.path, f.name())
}
func fsParseName(name string) *FileInfo {
fi := &FileInfo{}
var tail string
_, err := fmt.Sscanf(name, "%d.%s", &fi.Num, &tail)
if err == nil {
switch tail {
case "log":
fi.Type = TypeJournal
case "ldb", "sst":
fi.Type = TypeTable
case "tmp":
fi.Type = TypeTemp
default:
return nil
}
return fi
}
n, _ := fmt.Sscanf(name, "MANIFEST-%d%s", &fi.Num, &tail)
if n == 1 {
fi.Type = TypeManifest
return fi
}
return nil
}
func (f *file) parse(name string) bool {
fi := fsParseName(name)
if fi == nil {
return false
}
f.t = fi.Type
f.num = fi.Num
return true
}

View File

@@ -1,203 +0,0 @@
// Copyright (c) 2013, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package storage
import (
"bytes"
"os"
"sync"
"github.com/syndtr/goleveldb/leveldb/util"
)
const typeShift = 3
type memStorageLock struct {
ms *memStorage
}
func (lock *memStorageLock) Release() {
ms := lock.ms
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.slock == lock {
ms.slock = nil
}
return
}
// memStorage is a memory-backed storage.
type memStorage struct {
mu sync.Mutex
slock *memStorageLock
files map[uint64]*memFile
manifest *memFilePtr
}
// NewMemStorage returns a new memory-backed storage implementation.
func NewMemStorage() Storage {
return &memStorage{
files: make(map[uint64]*memFile),
}
}
func (ms *memStorage) Lock() (util.Releaser, error) {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.slock != nil {
return nil, ErrLocked
}
ms.slock = &memStorageLock{ms: ms}
return ms.slock, nil
}
func (*memStorage) Log(str string) {}
func (ms *memStorage) GetFile(num uint64, t FileType) File {
return &memFilePtr{ms: ms, num: num, t: t}
}
func (ms *memStorage) GetFiles(t FileType) ([]File, error) {
ms.mu.Lock()
var ff []File
for x, _ := range ms.files {
num, mt := x>>typeShift, FileType(x)&TypeAll
if mt&t == 0 {
continue
}
ff = append(ff, &memFilePtr{ms: ms, num: num, t: mt})
}
ms.mu.Unlock()
return ff, nil
}
func (ms *memStorage) GetManifest() (File, error) {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.manifest == nil {
return nil, os.ErrNotExist
}
return ms.manifest, nil
}
func (ms *memStorage) SetManifest(f File) error {
fm, ok := f.(*memFilePtr)
if !ok || fm.t != TypeManifest {
return ErrInvalidFile
}
ms.mu.Lock()
ms.manifest = fm
ms.mu.Unlock()
return nil
}
func (*memStorage) Close() error { return nil }
type memReader struct {
*bytes.Reader
m *memFile
}
func (mr *memReader) Close() error {
return mr.m.Close()
}
type memFile struct {
bytes.Buffer
ms *memStorage
open bool
}
func (*memFile) Sync() error { return nil }
func (m *memFile) Close() error {
m.ms.mu.Lock()
m.open = false
m.ms.mu.Unlock()
return nil
}
type memFilePtr struct {
ms *memStorage
num uint64
t FileType
}
func (p *memFilePtr) x() uint64 {
return p.Num()<<typeShift | uint64(p.Type())
}
func (p *memFilePtr) Open() (Reader, error) {
ms := p.ms
ms.mu.Lock()
defer ms.mu.Unlock()
if m, exist := ms.files[p.x()]; exist {
if m.open {
return nil, errFileOpen
}
m.open = true
return &memReader{Reader: bytes.NewReader(m.Bytes()), m: m}, nil
}
return nil, os.ErrNotExist
}
func (p *memFilePtr) Create() (Writer, error) {
ms := p.ms
ms.mu.Lock()
defer ms.mu.Unlock()
m, exist := ms.files[p.x()]
if exist {
if m.open {
return nil, errFileOpen
}
m.Reset()
} else {
m = &memFile{ms: ms}
ms.files[p.x()] = m
}
m.open = true
return m, nil
}
func (p *memFilePtr) Replace(newfile File) error {
p1, ok := newfile.(*memFilePtr)
if !ok {
return ErrInvalidFile
}
ms := p.ms
ms.mu.Lock()
defer ms.mu.Unlock()
m1, exist := ms.files[p1.x()]
if !exist {
return os.ErrNotExist
}
m0, exist := ms.files[p.x()]
if (exist && m0.open) || m1.open {
return errFileOpen
}
delete(ms.files, p1.x())
ms.files[p.x()] = m1
return nil
}
func (p *memFilePtr) Type() FileType {
return p.t
}
func (p *memFilePtr) Num() uint64 {
return p.num
}
func (p *memFilePtr) Remove() error {
ms := p.ms
ms.mu.Lock()
defer ms.mu.Unlock()
if _, exist := ms.files[p.x()]; exist {
delete(ms.files, p.x())
return nil
}
return os.ErrNotExist
}

View File

@@ -1,187 +0,0 @@
// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package testutil
import (
"fmt"
"math/rand"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/util"
)
func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, teardown func(DB)) {
if rnd == nil {
rnd = NewRand()
}
if p == nil {
BeforeEach(func() {
p = setup(kv)
})
if teardown != nil {
AfterEach(func() {
teardown(p)
})
}
}
It("Should find all keys with Find", func() {
if db, ok := p.(Find); ok {
ShuffledIndex(nil, kv.Len(), 1, func(i int) {
key_, key, value := kv.IndexInexact(i)
// Using exact key.
rkey, rvalue, err := db.TestFind(key)
Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
Expect(rkey).Should(Equal(key), "Key")
Expect(rvalue).Should(Equal(value), "Value for key %q", key)
// Using inexact key.
rkey, rvalue, err = db.TestFind(key_)
Expect(err).ShouldNot(HaveOccurred(), "Error for key %q (%q)", key_, key)
Expect(rkey).Should(Equal(key))
Expect(rvalue).Should(Equal(value), "Value for key %q (%q)", key_, key)
})
}
})
It("Should return error if the key is not present", func() {
if db, ok := p.(Find); ok {
var key []byte
if kv.Len() > 0 {
key_, _ := kv.Index(kv.Len() - 1)
key = BytesAfter(key_)
}
rkey, _, err := db.TestFind(key)
Expect(err).Should(HaveOccurred(), "Find for key %q yield key %q", key, rkey)
Expect(err).Should(Equal(errors.ErrNotFound))
}
})
It("Should only find exact key with Get", func() {
if db, ok := p.(Get); ok {
ShuffledIndex(nil, kv.Len(), 1, func(i int) {
key_, key, value := kv.IndexInexact(i)
// Using exact key.
rvalue, err := db.TestGet(key)
Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
Expect(rvalue).Should(Equal(value), "Value for key %q", key)
// Using inexact key.
if len(key_) > 0 {
_, err = db.TestGet(key_)
Expect(err).Should(HaveOccurred(), "Error for key %q", key_)
Expect(err).Should(Equal(errors.ErrNotFound))
}
})
}
})
It("Should only find present key with Has", func() {
if db, ok := p.(Has); ok {
ShuffledIndex(nil, kv.Len(), 1, func(i int) {
key_, key, _ := kv.IndexInexact(i)
// Using exact key.
ret, err := db.TestHas(key)
Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
Expect(ret).Should(BeTrue(), "False for key %q", key)
// Using inexact key.
if len(key_) > 0 {
ret, err = db.TestHas(key_)
Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key_)
Expect(ret).ShouldNot(BeTrue(), "True for key %q", key)
}
})
}
})
TestIter := func(r *util.Range, _kv KeyValue) {
if db, ok := p.(NewIterator); ok {
iter := db.TestNewIterator(r)
Expect(iter.Error()).ShouldNot(HaveOccurred())
t := IteratorTesting{
KeyValue: _kv,
Iter: iter,
}
DoIteratorTesting(&t)
iter.Release()
}
}
It("Should iterates and seeks correctly", func(done Done) {
TestIter(nil, kv.Clone())
done <- true
}, 3.0)
RandomIndex(rnd, kv.Len(), Min(kv.Len(), 50), func(i int) {
type slice struct {
r *util.Range
start, limit int
}
key_, _, _ := kv.IndexInexact(i)
for _, x := range []slice{
{&util.Range{Start: key_, Limit: nil}, i, kv.Len()},
{&util.Range{Start: nil, Limit: key_}, 0, i},
} {
It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", x.start, x.limit), func(done Done) {
TestIter(x.r, kv.Slice(x.start, x.limit))
done <- true
}, 3.0)
}
})
RandomRange(rnd, kv.Len(), Min(kv.Len(), 50), func(start, limit int) {
It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", start, limit), func(done Done) {
r := kv.Range(start, limit)
TestIter(&r, kv.Slice(start, limit))
done <- true
}, 3.0)
})
}
func AllKeyValueTesting(rnd *rand.Rand, body, setup func(KeyValue) DB, teardown func(DB)) {
Test := func(kv *KeyValue) func() {
return func() {
var p DB
if setup != nil {
Defer("setup", func() {
p = setup(*kv)
})
}
if teardown != nil {
Defer("teardown", func() {
teardown(p)
})
}
if body != nil {
p = body(*kv)
}
KeyValueTesting(rnd, *kv, p, func(KeyValue) DB {
return p
}, nil)
}
}
Describe("with no key/value (empty)", Test(&KeyValue{}))
Describe("with empty key", Test(KeyValue_EmptyKey()))
Describe("with empty value", Test(KeyValue_EmptyValue()))
Describe("with one key/value", Test(KeyValue_OneKeyValue()))
Describe("with big value", Test(KeyValue_BigValue()))
Describe("with special key", Test(KeyValue_SpecialKey()))
Describe("with multiple key/value", Test(KeyValue_MultipleKeyValue()))
Describe("with generated key/value", Test(KeyValue_Generate(nil, 120, 1, 50, 10, 120)))
}

View File

@@ -1,586 +0,0 @@
// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package testutil
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
. "github.com/onsi/gomega"
"github.com/syndtr/goleveldb/leveldb/storage"
"github.com/syndtr/goleveldb/leveldb/util"
)
var (
storageMu sync.Mutex
storageUseFS bool = true
storageKeepFS bool = false
storageNum int
)
type StorageMode int
const (
ModeOpen StorageMode = 1 << iota
ModeCreate
ModeRemove
ModeRead
ModeWrite
ModeSync
ModeClose
)
const (
modeOpen = iota
modeCreate
modeRemove
modeRead
modeWrite
modeSync
modeClose
modeCount
)
const (
typeManifest = iota
typeJournal
typeTable
typeTemp
typeCount
)
const flattenCount = modeCount * typeCount
func flattenType(m StorageMode, t storage.FileType) int {
var x int
switch m {
case ModeOpen:
x = modeOpen
case ModeCreate:
x = modeCreate
case ModeRemove:
x = modeRemove
case ModeRead:
x = modeRead
case ModeWrite:
x = modeWrite
case ModeSync:
x = modeSync
case ModeClose:
x = modeClose
default:
panic("invalid storage mode")
}
x *= typeCount
switch t {
case storage.TypeManifest:
return x + typeManifest
case storage.TypeJournal:
return x + typeJournal
case storage.TypeTable:
return x + typeTable
case storage.TypeTemp:
return x + typeTemp
default:
panic("invalid file type")
}
}
func listFlattenType(m StorageMode, t storage.FileType) []int {
ret := make([]int, 0, flattenCount)
add := func(x int) {
x *= typeCount
switch {
case t&storage.TypeManifest != 0:
ret = append(ret, x+typeManifest)
case t&storage.TypeJournal != 0:
ret = append(ret, x+typeJournal)
case t&storage.TypeTable != 0:
ret = append(ret, x+typeTable)
case t&storage.TypeTemp != 0:
ret = append(ret, x+typeTemp)
}
}
switch {
case m&ModeOpen != 0:
add(modeOpen)
case m&ModeCreate != 0:
add(modeCreate)
case m&ModeRemove != 0:
add(modeRemove)
case m&ModeRead != 0:
add(modeRead)
case m&ModeWrite != 0:
add(modeWrite)
case m&ModeSync != 0:
add(modeSync)
case m&ModeClose != 0:
add(modeClose)
}
return ret
}
func packFile(num uint64, t storage.FileType) uint64 {
if num>>(64-typeCount) != 0 {
panic("overflow")
}
return num<<typeCount | uint64(t)
}
func unpackFile(x uint64) (uint64, storage.FileType) {
return x >> typeCount, storage.FileType(x) & storage.TypeAll
}
type emulatedError struct {
err error
}
func (err emulatedError) Error() string {
return fmt.Sprintf("emulated storage error: %v", err.err)
}
type storageLock struct {
s *Storage
r util.Releaser
}
func (l storageLock) Release() {
l.r.Release()
l.s.logI("storage lock released")
}
type reader struct {
f *file
storage.Reader
}
func (r *reader) Read(p []byte) (n int, err error) {
err = r.f.s.emulateError(ModeRead, r.f.Type())
if err == nil {
r.f.s.stall(ModeRead, r.f.Type())
n, err = r.Reader.Read(p)
}
r.f.s.count(ModeRead, r.f.Type(), n)
if err != nil && err != io.EOF {
r.f.s.logI("read error, num=%d type=%v n=%d err=%v", r.f.Num(), r.f.Type(), n, err)
}
return
}
func (r *reader) ReadAt(p []byte, off int64) (n int, err error) {
err = r.f.s.emulateError(ModeRead, r.f.Type())
if err == nil {
r.f.s.stall(ModeRead, r.f.Type())
n, err = r.Reader.ReadAt(p, off)
}
r.f.s.count(ModeRead, r.f.Type(), n)
if err != nil && err != io.EOF {
r.f.s.logI("readAt error, num=%d type=%v offset=%d n=%d err=%v", r.f.Num(), r.f.Type(), off, n, err)
}
return
}
func (r *reader) Close() (err error) {
return r.f.doClose(r.Reader)
}
type writer struct {
f *file
storage.Writer
}
func (w *writer) Write(p []byte) (n int, err error) {
err = w.f.s.emulateError(ModeWrite, w.f.Type())
if err == nil {
w.f.s.stall(ModeWrite, w.f.Type())
n, err = w.Writer.Write(p)
}
w.f.s.count(ModeWrite, w.f.Type(), n)
if err != nil && err != io.EOF {
w.f.s.logI("write error, num=%d type=%v n=%d err=%v", w.f.Num(), w.f.Type(), n, err)
}
return
}
func (w *writer) Sync() (err error) {
err = w.f.s.emulateError(ModeSync, w.f.Type())
if err == nil {
w.f.s.stall(ModeSync, w.f.Type())
err = w.Writer.Sync()
}
w.f.s.count(ModeSync, w.f.Type(), 0)
if err != nil {
w.f.s.logI("sync error, num=%d type=%v err=%v", w.f.Num(), w.f.Type(), err)
}
return
}
func (w *writer) Close() (err error) {
return w.f.doClose(w.Writer)
}
type file struct {
s *Storage
storage.File
}
func (f *file) pack() uint64 {
return packFile(f.Num(), f.Type())
}
func (f *file) assertOpen() {
ExpectWithOffset(2, f.s.opens).NotTo(HaveKey(f.pack()), "File open, num=%d type=%v writer=%v", f.Num(), f.Type(), f.s.opens[f.pack()])
}
func (f *file) doClose(closer io.Closer) (err error) {
err = f.s.emulateError(ModeClose, f.Type())
if err == nil {
f.s.stall(ModeClose, f.Type())
}
f.s.mu.Lock()
defer f.s.mu.Unlock()
if err == nil {
ExpectWithOffset(2, f.s.opens).To(HaveKey(f.pack()), "File closed, num=%d type=%v", f.Num(), f.Type())
err = closer.Close()
}
f.s.countNB(ModeClose, f.Type(), 0)
writer := f.s.opens[f.pack()]
if err != nil {
f.s.logISkip(1, "file close failed, num=%d type=%v writer=%v err=%v", f.Num(), f.Type(), writer, err)
} else {
f.s.logISkip(1, "file closed, num=%d type=%v writer=%v", f.Num(), f.Type(), writer)
delete(f.s.opens, f.pack())
}
return
}
func (f *file) Open() (r storage.Reader, err error) {
err = f.s.emulateError(ModeOpen, f.Type())
if err == nil {
f.s.stall(ModeOpen, f.Type())
}
f.s.mu.Lock()
defer f.s.mu.Unlock()
if err == nil {
f.assertOpen()
f.s.countNB(ModeOpen, f.Type(), 0)
r, err = f.File.Open()
}
if err != nil {
f.s.logI("file open failed, num=%d type=%v err=%v", f.Num(), f.Type(), err)
} else {
f.s.logI("file opened, num=%d type=%v", f.Num(), f.Type())
f.s.opens[f.pack()] = false
r = &reader{f, r}
}
return
}
func (f *file) Create() (w storage.Writer, err error) {
err = f.s.emulateError(ModeCreate, f.Type())
if err == nil {
f.s.stall(ModeCreate, f.Type())
}
f.s.mu.Lock()
defer f.s.mu.Unlock()
if err == nil {
f.assertOpen()
f.s.countNB(ModeCreate, f.Type(), 0)
w, err = f.File.Create()
}
if err != nil {
f.s.logI("file create failed, num=%d type=%v err=%v", f.Num(), f.Type(), err)
} else {
f.s.logI("file created, num=%d type=%v", f.Num(), f.Type())
f.s.opens[f.pack()] = true
w = &writer{f, w}
}
return
}
func (f *file) Remove() (err error) {
err = f.s.emulateError(ModeRemove, f.Type())
if err == nil {
f.s.stall(ModeRemove, f.Type())
}
f.s.mu.Lock()
defer f.s.mu.Unlock()
if err == nil {
f.assertOpen()
f.s.countNB(ModeRemove, f.Type(), 0)
err = f.File.Remove()
}
if err != nil {
f.s.logI("file remove failed, num=%d type=%v err=%v", f.Num(), f.Type(), err)
} else {
f.s.logI("file removed, num=%d type=%v", f.Num(), f.Type())
}
return
}
type Storage struct {
storage.Storage
closeFn func() error
lmu sync.Mutex
lb bytes.Buffer
mu sync.Mutex
// Open files, true=writer, false=reader
opens map[uint64]bool
counters [flattenCount]int
bytesCounter [flattenCount]int64
emulatedError [flattenCount]error
stallCond sync.Cond
stalled [flattenCount]bool
}
func (s *Storage) log(skip int, str string) {
s.lmu.Lock()
defer s.lmu.Unlock()
_, file, line, ok := runtime.Caller(skip + 2)
if ok {
// Truncate file name at last file name separator.
if index := strings.LastIndex(file, "/"); index >= 0 {
file = file[index+1:]
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
file = file[index+1:]
}
} else {
file = "???"
line = 1
}
fmt.Fprintf(&s.lb, "%s:%d: ", file, line)
lines := strings.Split(str, "\n")
if l := len(lines); l > 1 && lines[l-1] == "" {
lines = lines[:l-1]
}
for i, line := range lines {
if i > 0 {
s.lb.WriteString("\n\t")
}
s.lb.WriteString(line)
}
s.lb.WriteByte('\n')
}
func (s *Storage) logISkip(skip int, format string, args ...interface{}) {
pc, _, _, ok := runtime.Caller(skip + 1)
if ok {
if f := runtime.FuncForPC(pc); f != nil {
fname := f.Name()
if index := strings.LastIndex(fname, "."); index >= 0 {
fname = fname[index+1:]
}
format = fname + ": " + format
}
}
s.log(skip+1, fmt.Sprintf(format, args...))
}
func (s *Storage) logI(format string, args ...interface{}) {
s.logISkip(1, format, args...)
}
func (s *Storage) Log(str string) {
s.log(1, "Log: "+str)
s.Storage.Log(str)
}
func (s *Storage) Lock() (r util.Releaser, err error) {
r, err = s.Storage.Lock()
if err != nil {
s.logI("storage locking failed, err=%v", err)
} else {
s.logI("storage locked")
r = storageLock{s, r}
}
return
}
func (s *Storage) GetFile(num uint64, t storage.FileType) storage.File {
return &file{s, s.Storage.GetFile(num, t)}
}
func (s *Storage) GetFiles(t storage.FileType) (files []storage.File, err error) {
rfiles, err := s.Storage.GetFiles(t)
if err != nil {
s.logI("get files failed, err=%v", err)
return
}
files = make([]storage.File, len(rfiles))
for i, f := range rfiles {
files[i] = &file{s, f}
}
s.logI("get files, type=0x%x count=%d", int(t), len(files))
return
}
func (s *Storage) GetManifest() (f storage.File, err error) {
manifest, err := s.Storage.GetManifest()
if err != nil {
if !os.IsNotExist(err) {
s.logI("get manifest failed, err=%v", err)
}
return
}
s.logI("get manifest, num=%d", manifest.Num())
return &file{s, manifest}, nil
}
func (s *Storage) SetManifest(f storage.File) error {
f_, ok := f.(*file)
ExpectWithOffset(1, ok).To(BeTrue())
ExpectWithOffset(1, f_.Type()).To(Equal(storage.TypeManifest))
err := s.Storage.SetManifest(f_.File)
if err != nil {
s.logI("set manifest failed, err=%v", err)
} else {
s.logI("set manifest, num=%d", f_.Num())
}
return err
}
func (s *Storage) openFiles() string {
out := "Open files:"
for x, writer := range s.opens {
num, t := unpackFile(x)
out += fmt.Sprintf("\n · num=%d type=%v writer=%v", num, t, writer)
}
return out
}
func (s *Storage) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
ExpectWithOffset(1, s.opens).To(BeEmpty(), s.openFiles())
err := s.Storage.Close()
if err != nil {
s.logI("storage closing failed, err=%v", err)
} else {
s.logI("storage closed")
}
if s.closeFn != nil {
if err1 := s.closeFn(); err1 != nil {
s.logI("close func error, err=%v", err1)
}
}
return err
}
func (s *Storage) countNB(m StorageMode, t storage.FileType, n int) {
s.counters[flattenType(m, t)]++
s.bytesCounter[flattenType(m, t)] += int64(n)
}
func (s *Storage) count(m StorageMode, t storage.FileType, n int) {
s.mu.Lock()
defer s.mu.Unlock()
s.countNB(m, t, n)
}
func (s *Storage) ResetCounter(m StorageMode, t storage.FileType) {
for _, x := range listFlattenType(m, t) {
s.counters[x] = 0
s.bytesCounter[x] = 0
}
}
func (s *Storage) Counter(m StorageMode, t storage.FileType) (count int, bytes int64) {
for _, x := range listFlattenType(m, t) {
count += s.counters[x]
bytes += s.bytesCounter[x]
}
return
}
func (s *Storage) emulateError(m StorageMode, t storage.FileType) error {
s.mu.Lock()
defer s.mu.Unlock()
err := s.emulatedError[flattenType(m, t)]
if err != nil {
return emulatedError{err}
}
return nil
}
func (s *Storage) EmulateError(m StorageMode, t storage.FileType, err error) {
s.mu.Lock()
defer s.mu.Unlock()
for _, x := range listFlattenType(m, t) {
s.emulatedError[x] = err
}
}
func (s *Storage) stall(m StorageMode, t storage.FileType) {
x := flattenType(m, t)
s.mu.Lock()
defer s.mu.Unlock()
for s.stalled[x] {
s.stallCond.Wait()
}
}
func (s *Storage) Stall(m StorageMode, t storage.FileType) {
s.mu.Lock()
defer s.mu.Unlock()
for _, x := range listFlattenType(m, t) {
s.stalled[x] = true
}
}
func (s *Storage) Release(m StorageMode, t storage.FileType) {
s.mu.Lock()
defer s.mu.Unlock()
for _, x := range listFlattenType(m, t) {
s.stalled[x] = false
}
s.stallCond.Broadcast()
}
func NewStorage() *Storage {
var stor storage.Storage
var closeFn func() error
if storageUseFS {
for {
storageMu.Lock()
num := storageNum
storageNum++
storageMu.Unlock()
path := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldb-test%d0%d0%d", os.Getuid(), os.Getpid(), num))
if _, err := os.Stat(path); os.IsNotExist(err) {
stor, err = storage.OpenFile(path)
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "creating storage at %s", path)
closeFn = func() error {
if storageKeepFS {
return nil
}
return os.RemoveAll(path)
}
break
}
}
} else {
stor = storage.NewMemStorage()
}
s := &Storage{
Storage: stor,
closeFn: closeFn,
opens: make(map[uint64]bool),
}
s.stallCond.L = &s.mu
return s
}

View File

@@ -1,7 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- tip

24
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,24 @@
If your issue is a support request ("How do I get my devices to connect?"
or similar), please use the support forum at https://forum.syncthing.net/
where a large number of helpful people hang out. This issue tracker is for
reporting bugs or feature requests directly to the developers.
If your issue is a bug report, replace this boilerplate with a description
of the problem, being sure to include at least:
- what happened,
- what you expected to happen instead, and
- any steps to reproduce the problem.
Also fill out the version information below and add log output or
screenshots as appropriate.
If your issue is a feature request, simply replace this template text in
its entirety.
### Version Information
Syncthing Version: v0.x.y
OS Version: Windows 7 / Ubuntu 14.04 / ...
Browser Version: (if applicable, for GUI issues)

3
NICKS
View File

@@ -22,6 +22,7 @@ cdata <chris@scriptolo.gy>
cdhowie <me@chrishowie.com>
ceh <emil@hessman.se>
cqcallaw <enlightened.despot@gmail.com>
dinosore <dinosore@dbrsoftware.co.uk>
dva <denisva@gmail.com>
dzarda <dzardacz@gmail.com>
eipiminus1 <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
@@ -45,6 +46,7 @@ kralo <max.schulze@online.de>
krozycki <rozycki.karol@gmail.com>
letiemble <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
LordLandon <lordlandon@gmail.com>
lkwg82 <lkwg82@gmx.de>
marcindziadus <dziadus.marcin@gmail.com>
marclaporte <marc@marclaporte.com>
mateon1 <matin1111@wp.pl>
@@ -52,6 +54,7 @@ mogwa1 <devriesb@gmail.com>
moshen <moshen.colin@gmail.com>
Moter8 <moter8@gmail.com>
mvdan <mvdan@mvdan.cc>
norgeous <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
nrm21 <natemorrison@gmail.com>
Nutomic <me@nutomic.com>
pascalj <github@pascalj.com> <mail@pascal-jungblut.com>

32
PULL_REQUEST_TEMPLATE.md Normal file
View File

@@ -0,0 +1,32 @@
### Purpose
Describe the purpose of this change. If there is an existing issue that is
resolved by this pull request, ensure that the commit subject is on the form
`Some short description (fixes #1234)` where 1234 is the issue number.
### Testing
Describe what testing has been done, and how the reviewer can test the change
if new tests are not included.
### Screenshots
If this is a GUI change, include screenshots of the change. If not, please
feel free to just delete this section.
### Documentation
If this is a user visible change (including API and protocol changes), add a link here
to the corresponding pull request on https://github.com/syncthing/docs or describe
the documentation changes necessary.
### Authorship
Every author of a code contribution (Go, Javascript, HTML, CSS etc, with the
possible exception of minor typo corrections and similar) is recorded in the
AUTHORS and NICKS files and the in-GUI credits. If this is your first
contribution, a maintainer will add you properly before accepting the
contribution. You need not do so yourself or worry about the fact that the
"authors" automated test fails. However, if your name (such as you want it
presented in the credits) is not visible on your Github profile or in your
commit messages, please assist by providing it here.

View File

@@ -1,7 +1,6 @@
# Syncthing
[![Latest Build (Official)](https://img.shields.io/jenkins/s/http/build.syncthing.net/syncthing.svg?style=flat-square&label=unix%20build)](http://build.syncthing.net/job/syncthing/lastBuild/)
[![AppVeyor Build](https://img.shields.io/appveyor/ci/calmh/syncthing/master.svg?style=flat-square&label=windows%20build)](https://ci.appveyor.com/project/calmh/syncthing)
[![API Documentation](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](http://godoc.org/github.com/syncthing/syncthing)
[![MPLv2 License](https://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/)
@@ -25,26 +24,30 @@ for incompatible changes.
Take a look at the [getting started guide][2].
There are a few examples for keeping Syncthing running in the background
on your system in [the etc directory][3].
on your system in [the etc directory][3]. There are also several [GUI
implementations][11] for Windows, Mac and Linux.
## Getting in Touch
The first and best point of contact is the [Forum][8]. There is also an IRC
channel, `#syncthing` on [Freenode][4] (with a [web client][9]), for talking
channel, `#syncthing` on [freenode][4] (with a [web client][9]), for talking
directly to developers and users. If you've found something that is clearly a
bug, feel free to report it in the [GitHub issue tracker][10].
## Building
Building Syncthing from source is easy, and there's a [guide][5].
Building Syncthing from source is easy, and there's a [guide][5]
that describes it for both Unix and Windows systems.
## Signed Releases
As of v0.10.15 and onwards, git tags and release binaries are GPG signed
with the key D26E6ED000654A3E (see https://syncthing.net/security.html).
For release binaries, MD5 and SHA1 checksums are calculated and signed,
available in the md5sum.txt.asc and sha1sum.txt.asc files.
As of v0.10.15 and onwards release binaries are GPG signed with the key
D26E6ED000654A3E, available from https://syncthing.net/security.html and
most key servers.
There is also a built in automatic upgrade mechanism (disabled in some
distribution channels) which uses a compiled in ECDSA signature. Mac OS
X binaries are also properly code signed.
## Documentation
@@ -62,3 +65,4 @@ All code is licensed under the [MPLv2 License][7].
[8]: https://forum.syncthing.net/
[9]: https://kiwiirc.com/client/irc.freenode.net/#syncthing
[10]: https://github.com/syncthing/syncthing/issues
[11]: http://docs.syncthing.net/users/contrib.html#gui-wrappers

View File

@@ -1,12 +0,0 @@
version: '{branch}-{build}'
clone_folder: C:\src\github.com\syncthing\syncthing
init:
- go version
environment:
GOPATH: C:\
build_script:
- go run build.go zip
test_script:
- go run build.go test
artifacts:
- path: '*.zip'

545
build.go
View File

@@ -26,6 +26,7 @@ import (
"runtime"
"strconv"
"strings"
"syscall"
"text/template"
"time"
)
@@ -40,12 +41,84 @@ var (
race bool
)
type target struct {
name string
buildPkg string
binaryName string
archiveFiles []archiveFile
debianFiles []archiveFile
}
type archiveFile struct {
src string
dst string
perm os.FileMode
}
var targets = map[string]target{
"all": {
// Only valid for the "build" and "install" commands as it lacks all
// the archive creation stuff.
buildPkg: "./cmd/...",
},
"syncthing": {
// The default target for "build", "install", "tar", "zip", "deb", etc.
name: "syncthing",
buildPkg: "./cmd/syncthing",
binaryName: "syncthing", // .exe will be added automatically for Windows builds
archiveFiles: []archiveFile{
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
{src: "README.md", dst: "README.txt", perm: 0644},
{src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
{src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
// All files from etc/ and extra/ added automatically in init().
},
debianFiles: []archiveFile{
{src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
{src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0644},
{src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0644},
{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0644},
{src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0644},
{src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0644},
{src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0644},
{src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0644},
{src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0644},
{src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0644},
{src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0644},
{src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0644},
{src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0644},
{src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0644},
{src: "etc/linux-systemd/system/syncthing@.service", dst: "deb/lib/systemd/system/syncthing@.service", perm: 0644},
{src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
{src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
},
},
}
func init() {
// The "syncthing" target includes a few more files found in the "etc"
// and "extra" dirs.
syncthingPkg := targets["syncthing"]
for _, file := range listFiles("etc") {
syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
}
for _, file := range listFiles("extra") {
syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
}
for _, file := range listFiles("extra") {
syncthingPkg.debianFiles = append(syncthingPkg.debianFiles, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0644})
}
targets["syncthing"] = syncthingPkg
}
const minGoVersion = 1.3
func main() {
log.SetOutput(os.Stdout)
log.SetFlags(0)
// If GOPATH isn't set, set it correctly with the assumption that we are
// in $GOPATH/src/github.com/syncthing/syncthing.
if os.Getenv("GOPATH") == "" {
cwd, err := os.Getwd()
if err != nil {
@@ -55,6 +128,12 @@ func main() {
log.Println("GOPATH is", gopath)
os.Setenv("GOPATH", gopath)
}
// We use Go 1.5+ vendoring.
os.Setenv("GO15VENDOREXPERIMENT", "1")
// Set path to $GOPATH/bin:$PATH so that we can for sure find tools we
// might have installed during "build.go setup".
os.Setenv("PATH", fmt.Sprintf("%s%cbin%c%s", os.Getenv("GOPATH"), os.PathSeparator, os.PathListSeparator, os.Getenv("PATH")))
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
@@ -73,92 +152,99 @@ func main() {
goVersion, _ = checkRequiredGoVersion()
// Invoking build.go with no parameters at all is equivalent to "go run
// build.go install all" as that builds everything (incrementally),
// which is what you want for maximum error checking during development.
if flag.NArg() == 0 {
var tags []string
if noupgrade {
tags = []string{"noupgrade"}
}
install("./cmd/...", tags)
install(targets["all"], tags)
vet("./cmd/syncthing")
vet("./lib/...")
lint("./cmd/syncthing")
vet("cmd", "lib")
lint("./cmd/...")
lint("./lib/...")
return
}
for _, cmd := range flag.Args() {
switch cmd {
case "setup":
setup()
// Otherwise, with any command given but not a target, the target is
// "syncthing". So "go run build.go install" is "go run build.go install
// syncthing" etc.
targetName := "syncthing"
if flag.NArg() > 1 {
targetName = flag.Arg(1)
}
target, ok := targets[targetName]
if !ok {
log.Fatalln("Unknown target", target)
}
case "install":
pkg := "./cmd/..."
var tags []string
if noupgrade {
tags = []string{"noupgrade"}
}
install(pkg, tags)
cmd := flag.Arg(0)
switch cmd {
case "setup":
setup()
case "build":
pkg := "./cmd/syncthing"
var tags []string
if noupgrade {
tags = []string{"noupgrade"}
}
build(pkg, tags)
case "test":
test("./...")
case "bench":
bench("./...")
case "assets":
assets()
case "xdr":
xdr()
case "translate":
translate()
case "transifex":
transifex()
case "deps":
deps()
case "tar":
buildTar()
case "zip":
buildZip()
case "deb":
buildDeb()
case "clean":
clean()
case "vet":
vet("./cmd/syncthing")
vet("./lib/...")
case "lint":
lint("./cmd/syncthing")
lint("./lib/...")
default:
log.Fatalf("Unknown command %q", cmd)
case "install":
var tags []string
if noupgrade {
tags = []string{"noupgrade"}
}
install(target, tags)
case "build":
var tags []string
if noupgrade {
tags = []string{"noupgrade"}
}
build(target, tags)
case "test":
test("./lib/...", "./cmd/...")
case "bench":
bench("./lib/...", "./cmd/...")
case "assets":
rebuildAssets()
case "xdr":
xdr()
case "translate":
translate()
case "transifex":
transifex()
case "tar":
buildTar(target)
case "zip":
buildZip(target)
case "deb":
buildDeb(target)
case "clean":
clean()
case "vet":
vet("cmd", "lib")
case "lint":
lint("./cmd/...")
lint("./lib/...")
default:
log.Fatalf("Unknown command %q", cmd)
}
}
func checkRequiredGoVersion() (float64, bool) {
ver := run("go", "version")
re := regexp.MustCompile(`go version go(\d+\.\d+)`)
if m := re.FindSubmatch(ver); len(m) == 2 {
re := regexp.MustCompile(`go(\d+\.\d+)`)
ver := runtime.Version()
if m := re.FindStringSubmatch(ver); len(m) == 2 {
vs := string(m[1])
// This is a standard go build. Verify that it's new enough.
f, err := strconv.ParseFloat(vs, 64)
@@ -166,7 +252,9 @@ func checkRequiredGoVersion() (float64, bool) {
log.Printf("*** Couldn't parse Go version out of %q.\n*** This isn't known to work, proceed on your own risk.", vs)
return 0, false
}
if f < minGoVersion {
if f < 1.5 {
log.Printf("*** Go version %.01f doesn't support the vendoring mechanism.\n*** Ensure correct dependencies in your $GOPATH.", f)
} else if f < minGoVersion {
log.Fatalf("*** Go version %.01f is less than required %.01f.\n*** This is known not to work, not proceeding.", f, minGoVersion)
}
return f, true
@@ -178,16 +266,16 @@ func checkRequiredGoVersion() (float64, bool) {
func setup() {
runPrint("go", "get", "-v", "golang.org/x/tools/cmd/cover")
runPrint("go", "get", "-v", "golang.org/x/tools/cmd/vet")
runPrint("go", "get", "-v", "golang.org/x/net/html")
runPrint("go", "get", "-v", "github.com/tools/godep")
runPrint("go", "get", "-v", "github.com/FiloSottile/gvt")
runPrint("go", "get", "-v", "github.com/axw/gocov/gocov")
runPrint("go", "get", "-v", "github.com/AlekSi/gocov-xml")
runPrint("go", "get", "-v", "bitbucket.org/tebeka/go2xunit")
}
func test(pkg string) {
setBuildEnv()
func test(pkgs ...string) {
lazyRebuildAssets()
useRace := runtime.GOARCH == "amd64"
switch runtime.GOOS {
case "darwin", "linux", "freebsd", "windows":
@@ -196,18 +284,20 @@ func test(pkg string) {
}
if useRace {
runPrint("go", "test", "-short", "-race", "-timeout", "60s", pkg)
runPrint("go", append([]string{"test", "-short", "-race", "-timeout", "60s"}, pkgs...)...)
} else {
runPrint("go", "test", "-short", "-timeout", "60s", pkg)
runPrint("go", append([]string{"test", "-short", "-timeout", "60s"}, pkgs...)...)
}
}
func bench(pkg string) {
setBuildEnv()
runPrint("go", "test", "-run", "NONE", "-bench", ".", pkg)
func bench(pkgs ...string) {
lazyRebuildAssets()
runPrint("go", append([]string{"test", "-run", "NONE", "-bench", "."}, pkgs...)...)
}
func install(pkg string, tags []string) {
func install(target target, tags []string) {
lazyRebuildAssets()
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
@@ -220,18 +310,17 @@ func install(pkg string, tags []string) {
if race {
args = append(args, "-race")
}
args = append(args, pkg)
setBuildEnv()
args = append(args, target.buildPkg)
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)
runPrint("go", args...)
}
func build(pkg string, tags []string) {
binary := "syncthing"
if goos == "windows" {
binary += ".exe"
}
func build(target target, tags []string) {
lazyRebuildAssets()
rmr(binary)
rmr(target.binaryName)
args := []string{"build", "-i", "-v", "-ldflags", ldflags()}
if len(tags) > 0 {
args = append(args, "-tags", strings.Join(tags, ","))
@@ -239,66 +328,64 @@ func build(pkg string, tags []string) {
if race {
args = append(args, "-race")
}
args = append(args, pkg)
setBuildEnv()
args = append(args, target.buildPkg)
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)
runPrint("go", args...)
}
func buildTar() {
name := archiveName()
func buildTar(target target) {
name := archiveName(target)
filename := name + ".tar.gz"
var tags []string
if noupgrade {
tags = []string{"noupgrade"}
name += "-noupgrade"
}
build("./cmd/syncthing", tags)
filename := name + ".tar.gz"
files := []archiveFile{
{src: "README.md", dst: name + "/README.txt"},
{src: "LICENSE", dst: name + "/LICENSE.txt"},
{src: "AUTHORS", dst: name + "/AUTHORS.txt"},
{src: "syncthing", dst: name + "/syncthing"},
}
for _, file := range listFiles("etc") {
files = append(files, archiveFile{src: file, dst: name + "/" + file})
}
for _, file := range listFiles("extra") {
files = append(files, archiveFile{src: file, dst: name + "/" + filepath.Base(file)})
}
build(target, tags)
if goos == "darwin" {
macosCodesign("syncthing")
macosCodesign(target.binaryName)
}
tarGz(filename, files)
for i := range target.archiveFiles {
target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
}
tarGz(filename, target.archiveFiles)
log.Println(filename)
}
func buildZip() {
name := archiveName()
func buildZip(target target) {
target.binaryName += ".exe"
name := archiveName(target)
filename := name + ".zip"
var tags []string
if noupgrade {
tags = []string{"noupgrade"}
name += "-noupgrade"
}
build("./cmd/syncthing", tags)
filename := name + ".zip"
files := []archiveFile{
{src: "README.md", dst: name + "/README.txt"},
{src: "LICENSE", dst: name + "/LICENSE.txt"},
{src: "AUTHORS", dst: name + "/AUTHORS.txt"},
{src: "syncthing.exe", dst: name + "/syncthing.exe"},
build(target, tags)
for i := range target.archiveFiles {
target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
}
for _, file := range listFiles("extra") {
files = append(files, archiveFile{src: file, dst: name + "/" + filepath.Base(file)})
}
zipFile(filename, files)
zipFile(filename, target.archiveFiles)
log.Println(filename)
}
func buildDeb() {
func buildDeb(target target) {
os.RemoveAll("deb")
// "goarch" here is set to whatever the Debian packages expect. We correct
@@ -312,32 +399,14 @@ func buildDeb() {
goarch = "arm"
}
build("./cmd/syncthing", []string{"noupgrade"})
build(target, []string{"noupgrade"})
files := []archiveFile{
{src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0644},
{src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0644},
{src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0644},
{src: "syncthing", dst: "deb/usr/bin/syncthing", perm: 0755},
{src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0644},
{src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0644},
{src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0644},
{src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0644},
{src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0644},
{src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0644},
{src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0644},
{src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0644},
{src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0644},
{src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0644},
{src: "etc/linux-systemd/system/syncthing@.service", dst: "deb/lib/systemd/system/syncthing@.service", perm: 0644},
{src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
for i := range target.debianFiles {
target.debianFiles[i].src = strings.Replace(target.debianFiles[i].src, "{{binary}}", target.binaryName, 1)
target.debianFiles[i].dst = strings.Replace(target.debianFiles[i].dst, "{{binary}}", target.binaryName, 1)
}
for _, file := range listFiles("extra") {
files = append(files, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0644})
}
for _, af := range files {
for _, af := range target.debianFiles {
if err := copyFile(af.src, af.dst, af.perm); err != nil {
log.Fatal(err)
}
@@ -346,11 +415,14 @@ func buildDeb() {
os.MkdirAll("deb/DEBIAN", 0755)
data := map[string]string{
"name": target.name,
"arch": debarch,
"version": version[1:],
"date": time.Now().Format(time.RFC1123),
}
for _, file := range listFiles("debian") {
debTemplateFiles := append(listFiles("debian/common"), listFiles("debian/"+target.name)...)
for _, file := range debTemplateFiles {
tpl, err := template.New(filepath.Base(file)).ParseFiles(file)
if err != nil {
log.Fatal(err)
@@ -402,21 +474,39 @@ func listFiles(dir string) []string {
return res
}
func setBuildEnv() {
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)
wd, err := os.Getwd()
if err != nil {
log.Println("Warning: can't determine current dir:", err)
log.Println("Build might not work as expected")
}
os.Setenv("GOPATH", fmt.Sprintf("%s%c%s", filepath.Join(wd, "Godeps", "_workspace"), os.PathListSeparator, os.Getenv("GOPATH")))
log.Println("GOPATH=" + os.Getenv("GOPATH"))
func rebuildAssets() {
runPipe("lib/auto/gui.files.go", "go", "run", "script/genassets.go", "gui")
}
func assets() {
setBuildEnv()
runPipe("lib/auto/gui.files.go", "go", "run", "script/genassets.go", "gui")
func lazyRebuildAssets() {
if shouldRebuildAssets() {
rebuildAssets()
}
}
func shouldRebuildAssets() bool {
info, err := os.Stat("lib/auto/gui.files.go")
if err != nil {
// If the file doesn't exist, we must rebuild it
return true
}
// Check if any of the files in gui/ are newer than the asset file. If
// so we should rebuild it.
currentBuild := info.ModTime()
assetsAreNewer := false
filepath.Walk("gui", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if assetsAreNewer {
return nil
}
assetsAreNewer = info.ModTime().After(currentBuild)
return nil
})
return assetsAreNewer
}
func xdr() {
@@ -437,24 +527,17 @@ func translate() {
func transifex() {
os.Chdir("gui/default/assets/lang")
runPrint("go", "run", "../../../../script/transifexdl.go")
os.Chdir("../../../..")
assets()
}
func deps() {
rmr("Godeps")
runPrint("godep", "save", "./cmd/...")
}
func clean() {
rmr("bin", "Godeps/_workspace/pkg", "Godeps/_workspace/bin")
rmr("bin")
rmr(filepath.Join(os.Getenv("GOPATH"), fmt.Sprintf("pkg/%s_%s/github.com/syncthing", goos, goarch)))
}
func ldflags() string {
sep := ' '
if goVersion > 1.4 {
sep = '='
sep := '='
if goVersion > 0 && goVersion < 1.5 {
sep = ' '
}
b := new(bytes.Buffer)
@@ -506,12 +589,66 @@ func getVersion() string {
}
// ... then see if we have a Git tag.
if ver, err := getGitVersion(); err == nil {
if strings.Contains(ver, "-") {
// The version already contains a hash and stuff. See if we can
// find a current branch name to tack onto it as well.
return ver + getBranchSuffix()
}
return ver
}
// This seems to be a dev build.
return "unknown-dev"
}
func getBranchSuffix() string {
bs, err := runError("git", "branch", "-a", "--contains")
if err != nil {
return ""
}
branches := strings.Split(string(bs), "\n")
if len(branches) == 0 {
return ""
}
branch := ""
for i, candidate := range branches {
if strings.HasPrefix(candidate, "*") {
// This is the current branch. Select it!
branch = strings.TrimLeft(candidate, " \t*")
break
} else if i == 0 {
// Otherwise the first branch in the list will do.
branch = strings.TrimSpace(branch)
}
}
if branch == "" {
return ""
}
// The branch name may be on the form "remotes/origin/foo" from which we
// just want "foo".
parts := strings.Split(branch, "/")
if len(parts) == 0 || len(parts[len(parts)-1]) == 0 {
return ""
}
branch = parts[len(parts)-1]
if branch == "master" {
// master builds are the default.
return ""
}
validBranchRe := regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
if !validBranchRe.MatchString(branch) {
// There's some odd stuff in the branch name. Better skip it.
return ""
}
return "-" + branch
}
func buildStamp() int64 {
bs, err := runError("git", "show", "-s", "--format=%ct")
if err != nil {
@@ -537,13 +674,6 @@ func buildHost() string {
return h
}
func buildEnvironment() string {
if v := os.Getenv("ENVIRONMENT"); len(v) > 0 {
return v
}
return "default"
}
func buildArch() string {
os := goos
if os == "darwin" {
@@ -552,18 +682,8 @@ func buildArch() string {
return fmt.Sprintf("%s-%s", os, goarch)
}
func archiveName() string {
return fmt.Sprintf("syncthing-%s-%s", buildArch(), version)
}
func run(cmd string, args ...string) []byte {
bs, err := runError(cmd, args...)
if err != nil {
log.Println(cmd, strings.Join(args, " "))
log.Println(string(bs))
log.Fatal(err)
}
return bytes.TrimSpace(bs)
func archiveName(target target) string {
return fmt.Sprintf("%s-%s-%s", target.name, buildArch(), version)
}
func runError(cmd string, args ...string) ([]byte, error) {
@@ -599,12 +719,6 @@ func runPipe(file, cmd string, args ...string) {
fd.Close()
}
type archiveFile struct {
src string
dst string
perm os.FileMode
}
func tarGz(out string, files []archiveFile) {
fd, err := os.Create(out)
if err != nil {
@@ -720,24 +834,25 @@ func zipFile(out string, files []archiveFile) {
}
}
func vet(pkg string) {
bs, err := runError("go", "vet", pkg)
if err != nil && err.Error() == "exit status 3" || bytes.Contains(bs, []byte("no such tool \"vet\"")) {
// Go said there is no go vet
log.Println(`- No go vet, no vetting. Try "go get -u golang.org/x/tools/cmd/vet".`)
return
func vet(dirs ...string) {
params := []string{"tool", "vet", "-all"}
params = append(params, dirs...)
bs, err := runError("go", params...)
if len(bs) > 0 {
log.Printf("%s", bs)
}
falseAlarmComposites := regexp.MustCompile("composite literal uses unkeyed fields")
exitStatus := regexp.MustCompile("exit status 1")
for _, line := range bytes.Split(bs, []byte("\n")) {
if falseAlarmComposites.Match(line) || exitStatus.Match(line) {
continue
}
if len(line) > 0 {
log.Printf("%s", line)
if err != nil {
if exitStatus(err) == 3 {
// Exit code 3, the "vet" tool is not installed
return
}
// A genuine error exit from the vet tool.
log.Fatal(err)
}
}
func lint(pkg string) {
@@ -776,3 +891,13 @@ func macosCodesign(file string) {
log.Println("Codesign: successfully signed", file)
}
}
func exitStatus(err error) int {
if err, ok := err.(*exec.ExitError); ok {
if ws, ok := err.ProcessState.Sys().(syscall.WaitStatus); ok {
return ws.ExitStatus()
}
}
return -1
}

View File

@@ -27,10 +27,6 @@ case "${1:-default}" in
build "$@"
;;
deps)
build "$@"
;;
assets)
build "$@"
;;
@@ -106,7 +102,7 @@ case "${1:-default}" in
fail=0
# For every package in the repo
for dir in $(go list ./...) ; do
for dir in $(go list ./lib/... ./cmd/...) ; do
# run the tests
GOPATH="$(pwd)/Godeps/_workspace:$GOPATH" go test -race -coverprofile=profile.out $dir
if [ -f profile.out ] ; then

143
cmd/stbench/main.go Normal file
View File

@@ -0,0 +1,143 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// This doesn't build on Windows due to the Rusage stuff.
// +build !windows
package main
import (
"flag"
"fmt"
"log"
"runtime"
"syscall"
"time"
"github.com/syncthing/syncthing/lib/rc"
)
var homeDir = "h1"
var syncthingBin = "./bin/syncthing"
var test = "scan"
func main() {
flag.StringVar(&homeDir, "home", homeDir, "Home directory location")
flag.StringVar(&syncthingBin, "bin", syncthingBin, "Binary location")
flag.StringVar(&test, "test", test, "Test to run")
flag.Parse()
switch test {
case "scan":
// scan measures the resource usage required to perform the initial
// scan, without cleaning away the database first.
testScan()
}
}
// testScan starts a process and reports on the resource usage required to
// perform the initial scan.
func testScan() {
log.Println("Starting...")
p := rc.NewProcess("127.0.0.1:8081")
if err := p.Start(syncthingBin, "-home", homeDir, "-no-browser"); err != nil {
log.Println(err)
return
}
defer p.Stop()
wallTime := awaitScanComplete(p)
report(p, wallTime)
}
// awaitScanComplete waits for a folder to transition idle->scanning and
// then scanning->idle and returns the time taken for the scan.
func awaitScanComplete(p *rc.Process) time.Duration {
log.Println("Awaiting scan completion...")
var t0, t1 time.Time
lastEvent := 0
loop:
for {
evs, err := p.Events(lastEvent)
if err != nil {
continue
}
for _, ev := range evs {
if ev.Type == "StateChanged" {
data := ev.Data.(map[string]interface{})
log.Println(ev)
if data["to"].(string) == "scanning" {
t0 = ev.Time
continue
}
if !t0.IsZero() && data["to"].(string) == "idle" {
t1 = ev.Time
break loop
}
}
lastEvent = ev.ID
}
time.Sleep(250 * time.Millisecond)
}
return t1.Sub(t0)
}
// report stops the given process and reports on it's resource usage in two
// ways: human readable to stderr, and CSV to stdout.
func report(p *rc.Process, wallTime time.Duration) {
sv, err := p.SystemVersion()
if err != nil {
log.Println(err)
return
}
ss, err := p.SystemStatus()
if err != nil {
log.Println(err)
return
}
proc, err := p.Stop()
if err != nil {
return
}
rusage, ok := proc.SysUsage().(*syscall.Rusage)
if !ok {
return
}
log.Println("Version:", sv.Version)
log.Println("Alloc:", ss.Alloc/1024, "KiB")
log.Println("Sys:", ss.Sys/1024, "KiB")
log.Println("Goroutines:", ss.Goroutines)
log.Println("Wall time:", wallTime)
log.Println("Utime:", time.Duration(rusage.Utime.Nano()))
log.Println("Stime:", time.Duration(rusage.Stime.Nano()))
if runtime.GOOS == "darwin" {
// Darwin reports in bytes, Linux seems to report in KiB even
// though the manpage says otherwise.
rusage.Maxrss /= 1024
}
log.Println("MaxRSS:", rusage.Maxrss, "KiB")
fmt.Printf("%s,%d,%d,%d,%.02f,%.02f,%.02f,%d\n",
sv.Version,
ss.Alloc/1024,
ss.Sys/1024,
ss.Goroutines,
wallTime.Seconds(),
time.Duration(rusage.Utime.Nano()).Seconds(),
time.Duration(rusage.Stime.Nano()).Seconds(),
rusage.Maxrss)
}

126
cmd/stdisco/main.go Normal file
View File

@@ -0,0 +1,126 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"bytes"
"crypto/rand"
"flag"
"log"
"strings"
"time"
"github.com/syncthing/syncthing/lib/beacon"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/protocol"
)
var (
all = false // print all packets, not just first from each device/source
fake = false // send fake packets to lure out other devices faster
mc = "[ff12::8384]:21027"
bc = 21027
)
var (
// Static prefix that we use when generating fake device IDs, so that we
// can recognize them ourselves. Also makes the device ID start with
// "STPROBE-" which is humanly recognizable.
randomPrefix = []byte{148, 223, 23, 4, 148}
// Our random, fake, device ID that we use when sending announcements.
myID = randomDeviceID()
)
func main() {
flag.BoolVar(&all, "all", all, "Print all received announcements (not only first)")
flag.BoolVar(&fake, "fake", fake, "Send fake announcements")
flag.StringVar(&mc, "mc", mc, "IPv6 multicast address")
flag.IntVar(&bc, "bc", bc, "IPv4 broadcast port number")
flag.Parse()
if fake {
log.Println("My ID:", protocol.DeviceIDFromBytes(myID))
}
runbeacon(beacon.NewMulticast(mc), fake)
runbeacon(beacon.NewBroadcast(bc), fake)
select {}
}
func runbeacon(bc beacon.Interface, fake bool) {
go bc.Serve()
go recv(bc)
if fake {
go send(bc)
}
}
// receives and prints discovery announcements
func recv(bc beacon.Interface) {
seen := make(map[string]bool)
for {
data, src := bc.Recv()
var ann discover.Announce
ann.UnmarshalXDR(data)
if bytes.Equal(ann.This.ID, myID) {
// This is one of our own fake packets, don't print it.
continue
}
// Print announcement details for the first packet from a given
// device ID and source address, or if -all was given.
key := string(ann.This.ID) + src.String()
if all || !seen[key] {
log.Printf("Announcement from %v\n", src)
log.Printf(" %v at %s\n", protocol.DeviceIDFromBytes(ann.This.ID), strings.Join(addrStrs(ann.This), ", "))
for _, dev := range ann.Extra {
log.Printf(" %v at %s\n", protocol.DeviceIDFromBytes(dev.ID), strings.Join(addrStrs(dev), ", "))
}
seen[key] = true
}
}
}
// sends fake discovery announcements once every second
func send(bc beacon.Interface) {
ann := discover.Announce{
Magic: discover.AnnouncementMagic,
This: discover.Device{
ID: myID,
Addresses: []discover.Address{
{URL: "tcp://fake.example.com:12345"},
},
},
}
bs, _ := ann.MarshalXDR()
for {
bc.Send(bs)
time.Sleep(time.Second)
}
}
// returns the list of address URLs
func addrStrs(dev discover.Device) []string {
ss := make([]string, len(dev.Addresses))
for i, addr := range dev.Addresses {
ss[i] = addr.URL
}
return ss
}
// returns a random but recognizable device ID
func randomDeviceID() []byte {
var id [32]byte
copy(id[:], randomPrefix)
rand.Read(id[len(randomPrefix):])
return id[:]
}

View File

@@ -49,9 +49,8 @@ func main() {
}
type checkResult struct {
server string
direct []string
relays []discover.Relay
server string
addresses []string
error
}
@@ -76,17 +75,14 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
if res.error != nil {
fmt.Println(" " + res.error.Error())
}
for _, addr := range res.direct {
for _, addr := range res.addresses {
fmt.Println(" address:", addr)
}
for _, rel := range res.relays {
fmt.Printf(" relay: %s (%d ms)\n", rel.URL, rel.Latency)
}
}
}
func checkServer(deviceID protocol.DeviceID, server string) checkResult {
disco, err := discover.NewGlobal(server, tls.Certificate{}, nil, nil)
disco, err := discover.NewGlobal(server, tls.Certificate{}, nil)
if err != nil {
return checkResult{error: err}
}
@@ -98,8 +94,8 @@ func checkServer(deviceID protocol.DeviceID, server string) checkResult {
})
go func() {
direct, relays, err := disco.Lookup(deviceID)
res <- checkResult{direct: direct, relays: relays, error: err}
addresses, err := disco.Lookup(deviceID)
res <- checkResult{addresses: addresses, error: err}
}()
return <-res

124
cmd/stgenfiles/main.go Normal file
View File

@@ -0,0 +1,124 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"path/filepath"
"time"
)
func main() {
dir := flag.String("dir", "~/files", "Directory to generate into")
files := flag.Int("files", 1000, "Number of files to create")
maxExp := flag.Int("maxexp", 20, "Max size exponent")
src := flag.String("src", "/dev/urandom", "Source of file data")
flag.Parse()
if err := generateFiles(*dir, *files, *maxExp, *src); err != nil {
log.Println(err)
}
}
func generateFiles(dir string, files, maxexp int, srcname string) error {
fd, err := os.Open(srcname)
if err != nil {
return err
}
for i := 0; i < files; i++ {
n := randomName()
if rand.Float64() < 0.05 {
// Some files and directories are dotfiles
n = "." + n
}
p0 := filepath.Join(dir, string(n[0]), n[0:2])
err = os.MkdirAll(p0, 0755)
if err != nil {
log.Fatal(err)
}
p1 := filepath.Join(p0, n)
s := int64(1 << uint(rand.Intn(maxexp)))
a := int64(128 * 1024)
if a > s {
a = s
}
s += rand.Int63n(a)
if err := generateOneFile(fd, p1, s); err != nil {
return err
}
}
return nil
}
func generateOneFile(fd io.ReadSeeker, p1 string, s int64) error {
src := io.LimitReader(&inifiteReader{fd}, int64(s))
dst, err := os.Create(p1)
if err != nil {
return err
}
_, err = io.Copy(dst, src)
if err != nil {
return err
}
err = dst.Close()
if err != nil {
return err
}
_ = os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400))
t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
err = os.Chtimes(p1, t, t)
if err != nil {
return err
}
return nil
}
func randomName() string {
var b [16]byte
readRand(b[:])
return fmt.Sprintf("%x", b[:])
}
func readRand(bs []byte) (int, error) {
var r uint32
for i := range bs {
if i%4 == 0 {
r = uint32(rand.Int63())
}
bs[i] = byte(r >> uint((i%4)*8))
}
return len(bs), nil
}
type inifiteReader struct {
rd io.ReadSeeker
}
func (i *inifiteReader) Read(bs []byte) (int, error) {
n, err := i.rd.Read(bs)
if err == io.EOF {
err = nil
i.rd.Seek(0, 0)
}
return n, err
}

View File

@@ -15,7 +15,6 @@ import (
"github.com/syndtr/goleveldb/leveldb"
)
// An IntHeap is a min-heap of ints.
type SizedElement struct {
key string
size int

View File

@@ -8,6 +8,7 @@ package main
import (
"flag"
"io"
"io/ioutil"
"log"
"os"
@@ -31,7 +32,7 @@ Where command is one of:
gen
- generate a new key pair
sign <privkeyfile> <datafile>
sign <privkeyfile> [datafile]
- sign a file
verify <signaturefile> <datafile>
@@ -72,13 +73,19 @@ func sign(keyname, dataname string) {
log.Fatal(err)
}
fd, err := os.Open(dataname)
if err != nil {
log.Fatal(err)
var input io.Reader
if dataname == "-" || dataname == "" {
input = os.Stdin
} else {
fd, err := os.Open(dataname)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
input = fd
}
defer fd.Close()
sig, err := signature.Sign(privkey, fd)
sig, err := signature.Sign(privkey, input)
if err != nil {
log.Fatal(err)
}

212
cmd/stvanity/main.go Normal file
View File

@@ -0,0 +1,212 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"flag"
"fmt"
"math/big"
mr "math/rand"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/syncthing/syncthing/lib/protocol"
)
type result struct {
id protocol.DeviceID
priv *ecdsa.PrivateKey
derBytes []byte
}
func main() {
flag.Parse()
prefix := strings.ToUpper(strings.Replace(flag.Arg(0), "-", "", -1))
if len(prefix) > 7 {
prefix = prefix[:7] + "-" + prefix[7:]
}
found := make(chan result)
stop := make(chan struct{})
var count int64
// Print periodic progress reports.
go printProgress(prefix, &count)
// Run one certificate generator per CPU core.
var wg sync.WaitGroup
for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
wg.Add(1)
go func() {
generatePrefixed(prefix, &count, found, stop)
wg.Done()
}()
}
// Save the result, when one has been found.
res := <-found
close(stop)
wg.Wait()
fmt.Println("Found", res.id)
saveCert(res.priv, res.derBytes)
fmt.Println("Saved to cert.pem, key.pem")
}
// Try certificates until one is found that has the prefix at the start of
// the resulting device ID. Increments count atomically, sends the result to
// found, returns when stop is closed.
func generatePrefixed(prefix string, count *int64, found chan<- result, stop <-chan struct{}) {
notBefore := time.Now()
notAfter := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
template := x509.Certificate{
SerialNumber: new(big.Int).SetInt64(mr.Int63()),
Subject: pkix.Name{
CommonName: "syncthing",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
for {
select {
case <-stop:
return
default:
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
id := protocol.NewDeviceID(derBytes)
atomic.AddInt64(count, 1)
if strings.HasPrefix(id.String(), prefix) {
select {
case found <- result{id, priv, derBytes}:
case <-stop:
}
return
}
}
}
func printProgress(prefix string, count *int64) {
started := time.Now()
wantBits := 5 * len(prefix)
if wantBits > 63 {
fmt.Printf("Want %d bits for prefix %q, refusing to boil the ocean.\n", wantBits, prefix)
os.Exit(1)
}
expectedIterations := float64(int(1) << uint(wantBits))
fmt.Printf("Want %d bits for prefix %q, about %.2g certs to test (statistically speaking)\n", wantBits, prefix, expectedIterations)
for _ = range time.NewTicker(15 * time.Second).C {
tried := atomic.LoadInt64(count)
elapsed := time.Since(started)
rate := float64(tried) / elapsed.Seconds()
expected := timeStr(expectedIterations / rate)
fmt.Printf("Trying %.0f certs/s, tested %d so far in %v, expect ~%s total time to complete\n", rate, tried, elapsed/time.Second*time.Second, expected)
}
}
func saveCert(priv interface{}, derBytes []byte) {
certOut, err := os.Create("cert.pem")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = certOut.Close()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
block, err := pemBlockForKey(priv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = pem.Encode(keyOut, block)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = keyOut.Close()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func pemBlockForKey(priv interface{}) (*pem.Block, error) {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
return nil, err
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
default:
return nil, fmt.Errorf("unknown key type")
}
}
func timeStr(seconds float64) string {
if seconds < 60 {
return fmt.Sprintf("%.0fs", seconds)
}
if seconds < 3600 {
return fmt.Sprintf("%.0fm", seconds/60)
}
if seconds < 86400 {
return fmt.Sprintf("%.0fh", seconds/3600)
}
if seconds < 86400*365 {
return fmt.Sprintf("%.0f days", seconds/3600)
}
return fmt.Sprintf("%.0f years", seconds/86400/365)
}

View File

@@ -1,127 +0,0 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"fmt"
"net"
"net/url"
"github.com/syncthing/syncthing/lib/config"
)
type addressLister struct {
upnpService *upnpService
cfg *config.Wrapper
}
func newAddressLister(upnpService *upnpService, cfg *config.Wrapper) *addressLister {
return &addressLister{
upnpService: upnpService,
cfg: cfg,
}
}
// ExternalAddresses returns a list of addresses that are our best guess for
// where we are reachable from the outside. As a special case, we may return
// one or more addresses with an empty IP address (0.0.0.0 or ::) and just
// port number - this means that the outside address of a NAT gateway should
// be substituted.
func (e *addressLister) ExternalAddresses() []string {
return e.addresses(false)
}
// AllAddresses returns a list of addresses that are our best guess for where
// we are reachable from the local network. Same conditions as
// ExternalAddresses, but private IPv4 addresses are included.
func (e *addressLister) AllAddresses() []string {
return e.addresses(true)
}
func (e *addressLister) addresses(includePrivateIPV4 bool) []string {
var addrs []string
// Grab our listen addresses from the config. Unspecified ones are passed
// on verbatim (to be interpreted by a global discovery server or local
// discovery peer). Public addresses are passed on verbatim. Private
// addresses are filtered.
for _, addrStr := range e.cfg.Options().ListenAddress {
addrURL, err := url.Parse(addrStr)
if err != nil {
l.Infoln("Listen address", addrStr, "is invalid:", err)
continue
}
addr, err := net.ResolveTCPAddr("tcp", addrURL.Host)
if err != nil {
l.Infoln("Listen address", addrStr, "is invalid:", err)
continue
}
if addr.IP == nil || addr.IP.IsUnspecified() {
// Address like 0.0.0.0:22000 or [::]:22000 or :22000; include as is.
addrs = append(addrs, tcpAddr(addr.String()))
} else if isPublicIPv4(addr.IP) || isPublicIPv6(addr.IP) {
// A public address; include as is.
addrs = append(addrs, tcpAddr(addr.String()))
} else if includePrivateIPV4 && addr.IP.To4().IsGlobalUnicast() {
// A private IPv4 address.
addrs = append(addrs, tcpAddr(addr.String()))
}
}
// Get an external port mapping from the upnpService, if it has one. If so,
// add it as another unspecified address.
if e.upnpService != nil {
if port := e.upnpService.ExternalPort(); port != 0 {
addrs = append(addrs, fmt.Sprintf("tcp://:%d", port))
}
}
return addrs
}
func isPublicIPv4(ip net.IP) bool {
ip = ip.To4()
if ip == nil {
// Not an IPv4 address (IPv6)
return false
}
// IsGlobalUnicast below only checks that it's not link local or
// multicast, and we want to exclude private (NAT:ed) addresses as well.
rfc1918 := []net.IPNet{
{IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}},
{IP: net.IP{172, 16, 0, 0}, Mask: net.IPMask{255, 240, 0, 0}},
{IP: net.IP{192, 168, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}},
}
for _, n := range rfc1918 {
if n.Contains(ip) {
return false
}
}
return ip.IsGlobalUnicast()
}
func isPublicIPv6(ip net.IP) bool {
if ip.To4() != nil {
// Not an IPv6 address (IPv4)
// (To16() returns a v6 mapped v4 address so can't be used to check
// that it's an actual v6 address)
return false
}
return ip.IsGlobalUnicast()
}
func tcpAddr(host string) string {
u := url.URL{
Scheme: "tcp",
Host: host,
}
return u.String()
}

View File

@@ -35,10 +35,11 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/stats"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/util"
"github.com/vitrun/qart/qr"
"golang.org/x/crypto/bcrypt"
)
@@ -49,45 +50,96 @@ var (
)
type apiService struct {
id protocol.DeviceID
cfg *config.Wrapper
assetDir string
themes []string
model *model.Model
eventSub *events.BufferedSubscription
discoverer *discover.CachingMux
relayService *relay.Service
fss *folderSummaryService
systemConfigMut sync.Mutex // serializes posts to /rest/system/config
stop chan struct{} // signals intentional stop
configChanged chan struct{} // signals intentional listener close due to config change
started chan struct{} // signals startup complete, for testing only
id protocol.DeviceID
cfg configIntf
httpsCertFile string
httpsKeyFile string
assetDir string
themes []string
model modelIntf
eventSub events.BufferedSubscription
discoverer discover.CachingMux
connectionsService connectionsIntf
fss *folderSummaryService
systemConfigMut sync.Mutex // serializes posts to /rest/system/config
stop chan struct{} // signals intentional stop
configChanged chan struct{} // signals intentional listener close due to config change
started chan struct{} // signals startup complete, for testing only
listener net.Listener
listenerMut sync.Mutex
guiErrors *logger.Recorder
systemLog *logger.Recorder
guiErrors logger.Recorder
systemLog logger.Recorder
}
func newAPIService(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.CachingMux, relayService *relay.Service, errors, systemLog *logger.Recorder) (*apiService, error) {
type modelIntf interface {
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
Completion(device protocol.DeviceID, folder string) float64
Override(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int)
NeedSize(folder string) (nfiles int, bytes int64)
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
ResetFolder(folder string)
Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []model.Availability
GetIgnores(folder string) ([]string, []string, error)
SetIgnores(folder string, content []string) error
PauseDevice(device protocol.DeviceID)
ResumeDevice(device protocol.DeviceID)
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
ScanFolders() map[string]error
ScanFolderSubs(folder string, subs []string) error
BringToFront(folder, file string)
ConnectedTo(deviceID protocol.DeviceID) bool
GlobalSize(folder string) (nfiles, deleted int, bytes int64)
LocalSize(folder string) (nfiles, deleted int, bytes int64)
CurrentLocalVersion(folder string) (int64, bool)
RemoteLocalVersion(folder string) (int64, bool)
State(folder string) (string, time.Time, error)
}
type configIntf interface {
GUI() config.GUIConfiguration
Raw() config.Configuration
Options() config.OptionsConfiguration
Replace(cfg config.Configuration) config.CommitResponse
Subscribe(c config.Committer)
Folders() map[string]config.FolderConfiguration
Devices() map[protocol.DeviceID]config.DeviceConfiguration
Save() error
ListenAddresses() []string
}
type connectionsIntf interface {
Status() map[string]interface{}
}
func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKeyFile, assetDir string, m modelIntf, eventSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connectionsIntf, errors, systemLog logger.Recorder) (*apiService, error) {
service := &apiService{
id: id,
cfg: cfg,
assetDir: assetDir,
model: m,
eventSub: eventSub,
discoverer: discoverer,
relayService: relayService,
systemConfigMut: sync.NewMutex(),
stop: make(chan struct{}),
configChanged: make(chan struct{}),
listenerMut: sync.NewMutex(),
guiErrors: errors,
systemLog: systemLog,
id: id,
cfg: cfg,
httpsCertFile: httpsCertFile,
httpsKeyFile: httpsKeyFile,
assetDir: assetDir,
model: m,
eventSub: eventSub,
discoverer: discoverer,
connectionsService: connectionsService,
systemConfigMut: sync.NewMutex(),
stop: make(chan struct{}),
configChanged: make(chan struct{}),
listenerMut: sync.NewMutex(),
guiErrors: errors,
systemLog: systemLog,
}
seen := make(map[string]struct{})
// Load themes from compiled in assets.
for file := range auto.Assets() {
theme := strings.Split(file, "/")[0]
if _, ok := seen[theme]; !ok {
@@ -95,6 +147,15 @@ func newAPIService(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m
service.themes = append(service.themes, theme)
}
}
if assetDir != "" {
// Load any extra themes from the asset override dir.
for _, dir := range dirNames(assetDir) {
if _, ok := seen[dir]; !ok {
seen[dir] = struct{}{}
service.themes = append(service.themes, dir)
}
}
}
var err error
service.listener, err = service.getListener(cfg.GUI())
@@ -102,7 +163,7 @@ func newAPIService(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m
}
func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
cert, err := tls.LoadX509KeyPair(s.httpsCertFile, s.httpsKeyFile)
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
l.Infoln("Creating new HTTPS certificate")
@@ -115,7 +176,7 @@ func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener,
name = tlsDefaultCommonName
}
cert, err = tlsutil.NewCertificate(locations[locHTTPSCertFile], locations[locHTTPSKeyFile], name, httpsRSABits)
cert, err = tlsutil.NewCertificate(s.httpsCertFile, s.httpsKeyFile, name, httpsRSABits)
}
if err != nil {
return nil, err
@@ -143,7 +204,10 @@ func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener,
return nil, err
}
listener := &tlsutil.DowngradingListener{rawListener, tlsCfg}
listener := &tlsutil.DowngradingListener{
Listener: rawListener,
TLSConfig: tlsCfg,
}
return listener, nil
}
@@ -245,12 +309,9 @@ func (s *apiService) Serve() {
guiCfg := s.cfg.GUI()
// Add the CORS handling
handler := corsMiddleware(mux)
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
handler = csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, handler)
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, mux)
// Add our version and ID as a header to responses
handler = withDetailsMiddleware(s.id, handler)
@@ -265,6 +326,9 @@ func (s *apiService) Serve() {
handler = redirectToHTTPSMiddleware(handler)
}
// Add the CORS handling
handler = corsMiddleware(handler)
handler = debugMiddleware(handler)
srv := http.Server{
@@ -393,7 +457,7 @@ func corsMiddleware(next http.Handler) http.Handler {
// when the browser initiate a POST request.
//
// As the OPTIONS request is unauthorized, this handler must be the first
// of the chain.
// of the chain (hence added at the end).
//
// See https://www.w3.org/TR/cors/ for details.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -540,7 +604,7 @@ func (s *apiService) getDBStatus(w http.ResponseWriter, r *http.Request) {
sendJSON(w, folderSummary(s.cfg, s.model, folder))
}
func folderSummary(cfg *config.Wrapper, m *model.Model, folder string) map[string]interface{} {
func folderSummary(cfg configIntf, m modelIntf, folder string) map[string]interface{} {
var res = make(map[string]interface{})
res["invalid"] = cfg.Folders()[folder].Invalid
@@ -637,7 +701,7 @@ func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
return
}
av := s.model.Availability(folder, file)
av := s.model.Availability(folder, file, protocol.Vector{}, protocol.BlockInfo{})
sendJSON(w, map[string]interface{}{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
@@ -654,6 +718,7 @@ func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
defer s.systemConfigMut.Unlock()
to, err := config.ReadJSON(r.Body, myID)
r.Body.Close()
if err != nil {
l.Warnln("decoding posted config:", err)
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -678,7 +743,7 @@ func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
// UR was enabled
to.Options.URAccepted = usageReportVersion
to.Options.URUniqueID = randomString(8)
to.Options.URUniqueID = util.RandomString(8)
} else if to.Options.URAccepted < curAcc {
// UR was disabled
to.Options.URAccepted = -1
@@ -765,18 +830,9 @@ func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["discoveryMethods"] = discoMethods
res["discoveryErrors"] = discoErrors
}
if s.relayService != nil {
res["relaysEnabled"] = true
relayClientStatus := make(map[string]bool)
relayClientLatency := make(map[string]int)
for _, relay := range s.relayService.Relays() {
latency, ok := s.relayService.RelayStatus(relay)
relayClientStatus[relay] = ok
relayClientLatency[relay] = int(latency / time.Millisecond)
}
res["relayClientStatus"] = relayClientStatus
res["relayClientLatency"] = relayClientLatency
}
res["connectionServiceStatus"] = s.connectionsService.Status()
cpuUsageLock.RLock()
var cpusum float64
for _, p := range cpuUsagePercent {
@@ -878,17 +934,22 @@ func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) {
sendJSON(w, map[string][]string{
"ignore": ignores,
"patterns": patterns,
"expanded": patterns,
})
}
func (s *apiService) postDBIgnores(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
var data map[string][]string
err := json.NewDecoder(r.Body).Decode(&data)
bs, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
var data map[string][]string
err = json.Unmarshal(bs, &data)
if err != nil {
http.Error(w, err.Error(), 500)
return
@@ -1133,22 +1194,33 @@ func (s embeddedStatic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
file = "index.html"
}
if s.assetDir != "" {
p := filepath.Join(s.assetDir, filepath.FromSlash(file))
_, err := os.Stat(p)
if err == nil {
http.ServeFile(w, r, p)
return
}
}
s.mut.RLock()
theme := s.theme
modified := s.lastModified
s.mut.RUnlock()
// Check for an override for the current theme.
if s.assetDir != "" {
p := filepath.Join(s.assetDir, s.theme, filepath.FromSlash(file))
if _, err := os.Stat(p); err == nil {
http.ServeFile(w, r, p)
return
}
}
// Check for a compiled in asset for the current theme.
bs, ok := s.assets[theme+"/"+file]
if !ok {
// Check for an overridden default asset.
if s.assetDir != "" {
p := filepath.Join(s.assetDir, config.DefaultTheme, filepath.FromSlash(file))
if _, err := os.Stat(p); err == nil {
http.ServeFile(w, r, p)
return
}
}
// Check for a compiled in default asset.
bs, ok = s.assets[config.DefaultTheme+"/"+file]
if !ok {
http.NotFound(w, r)
@@ -1275,3 +1347,26 @@ func (v jsonVersionVector) MarshalJSON() ([]byte, error) {
}
return json.Marshal(res)
}
func dirNames(dir string) []string {
fd, err := os.Open(dir)
if err != nil {
return nil
}
defer fd.Close()
fis, err := fd.Readdir(-1)
if err != nil {
return nil
}
var dirs []string
for _, fi := range fis {
if fi.IsDir() {
dirs = append(dirs, filepath.Base(fi.Name()))
}
}
sort.Strings(dirs)
return dirs
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
"golang.org/x/crypto/bcrypt"
)
@@ -77,20 +78,43 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
return
}
// Check if the username is correct, assuming it was sent as UTF-8
username := string(fields[0])
if username != cfg.User {
emitLoginAttempt(false, username)
error()
return
if username == cfg.User {
goto usernameOK
}
if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), fields[1]); err != nil {
emitLoginAttempt(false, username)
error()
return
// ... check it again, converting it from assumed ISO-8859-1 to UTF-8
username = string(iso88591ToUTF8(fields[0]))
if username == cfg.User {
goto usernameOK
}
sessionid := randomString(32)
// Neither of the possible interpretations match the configured username
emitLoginAttempt(false, username)
error()
return
usernameOK:
// Check password as given (assumes UTF-8 encoding)
password := fields[1]
if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), password); err == nil {
goto passwordOK
}
// ... check it again, converting it from assumed ISO-8859-1 to UTF-8
password = iso88591ToUTF8(password)
if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), password); err == nil {
goto passwordOK
}
// Neither of the attempts to verify the password checked out
emitLoginAttempt(false, username)
error()
return
passwordOK:
sessionid := util.RandomString(32)
sessionsMut.Lock()
sessions[sessionid] = true
sessionsMut.Unlock()
@@ -104,3 +128,15 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
next.ServeHTTP(w, r)
})
}
// Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
// principle that ISO-8859-1 bytes are equivalent to unicode code points,
// that a rune slice is a list of code points, and that stringifying a slice
// of runes generates UTF-8 in Go.
func iso88591ToUTF8(s []byte) []byte {
runes := make([]rune, len(s))
for i := range s {
runes[i] = rune(s[i])
}
return []byte(string(runes))
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
// csrfTokens is a list of valid tokens. It is sorted so that the most
@@ -97,7 +98,7 @@ func validCsrfToken(token string) bool {
}
func newCsrfToken() string {
token := randomString(32)
token := util.RandomString(32)
csrfMut.Lock()
csrfTokens = append([]string{token}, csrfTokens...)

View File

@@ -7,10 +7,21 @@
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
)
@@ -48,9 +59,6 @@ func TestCSRFToken(t *testing.T) {
}
func TestStopAfterBrokenConfig(t *testing.T) {
baseDirs["config"] = "../../test/h1" // to load HTTPS keys
expandLocations()
cfg := config.Configuration{
GUI: config.GUIConfiguration{
RawAddress: "127.0.0.1:0",
@@ -59,7 +67,7 @@ func TestStopAfterBrokenConfig(t *testing.T) {
}
w := config.Wrap("/dev/null", cfg)
srv, err := newAPIService(protocol.LocalDeviceID, w, "", nil, nil, nil, nil, nil, nil)
srv, err := newAPIService(protocol.LocalDeviceID, w, "../../test/h1/https-cert.pem", "../../test/h1/https-key.pem", "", nil, nil, nil, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -89,3 +97,397 @@ func TestStopAfterBrokenConfig(t *testing.T) {
sup.Stop()
}
func TestAssetsDir(t *testing.T) {
// For any given request to $FILE, we should return the first found of
// - assetsdir/$THEME/$FILE
// - compiled in asset $THEME/$FILE
// - assetsdir/default/$FILE
// - compiled in asset default/$FILE
// The asset map contains compressed assets, so create a couple of gzip compressed assets here.
buf := new(bytes.Buffer)
gw := gzip.NewWriter(buf)
gw.Write([]byte("default"))
gw.Close()
def := buf.Bytes()
buf = new(bytes.Buffer)
gw = gzip.NewWriter(buf)
gw.Write([]byte("foo"))
gw.Close()
foo := buf.Bytes()
e := embeddedStatic{
theme: "foo",
mut: sync.NewRWMutex(),
assetDir: "testdata",
assets: map[string][]byte{
"foo/a": foo, // overridden in foo/a
"foo/b": foo,
"default/a": def, // overridden in default/a (but foo/a takes precedence)
"default/b": def, // overridden in default/b (but foo/b takes precedence)
"default/c": def,
},
}
s := httptest.NewServer(e)
defer s.Close()
// assetsdir/foo/a exists, overrides compiled in
expectURLToContain(t, s.URL+"/a", "overridden-foo")
// foo/b is compiled in, default/b is overridden, return compiled in
expectURLToContain(t, s.URL+"/b", "foo")
// only exists as compiled in default/c so use that
expectURLToContain(t, s.URL+"/c", "default")
// only exists as overridden default/d so use that
expectURLToContain(t, s.URL+"/d", "overridden-default")
}
func expectURLToContain(t *testing.T, url, exp string) {
res, err := http.Get(url)
if err != nil {
t.Error(err)
return
}
if res.StatusCode != 200 {
t.Errorf("Got %s instead of 200 OK", res.Status)
return
}
data, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Error(err)
return
}
if string(data) != exp {
t.Errorf("Got %q instead of %q on %q", data, exp, url)
return
}
}
func TestDirNames(t *testing.T) {
names := dirNames("testdata")
expected := []string{"default", "foo", "testfolder"}
if diff, equal := messagediff.PrettyDiff(expected, names); !equal {
t.Errorf("Unexpected dirNames return: %#v\n%s", names, diff)
}
}
type httpTestCase struct {
URL string // URL to check
Code int // Expected result code
Type string // Expected content type
Prefix string // Expected result prefix
Timeout time.Duration // Defaults to a second
}
func TestAPIServiceRequests(t *testing.T) {
cfg := new(mockedConfig)
baseURL, err := startHTTP(cfg)
if err != nil {
t.Fatal(err)
}
cases := []httpTestCase{
// /rest/db
{
URL: "/rest/db/completion?device=" + protocol.LocalDeviceID.String() + "&folder=default",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/db/file?folder=default&file=something",
Code: 404,
},
{
URL: "/rest/db/ignores?folder=default",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/db/need?folder=default",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/db/status?folder=default",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/db/browse?folder=default",
Code: 200,
Type: "application/json",
Prefix: "null",
},
// /rest/stats
{
URL: "/rest/stats/device",
Code: 200,
Type: "application/json",
Prefix: "null",
},
{
URL: "/rest/stats/folder",
Code: 200,
Type: "application/json",
Prefix: "null",
},
// /rest/svc
{
URL: "/rest/svc/deviceid?id=" + protocol.LocalDeviceID.String(),
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/svc/lang",
Code: 200,
Type: "application/json",
Prefix: "[",
},
{
URL: "/rest/svc/report",
Code: 200,
Type: "application/json",
Prefix: "{",
Timeout: 5 * time.Second,
},
// /rest/system
{
URL: "/rest/system/browse?current=~",
Code: 200,
Type: "application/json",
Prefix: "[",
},
{
URL: "/rest/system/config",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/config/insync",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/connections",
Code: 200,
Type: "application/json",
Prefix: "null",
},
{
URL: "/rest/system/discovery",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/error?since=0",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/ping",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/status",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/version",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/debug",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/log?since=0",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/system/log.txt?since=0",
Code: 200,
Type: "text/plain",
Prefix: "",
},
}
for _, tc := range cases {
t.Log("Testing", tc.URL, "...")
testHTTPRequest(t, baseURL, tc)
}
}
// testHTTPRequest tries the given test case, comparing the result code,
// content type, and result prefix.
func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase) {
timeout := time.Second
if tc.Timeout > 0 {
timeout = tc.Timeout
}
cli := &http.Client{
Timeout: timeout,
}
resp, err := cli.Get(baseURL + tc.URL)
if err != nil {
t.Errorf("Unexpected error requesting %s: %v", tc.URL, err)
return
}
defer resp.Body.Close()
if resp.StatusCode != tc.Code {
t.Errorf("Get on %s should have returned status code %d, not %s", tc.URL, tc.Code, resp.Status)
return
}
ct := resp.Header.Get("Content-Type")
if !strings.HasPrefix(ct, tc.Type) {
t.Errorf("The content type on %s should be %q, not %q", tc.URL, tc.Type, ct)
return
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("Unexpected error reading %s: %v", tc.URL, err)
return
}
if !bytes.HasPrefix(data, []byte(tc.Prefix)) {
t.Errorf("Returned data from %s does not have prefix %q: %s", tc.URL, tc.Prefix, data)
return
}
}
func TestHTTPLogin(t *testing.T) {
cfg := new(mockedConfig)
cfg.gui.User = "üser"
cfg.gui.Password = "$2a$10$IdIZTxTg/dCNuNEGlmLynOjqg4B1FvDKuIV5e0BB3pnWVHNb8.GSq" // bcrypt of "räksmörgås" in UTF-8
baseURL, err := startHTTP(cfg)
if err != nil {
t.Fatal(err)
}
// Verify rejection when not using authorization
req, _ := http.NewRequest("GET", baseURL+"/rest/system/status", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusUnauthorized {
t.Errorf("Unexpected non-401 return code %d for unauthed request", resp.StatusCode)
}
// Verify that incorrect password is rejected
req.SetBasicAuth("üser", "rksmrgs")
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusUnauthorized {
t.Errorf("Unexpected non-401 return code %d for incorrect password", resp.StatusCode)
}
// Verify that incorrect username is rejected
req.SetBasicAuth("user", "räksmörgås") // string literals in Go source code are in UTF-8
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusUnauthorized {
t.Errorf("Unexpected non-401 return code %d for incorrect username", resp.StatusCode)
}
// Verify that UTF-8 auth works
req.SetBasicAuth("üser", "räksmörgås") // string literals in Go source code are in UTF-8
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected non-200 return code %d for authed request (UTF-8)", resp.StatusCode)
}
// Verify that ISO-8859-1 auth
req.SetBasicAuth("\xfcser", "r\xe4ksm\xf6rg\xe5s") // escaped ISO-8859-1
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected non-200 return code %d for authed request (ISO-8859-1)", resp.StatusCode)
}
}
func startHTTP(cfg *mockedConfig) (string, error) {
model := new(mockedModel)
httpsCertFile := "../../test/h1/https-cert.pem"
httpsKeyFile := "../../test/h1/https-key.pem"
assetDir := "../../gui"
eventSub := new(mockedEventSub)
discoverer := new(mockedCachingMux)
connections := new(mockedConnections)
errorLog := new(mockedLoggerRecorder)
systemLog := new(mockedLoggerRecorder)
// Instantiate the API service
svc, err := newAPIService(protocol.LocalDeviceID, cfg, httpsCertFile, httpsKeyFile, assetDir, model,
eventSub, discoverer, connections, errorLog, systemLog)
if err != nil {
return "", err
}
// Make sure the API service is listening, and get the URL to use.
addr := svc.listener.Addr()
if addr == nil {
return "", fmt.Errorf("Nil listening address from API service")
}
tcpAddr, err := net.ResolveTCPAddr("tcp", addr.String())
if err != nil {
return "", fmt.Errorf("Weird address from API service: %v", err)
}
baseURL := fmt.Sprintf("http://127.0.0.1:%d", tcpAddr.Port)
// Actually start the API service
supervisor := suture.NewSimple("API test")
supervisor.Add(svc)
supervisor.ServeBackground()
return baseURL, nil
}

View File

@@ -48,7 +48,7 @@ var locations = map[locationEnum]string{
locKeyFile: "${config}/key.pem",
locHTTPSCertFile: "${config}/https-cert.pem",
locHTTPSKeyFile: "${config}/https-key.pem",
locDatabase: "${config}/index-v0.11.0.db",
locDatabase: "${config}/index-v0.13.0.db",
locLogFile: "${config}/syncthing.log", // -logfile on Windows
locCsrfTokens: "${config}/csrftokens.txt",
locPanicLog: "${config}/panic-${timestamp}.log",

View File

@@ -17,7 +17,6 @@ import (
"net"
"net/http"
_ "net/http/pprof"
"net/url"
"os"
"os/signal"
"path/filepath"
@@ -40,24 +39,25 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/relay"
"github.com/syncthing/syncthing/lib/symlinks"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/util"
"github.com/thejerf/suture"
)
var (
Version = "unknown-dev"
Codename = "Beryllium Bedbug"
BuildStamp = "0"
BuildDate time.Time
BuildHost = "unknown"
BuildUser = "unknown"
IsRelease bool
IsBeta bool
LongVersion string
Version = "unknown-dev"
Codename = "Copper Cockroach"
BuildStamp = "0"
BuildDate time.Time
BuildHost = "unknown"
BuildUser = "unknown"
IsRelease bool
IsBeta bool
LongVersion string
allowedVersionExp = regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z0-9]+)*(\.\d+)*(\+\d+-g[0-9a-f]+)?(-[^\s]+)?$`)
)
const (
@@ -89,9 +89,8 @@ const (
func init() {
if Version != "unknown-dev" {
// If not a generic dev build, version string should come from git describe
exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-[a-z0-9]+)*(\+\d+-g[0-9a-f]+)?(-dirty)?$`)
if !exp.MatchString(Version) {
l.Fatalf("Invalid version string %q;\n\tdoes not match regexp %v", Version, exp)
if !allowedVersionExp.MatchString(Version) {
l.Fatalf("Invalid version string %q;\n\tdoes not match regexp %v", Version, allowedVersionExp)
}
}
@@ -115,7 +114,6 @@ func init() {
var (
myID protocol.DeviceID
stop = make(chan int)
cert tls.Certificate
lans []*net.IPNet
)
@@ -340,7 +338,7 @@ func main() {
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
l.Okln("Upgraded from", options.upgradeTo)
l.Infoln("Upgraded from", options.upgradeTo)
return
}
@@ -464,14 +462,14 @@ func performUpgrade(release upgrade.Release) {
if err != nil {
l.Fatalln("Upgrade:", err)
}
l.Okf("Upgraded to %q", release.Tag)
l.Infof("Upgraded to %q", release.Tag)
} else {
l.Infoln("Attempting upgrade through running Syncthing...")
err = upgradeViaRest()
if err != nil {
l.Fatalln("Upgrade:", err)
}
l.Okln("Syncthing upgrading")
l.Infoln("Syncthing upgrading")
os.Exit(exitUpgrading)
}
}
@@ -556,10 +554,6 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
// We reinitialize the predictable RNG with our device ID, to get a
// sequence that is always the same but unique to this syncthing instance.
predictableRandom.Seed(seedFromBytes(cert.Certificate[0]))
myID = protocol.NewDeviceID(cert.Certificate[0])
l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5]))
@@ -640,6 +634,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
dbFile := locations[locDatabase]
ldb, err := db.Open(dbFile)
if err != nil {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
}
@@ -660,13 +655,6 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
// Pack and optimize the database
if err := ldb.Compact(); err != nil {
// I don't think this is fatal, but who knows. If it is, we'll surely
// get an error when trying to write to the db later.
l.Infoln("Compacting database:", err)
}
m := model.NewModel(cfg, myID, myDeviceName(cfg), "syncthing", Version, ldb, protectedFiles)
cfg.Subscribe(m)
@@ -696,63 +684,25 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
m.Index(device, folderCfg.ID, nil, 0, nil)
}
// Routine to pull blocks from other devices to synchronize the local
// folder. Does not run when we are in read only (publish only) mode.
if folderCfg.ReadOnly {
m.StartFolderRO(folderCfg.ID)
} else {
m.StartFolderRW(folderCfg.ID)
}
m.StartFolder(folderCfg.ID)
}
mainService.Add(m)
// The default port we announce, possibly modified by setupUPnP next.
uri, err := url.Parse(opts.ListenAddress[0])
if err != nil {
l.Fatalf("Failed to parse listen address %s: %v", opts.ListenAddress[0], err)
}
addr, err := net.ResolveTCPAddr("tcp", uri.Host)
if err != nil {
l.Fatalln("Bad listen address:", err)
}
// The externalAddr tracks our external addresses for discovery purposes.
var addrList *addressLister
// Start UPnP
if opts.UPnPEnabled {
upnpService := newUPnPService(cfg, addr.Port)
mainService.Add(upnpService)
// The external address tracker needs to know about the UPnP service
// so it can check for an external mapped port.
addrList = newAddressLister(upnpService, cfg)
} else {
addrList = newAddressLister(nil, cfg)
}
// Start relay management
var relayService *relay.Service
if opts.RelaysEnabled {
relayService = relay.NewService(cfg, tlsCfg)
mainService.Add(relayService)
}
// Start discovery
cachedDiscovery := discover.NewCachingMux()
mainService.Add(cachedDiscovery)
// Start connection management
connectionsService := connections.NewService(cfg, myID, m, tlsCfg, cachedDiscovery, bepProtocolName, tlsDefaultCommonName, lans)
mainService.Add(connectionsService)
if cfg.Options().GlobalAnnEnabled {
for _, srv := range cfg.GlobalDiscoveryServers() {
l.Infoln("Using discovery server", srv)
gd, err := discover.NewGlobal(srv, cert, addrList, relayService)
gd, err := discover.NewGlobal(srv, cert, connectionsService)
if err != nil {
l.Warnln("Global discovery:", err)
continue
@@ -767,14 +717,14 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
if cfg.Options().LocalAnnEnabled {
// v4 broadcasts
bcd, err := discover.NewLocal(myID, fmt.Sprintf(":%d", cfg.Options().LocalAnnPort), addrList, relayService)
bcd, err := discover.NewLocal(myID, fmt.Sprintf(":%d", cfg.Options().LocalAnnPort), connectionsService)
if err != nil {
l.Warnln("IPv4 local discovery:", err)
} else {
cachedDiscovery.Add(bcd, 0, 0, ipv4LocalDiscoveryPriority)
}
// v6 multicasts
mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, addrList, relayService)
mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, connectionsService)
if err != nil {
l.Warnln("IPv6 local discovery:", err)
} else {
@@ -784,12 +734,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
// GUI
setupGUI(mainService, cfg, m, apiSub, cachedDiscovery, relayService, errors, systemLog, runtimeOptions)
// Start connection management
connectionService := connections.NewConnectionService(cfg, myID, m, tlsCfg, cachedDiscovery, relayService, bepProtocolName, tlsDefaultCommonName, lans)
mainService.Add(connectionService)
setupGUI(mainService, cfg, m, apiSub, cachedDiscovery, connectionsService, errors, systemLog, runtimeOptions)
if runtimeOptions.cpuProfile {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
@@ -815,7 +760,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
if opts.URUniqueID == "" {
// Previously the ID was generated from the node ID. We now need
// to generate a new one.
opts.URUniqueID = randomString(8)
opts.URUniqueID = util.RandomString(8)
cfg.SetOptions(opts)
cfg.Save()
}
@@ -851,7 +796,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
mainService.Stop()
l.Okln("Exiting")
l.Infoln("Exiting")
if runtimeOptions.cpuProfile {
pprof.StopCPUProfile()
@@ -971,7 +916,7 @@ func startAuditing(mainService *suture.Supervisor) {
l.Infoln("Audit log in", auditFile)
}
func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub *events.BufferedSubscription, discoverer *discover.CachingMux, relayService *relay.Service, errors, systemLog *logger.Recorder, runtimeOptions RuntimeOptions) {
func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService *connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) {
guiCfg := cfg.GUI()
if !guiCfg.Enabled {
@@ -982,7 +927,7 @@ func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Mode
l.Warnln("Insecure admin access is enabled.")
}
api, err := newAPIService(myID, cfg, runtimeOptions.assetDir, m, apiSub, discoverer, relayService, errors, systemLog)
api, err := newAPIService(myID, cfg, locations[locHTTPSCertFile], locations[locHTTPSKeyFile], runtimeOptions.assetDir, m, apiSub, discoverer, connectionsService, errors, systemLog)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}
@@ -1001,8 +946,9 @@ func defaultConfig(myName string) config.Configuration {
if !noDefaultFolder {
l.Infoln("Default folder created and/or linked to new config")
defaultFolder = config.NewFolderConfiguration("default", locations[locDefFolder])
folderID := util.RandomString(5) + "-" + util.RandomString(5)
defaultFolder = config.NewFolderConfiguration(folderID, locations[locDefFolder])
defaultFolder.Label = "Default Folder (" + folderID + ")"
defaultFolder.RescanIntervalS = 60
defaultFolder.MinDiskFreePct = 1
defaultFolder.Devices = []config.FolderDeviceConfiguration{{DeviceID: myID}}
@@ -1031,7 +977,15 @@ func defaultConfig(myName string) config.Configuration {
if err != nil {
l.Fatalln("get free port (BEP):", err)
}
newCfg.Options.ListenAddress = []string{fmt.Sprintf("tcp://0.0.0.0:%d", port)}
if port == 22000 {
newCfg.Options.ListenAddresses = []string{"default"}
} else {
newCfg.Options.ListenAddresses = []string{
fmt.Sprintf("tcp://%s", net.JoinHostPort("0.0.0.0", strconv.Itoa(port))),
"dynamic+https://relays.syncthing.net/endpoint",
}
}
return newCfg
}
@@ -1170,12 +1124,13 @@ func autoUpgrade(cfg *config.Wrapper) {
// suitable time after they have gone out of fashion.
func cleanConfigDirectory() {
patterns := map[string]time.Duration{
"panic-*.log": 7 * 24 * time.Hour, // keep panic logs for a week
"audit-*.log": 7 * 24 * time.Hour, // keep audit logs for a week
"index": 14 * 24 * time.Hour, // keep old index format for two weeks
"config.xml.v*": 30 * 24 * time.Hour, // old config versions for a month
"*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
"backup-of-v0.8": 30 * 24 * time.Hour, // these neither
"panic-*.log": 7 * 24 * time.Hour, // keep panic logs for a week
"audit-*.log": 7 * 24 * time.Hour, // keep audit logs for a week
"index": 14 * 24 * time.Hour, // keep old index format for two weeks
"index*.converted": 14 * 24 * time.Hour, // keep old converted indexes for two weeks
"config.xml.v*": 30 * 24 * time.Hour, // old config versions for a month
"*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
"backup-of-v0.8": 30 * 24 * time.Hour, // these neither
}
for pat, dur := range patterns {

View File

@@ -175,3 +175,29 @@ func TestShortIDCheck(t *testing.T) {
t.Error("Should have gotten an error")
}
}
func TestAllowedVersions(t *testing.T) {
testcases := []struct {
ver string
allowed bool
}{
{"v0.13.0", true},
{"v0.12.11+22-gabcdef0", true},
{"v0.13.0-beta0", true},
{"v0.13.0-beta47", true},
{"v0.13.0-beta47+1-gabcdef0", true},
{"v0.13.0-beta.0", true},
{"v0.13.0-beta.47", true},
{"v0.13.0-beta.0+1-gabcdef0", true},
{"v0.13.0-beta.47+1-gabcdef0", true},
{"v0.13.0-some-weird-but-allowed-tag", true},
{"v0.13.0-allowed.to.do.this", true},
{"v0.13.0+not.allowed.to.do.this", false},
}
for i, c := range testcases {
if allowed := allowedVersionExp.MatchString(c.ver); allowed != c.allowed {
t.Errorf("%d: incorrect result %v != %v for %q", i, allowed, c.allowed, c.ver)
}
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol"
)
type mockedConfig struct {
gui config.GUIConfiguration
}
func (c *mockedConfig) GUI() config.GUIConfiguration {
return c.gui
}
func (c *mockedConfig) ListenAddresses() []string {
return nil
}
func (c *mockedConfig) Raw() config.Configuration {
return config.Configuration{}
}
func (c *mockedConfig) Options() config.OptionsConfiguration {
return config.OptionsConfiguration{}
}
func (c *mockedConfig) Replace(cfg config.Configuration) config.CommitResponse {
return config.CommitResponse{}
}
func (c *mockedConfig) Subscribe(cm config.Committer) {}
func (c *mockedConfig) Folders() map[string]config.FolderConfiguration {
return nil
}
func (c *mockedConfig) Devices() map[protocol.DeviceID]config.DeviceConfiguration {
return nil
}
func (c *mockedConfig) Save() error {
return nil
}

View File

@@ -0,0 +1,13 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
type mockedConnections struct{}
func (m *mockedConnections) Status() map[string]interface{} {
return nil
}

View File

@@ -0,0 +1,52 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"time"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/protocol"
)
type mockedCachingMux struct{}
// from suture.Service
func (m *mockedCachingMux) Serve() {
select {}
}
func (m *mockedCachingMux) Stop() {
}
// from events.Finder
func (m *mockedCachingMux) Lookup(deviceID protocol.DeviceID) (direct []string, err error) {
return nil, nil
}
func (m *mockedCachingMux) Error() error {
return nil
}
func (m *mockedCachingMux) String() string {
return "mockedCachingMux"
}
func (m *mockedCachingMux) Cache() map[protocol.DeviceID]discover.CacheEntry {
return nil
}
// from events.CachingMux
func (m *mockedCachingMux) Add(finder discover.Finder, cacheTime, negCacheTime time.Duration, priority int) {
}
func (m *mockedCachingMux) ChildErrors() map[string]error {
return nil
}

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import "github.com/syncthing/syncthing/lib/events"
type mockedEventSub struct{}
func (s *mockedEventSub) Since(id int, into []events.Event) []events.Event {
select {}
}

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"time"
"github.com/syncthing/syncthing/lib/logger"
)
type mockedLoggerRecorder struct{}
func (r *mockedLoggerRecorder) Since(t time.Time) []logger.Line {
return []logger.Line{
{
When: time.Now(),
Message: "Test message",
},
}
}
func (r *mockedLoggerRecorder) Clear() {}

View File

@@ -0,0 +1,116 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"time"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/stats"
)
type mockedModel struct{}
func (m *mockedModel) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{} {
return nil
}
func (m *mockedModel) Completion(device protocol.DeviceID, folder string) float64 {
return 0
}
func (m *mockedModel) Override(folder string) {}
func (m *mockedModel) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int) {
return nil, nil, nil, 0
}
func (m *mockedModel) NeedSize(folder string) (nfiles int, bytes int64) {
return 0, 0
}
func (m *mockedModel) ConnectionStats() map[string]interface{} {
return nil
}
func (m *mockedModel) DeviceStatistics() map[string]stats.DeviceStatistics {
return nil
}
func (m *mockedModel) FolderStatistics() map[string]stats.FolderStatistics {
return nil
}
func (m *mockedModel) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
return protocol.FileInfo{}, false
}
func (m *mockedModel) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) {
return protocol.FileInfo{}, false
}
func (m *mockedModel) ResetFolder(folder string) {
}
func (m *mockedModel) Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []model.Availability {
return nil
}
func (m *mockedModel) GetIgnores(folder string) ([]string, []string, error) {
return nil, nil, nil
}
func (m *mockedModel) SetIgnores(folder string, content []string) error {
return nil
}
func (m *mockedModel) PauseDevice(device protocol.DeviceID) {
}
func (m *mockedModel) ResumeDevice(device protocol.DeviceID) {}
func (m *mockedModel) DelayScan(folder string, next time.Duration) {}
func (m *mockedModel) ScanFolder(folder string) error {
return nil
}
func (m *mockedModel) ScanFolders() map[string]error {
return nil
}
func (m *mockedModel) ScanFolderSubs(folder string, subs []string) error {
return nil
}
func (m *mockedModel) BringToFront(folder, file string) {}
func (m *mockedModel) ConnectedTo(deviceID protocol.DeviceID) bool {
return false
}
func (m *mockedModel) GlobalSize(folder string) (nfiles, deleted int, bytes int64) {
return 0, 0, 0
}
func (m *mockedModel) LocalSize(folder string) (nfiles, deleted int, bytes int64) {
return 0, 0, 0
}
func (m *mockedModel) CurrentLocalVersion(folder string) (int64, bool) {
return 0, false
}
func (m *mockedModel) RemoteLocalVersion(folder string) (int64, bool) {
return 0, false
}
func (m *mockedModel) State(folder string) (string, time.Time, error) {
return "", time.Time{}, nil
}

View File

@@ -9,9 +9,7 @@ package main
import (
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture"
)
@@ -21,8 +19,8 @@ import (
type folderSummaryService struct {
*suture.Supervisor
cfg *config.Wrapper
model *model.Model
cfg configIntf
model modelIntf
stop chan struct{}
immediate chan string
@@ -35,7 +33,7 @@ type folderSummaryService struct {
lastEventReqMut sync.Mutex
}
func newFolderSummaryService(cfg *config.Wrapper, m *model.Model) *folderSummaryService {
func newFolderSummaryService(cfg configIntf, m modelIntf) *folderSummaryService {
service := &folderSummaryService{
Supervisor: suture.NewSimple("folderSummaryService"),
cfg: cfg,

1
cmd/syncthing/testdata/default/a vendored Normal file
View File

@@ -0,0 +1 @@
overridden-default

1
cmd/syncthing/testdata/default/b vendored Normal file
View File

@@ -0,0 +1 @@
overridden-default

1
cmd/syncthing/testdata/default/d vendored Normal file
View File

@@ -0,0 +1 @@
overridden-default

1
cmd/syncthing/testdata/foo/a vendored Normal file
View File

@@ -0,0 +1 @@
overridden-foo

View File

@@ -1,132 +0,0 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"fmt"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/upnp"
)
// The UPnP service runs a loop for discovery of IGDs (Internet Gateway
// Devices) and setup/renewal of a port mapping.
type upnpService struct {
cfg *config.Wrapper
localPort int
extPort int
extPortMut sync.Mutex
stop chan struct{}
}
func newUPnPService(cfg *config.Wrapper, localPort int) *upnpService {
return &upnpService{
cfg: cfg,
localPort: localPort,
extPortMut: sync.NewMutex(),
}
}
func (s *upnpService) Serve() {
foundIGD := true
s.stop = make(chan struct{})
for {
igds := upnp.Discover(time.Duration(s.cfg.Options().UPnPTimeoutS) * time.Second)
if len(igds) > 0 {
foundIGD = true
s.extPortMut.Lock()
oldExtPort := s.extPort
s.extPortMut.Unlock()
newExtPort := s.tryIGDs(igds, oldExtPort)
s.extPortMut.Lock()
s.extPort = newExtPort
s.extPortMut.Unlock()
} else if foundIGD {
// Only print a notice if we've previously found an IGD or this is
// the first time around.
foundIGD = false
l.Infof("No UPnP device detected")
}
d := time.Duration(s.cfg.Options().UPnPRenewalM) * time.Minute
if d == 0 {
// We always want to do renewal so lets just pick a nice sane number.
d = 30 * time.Minute
}
select {
case <-s.stop:
return
case <-time.After(d):
}
}
}
func (s *upnpService) Stop() {
close(s.stop)
}
func (s *upnpService) ExternalPort() int {
s.extPortMut.Lock()
port := s.extPort
s.extPortMut.Unlock()
return port
}
func (s *upnpService) tryIGDs(igds []upnp.IGD, prevExtPort int) int {
// Lets try all the IGDs we found and use the first one that works.
// TODO: Use all of them, and sort out the resulting mess to the
// discovery announcement code...
for _, igd := range igds {
extPort, err := s.tryIGD(igd, prevExtPort)
if err != nil {
l.Warnf("Failed to set UPnP port mapping: external port %d on device %s.", extPort, igd.FriendlyIdentifier())
continue
}
if extPort != prevExtPort {
l.Infof("New UPnP port mapping: external port %d to local port %d.", extPort, s.localPort)
events.Default.Log(events.ExternalPortMappingChanged, map[string]int{"port": extPort})
}
l.Debugf("Created/updated UPnP port mapping for external port %d on device %s.", extPort, igd.FriendlyIdentifier())
return extPort
}
return 0
}
func (s *upnpService) tryIGD(igd upnp.IGD, suggestedPort int) (int, error) {
var err error
leaseTime := s.cfg.Options().UPnPLeaseM * 60
if suggestedPort != 0 {
// First try renewing our existing mapping.
name := fmt.Sprintf("syncthing-%d", suggestedPort)
err = igd.AddPortMapping(upnp.TCP, suggestedPort, s.localPort, name, leaseTime)
if err == nil {
return suggestedPort, nil
}
}
for i := 0; i < 10; i++ {
// Then try up to ten random ports.
extPort := 1024 + predictableRandom.Intn(65535-1024)
name := fmt.Sprintf("syncthing-%d", extPort)
err = igd.AddPortMapping(upnp.TCP, extPort, s.localPort, name, leaseTime)
if err == nil {
return extPort, nil
}
}
return 0, err
}

View File

@@ -16,6 +16,7 @@ import (
"net/http"
"runtime"
"sort"
"strings"
"time"
"github.com/syncthing/syncthing/lib/config"
@@ -79,7 +80,7 @@ func (m *usageReportingManager) String() string {
// reportData returns the data to be sent in a usage report. It's used in
// various places, so not part of the usageReportingManager object.
func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
func reportData(cfg configIntf, m modelIntf) map[string]interface{} {
res := make(map[string]interface{})
res["urVersion"] = usageReportVersion
res["uniqueID"] = cfg.Options().URUniqueID
@@ -133,7 +134,7 @@ func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
for _, cfg := range cfg.Folders() {
rescanIntvs = append(rescanIntvs, cfg.RescanIntervalS)
if cfg.ReadOnly {
if cfg.Type == config.FolderTypeReadOnly {
folderUses["readonly"]++
}
if cfg.IgnorePerms {
@@ -203,16 +204,16 @@ func reportData(cfg *config.Wrapper, m *model.Model) map[string]interface{} {
}
defaultRelayServers, otherRelayServers := 0, 0
for _, addr := range cfg.Options().RelayServers {
switch addr {
case "dynamic+https://relays.syncthing.net/endpoint":
for _, addr := range cfg.ListenAddresses() {
switch {
case addr == "dynamic+https://relays.syncthing.net/endpoint":
defaultRelayServers++
default:
case strings.HasPrefix(addr, "relay://") || strings.HasPrefix(addr, "dynamic+http"):
otherRelayServers++
}
}
res["relays"] = map[string]interface{}{
"enabled": cfg.Options().RelaysEnabled,
"enabled": defaultRelayServers+otherAnnounceServers > 0,
"defaultServers": defaultRelayServers,
"otherServers": otherRelayServers,
}

View File

@@ -8,7 +8,6 @@ package main
import (
"fmt"
"strings"
"github.com/syncthing/syncthing/lib/events"
)
@@ -147,15 +146,12 @@ func (s *verboseService) formatEvent(ev events.Event) string {
data := ev.Data.(map[string]string)
device := data["device"]
return fmt.Sprintf("Device %v was resumed", device)
case events.ExternalPortMappingChanged:
data := ev.Data.(map[string]int)
port := data["port"]
return fmt.Sprintf("External port mapping changed; new port is %d.", port)
case events.RelayStateChanged:
data := ev.Data.(map[string][]string)
newRelays := data["new"]
return fmt.Sprintf("Relay state changed; connected relay(s) are %s.", strings.Join(newRelays, ", "))
case events.ListenAddressesChanged:
data := ev.Data.(map[string]interface{})
address := data["address"]
lan := data["lan"]
wan := data["wan"]
return fmt.Sprintf("Listen address %s resolution has changed: lan addresses: %s wan addresses: %s", address, lan, wan)
case events.LoginAttempt:
data := ev.Data.(map[string]interface{})
username := data["username"].(string)

View File

@@ -1,4 +1,4 @@
syncthing ({{.version}}); urgency=medium
{{.name}} ({{.version}}); urgency=medium
* Packaging of {{.version}}.

View File

View File

View File

View File

@@ -1,6 +1,6 @@
[Unit]
Description=Syncthing - Open Source Continuous File Synchronization for %I
Documentation=http://docs.syncthing.net/
Documentation=man:syncthing(1)
After=network.target
Wants=syncthing-inotify@.service

View File

@@ -1,6 +1,6 @@
[Unit]
Description=Syncthing - Open Source Continuous File Synchronization
Documentation=http://docs.syncthing.net/
Documentation=man:syncthing(1)
After=network.target
Wants=syncthing-inotify.service

View File

@@ -1,475 +0,0 @@
/*
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
*/
body {
padding-bottom: 70px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
h1, h2, h3, h4, h5 {
font-family: "Raleway", "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.25;
}
ul+h5 {
margin-top: 1.5em;
}
#content {
margin-bottom: 50px;
}
.panel-progress {
background: #3498db;
height: 3px;
left: 0;
position: absolute;
top: 0;
display: block;
}
.panel-title {
position: relative;
text-overflow: ellipsis;
overflow: hidden;
}
identicon {
display: inline-block;
position: relative;
width: 1em;
height: 1em;
line-height: 1;
margin-right: 5px;
}
.identicon {
width: 1em;
height: 1em;
}
.identicon rect {
fill: #333;
}
.checkbox {
margin-top: 0px;
}
.checkbox input[type="checkbox"], .radio input[type="radio"] {
float: none; /* issue #1197 */
}
.popover {
max-width: none;
min-width: 250px;
}
.panel-heading .fa, .modal-header .fa {
margin-right: 10px;
}
.panel-heading {
position: relative;
overflow: hidden;
}
.text-monospace {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}
.table-condensed>thead>tr>th, .table-condensed>tbody>tr>th, .table-condensed>tfoot>tr>th, .table-condensed>thead>tr>td, .table-condensed>tbody>tr>td, .table-condensed>tfoot>tr>td {
border-top: none;
}
.logo {
margin: 0;
padding: 0;
top: -5px;
position: relative;
}
.list-no-bullet {
list-style-type: none
}
.li-column {
display: inline-block;
min-width: 7em;
margin-right: 1em;
background-color: rgb(236, 240, 241);
border-radius: 3px;
padding: 1px 4px;
margin: 2px 2px;
}
.li-column span.data {
margin-left: 0.5em;
min-width: 10em;
text-align: right;
display: inline-block;
}
.ng-cloak {
display: none !important;
}
.table th {
white-space: nowrap;
font-weight: 400;
}
.table td {
padding-left: 20px !important;
}
.table td.small-data {
white-space: nowrap;
}
table.table-condensed {
table-layout: fixed;
}
table.table-condensed td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (max-width:767px) {
table.table-condensed td {
/* for mobile phones to allow linebreaks in long repro folder/shared with
* columns. */
white-space: normal;
}
}
.navbar-right {
/* to align with panel */
padding-right: 15px;
}
/**
* Menu for select language
*/
@media (min-width:480px) and (max-width:649px) {
*[language-select] > .dropdown-menu {
width: 230px;
}
}
@media (min-width:650px) {
*[language-select] > .dropdown-menu > li {
width: 50%;
float: left;
}
*[language-select] > .dropdown-menu {
width: 440px;
}
}
@media (max-width:479px) {
.dropdown-menu {
padding-top: 55px;
}
nav .dropdown-toggle {
font-size: 14px;
}
.dropdown-toggle {
float: left;
}
.navbar-brand {
padding-left: 0;
padding-top: 16px;
}
.navbar-nav .open .dropdown-menu > li > a {
padding: 12px 15px 12px 25px;
}
}
.panel-body .table-condensed {
margin-bottom: 0;
}
.dl-horizontal.dl-narrow dt {
width: 40px;
}
.dl-horizontal.dl-narrow dd {
margin-left: 60px;
}
/**
* Progress bars with centered text
*/
.progress {
margin-bottom: 0px;
position: relative;
}
.progress span.frontal {
text-align: center;
position: absolute;
display: block;
width: 100%;
}
.three-columns {
-webkit-column-count: 3;
-moz-column-count: 3;
column-count: 3;
}
.two-columns {
-webkit-column-count: 2;
-moz-column-count: 2;
column-count: 2;
}
ul.three-columns li, ul.two-columns li {
padding-left: 0.5em;
text-indent: -0.5em;
}
/** Footer nav on small devices **/
@media (max-width: 1199px) {
body {
padding-bottom: 0;
}
.navbar-fixed-bottom {
position: static;
}
}
/**
Dark theme customizations start here.
Author: alessandro.g89
Source: https://userstyles.org/styles/122502/syncthing-dark
**/
body {
color: #aaa !important;
background-color: black !important;
}
a:hover,a:focus,a.focus{
outline: none !important;
}
/* navbar */
.navbar {
background-color: #333 !important;
border-color: #333 !important;
border-width: 2px !important;
}
.navbar-text, .dropdown>a, .dropdown-menu>li>a, .hidden-xs>a, .navbar-link {
color: #aaa !important;
}
.dropdown-menu {
border-color: #333 !important;
border-width: 2px !important;
background-color: #222 !important;
}
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus {
color: #fff !important;
background-color: #333 !important;
}
.open>.dropdown-toggle, .dropdown-toggle:hover {
border-color: #333 !important;
background-color: #222 !important;
}
.divider {
background-color: #333 !important;
height: 2px !important;
}
li.hidden-xs:hover, .navbar-link:hover, .navbar-link:focus {
outline: none !important;
border-color: #333 !important;
background-color: #222 !important;
}
.dropdown-menu>.active>a {
color: #fff !important;
background-color: #217dbb !important;
}
/* main panel */
.panel {
background-color: #111 !important;
border-width: 2px !important;
}
.panel-default {
border-color: #222 !important;
}
.panel-default>.panel-heading {
color: #aaa !important;
border-color: #222 !important;
background-color: #222 !important;
}
.panel-footer {
background-color: #111 !important;
border-width: 0 !important;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #181818 !important;
}
.panel-group .panel-heading+.panel-collapse>.panel-body, .panel-group .panel-heading+.panel-collapse>.list-group {
border-top: 1px solid #222 !important;
}
.identicon>rect {
fill: #aaa !important;
}
/* buttons */
.btn {
border-radius: 3px !important;
border-width: 0px !important;
}
.btn:hover, .btn:focus, .btn.focus {
outline: none !important;
}
.btn-default {
color: #aaa !important;
background-color: #333 !important;
}
.btn-default:hover, .btn-default:focus, .btn-default.focus {
color: #fff !important;
background-color: #484848 !important;
}
.btn-primary {
background-color: #217dbb !important;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary.focus {
background-color: #3498db !important;
}
.btn-warning {
background-color: #c29d0b !important;
}
.btn-warning:hover, .btn-warning:focus, .btn-warning.focus {
background-color: #f1c40f !important;
}
.btn-danger {
background-color: #d62c1a !important;
}
.btn-danger:hover, .btn-danger:focus, .btn-danger.focus {
background-color: #e74c3c !important;
}
/* modal dialogs */
.modal-header {
border-color: #222 !important;
background-color: #222;
}
.modal-content {
border-color: #666 !important;
border-width: 2px !important;
background-color: #111 !important;
}
.modal-footer {
border-color: #111 !important;
background-color: #111 !important;
}
.alert-warning {
background-color: #c29d0b !important;
}
.alert-danger {
background-color: #d62c1a !important;
}
.help-block {
color: #aaa !important;
}
.form-control {
color: #aaa !important;
border-color: #444 !important;
background-color: black !important;
}
code.ng-binding{
color: #f99 !important;
background-color: #444 !important;
}
.well, .form-control[readonly="readonly"] { /* read-only fields*/
color: #666 !important;
border-color: #444 !important;
background-color: #111 !important;
}
/* buttons for pagination */
.pagination>li>a, .pagination>li>span {
background-color: #333 !important;
border-color: #484848 !important;
}
.pagination>li>a:hover, .pagination>li>a:focus, .pagination>li>a.focus {
background-color: #484848 !important;
}
/* progess bars */
.progress-bar {
background-color: #217dbb !important;
}
.progress-bar-success {
background-color: #0A8522 !important;
}
.progress-bar-info {
background-color: #9b59b6 !important;
}
.progress-bar-warning {
background-color: #c29d0b !important;
}
.progress-bar-danger {
background-color: #d62c1a !important;
}

View File

@@ -0,0 +1,217 @@
/**
Dark theme
Author: alessandro.g89
Source: https://userstyles.org/styles/122502/syncthing-dark
**/
body {
color: #aaa !important;
background-color: black !important;
}
a:hover,a:focus,a.focus{
outline: none !important;
}
/* navbar */
.navbar {
background-color: #333 !important;
border-color: #333 !important;
border-width: 2px !important;
}
.navbar-text, .dropdown>a, .dropdown-menu>li>a, .hidden-xs>a, .navbar-link {
color: #aaa !important;
}
.dropdown-menu {
border-color: #333 !important;
border-width: 2px !important;
background-color: #222 !important;
}
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus {
color: #fff !important;
background-color: #333 !important;
}
.open>.dropdown-toggle, .dropdown-toggle:hover {
border-color: #333 !important;
background-color: #222 !important;
}
.divider {
background-color: #333 !important;
height: 2px !important;
}
li.hidden-xs:hover, .navbar-link:hover, .navbar-link:focus {
outline: none !important;
border-color: #333 !important;
background-color: #222 !important;
}
.dropdown-menu>.active>a {
color: #fff !important;
background-color: #217dbb !important;
}
/* main panel */
.panel {
background-color: #111 !important;
border-width: 2px !important;
}
.panel-default {
border-color: #222 !important;
}
.panel-default>.panel-heading {
color: #aaa !important;
border-color: #222 !important;
background-color: #222 !important;
}
.panel-footer {
background-color: #111 !important;
border-width: 0 !important;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #181818 !important;
}
.panel-group .panel-heading+.panel-collapse>.panel-body, .panel-group .panel-heading+.panel-collapse>.list-group {
border-top: 1px solid #222 !important;
}
.identicon>rect {
fill: #aaa !important;
}
/* buttons */
.btn {
border-radius: 3px !important;
border-width: 0px !important;
}
.btn:hover, .btn:focus, .btn.focus {
outline: none !important;
}
.btn-default {
color: #aaa !important;
background-color: #333 !important;
}
.btn-default:hover, .btn-default:focus, .btn-default.focus {
color: #fff !important;
background-color: #484848 !important;
}
.btn-primary {
background-color: #217dbb !important;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary.focus {
background-color: #3498db !important;
}
.btn-warning {
background-color: #c29d0b !important;
}
.btn-warning:hover, .btn-warning:focus, .btn-warning.focus {
background-color: #f1c40f !important;
}
.btn-danger {
background-color: #d62c1a !important;
}
.btn-danger:hover, .btn-danger:focus, .btn-danger.focus {
background-color: #e74c3c !important;
}
/* modal dialogs */
.modal-header {
border-color: #222 !important;
background-color: #222;
}
.modal-content {
border-color: #666 !important;
border-width: 2px !important;
background-color: #111 !important;
}
.modal-footer {
border-color: #111 !important;
background-color: #111 !important;
}
.alert-warning {
background-color: #c29d0b !important;
}
.alert-danger {
background-color: #d62c1a !important;
}
.help-block {
color: #aaa !important;
}
.form-control {
color: #aaa !important;
border-color: #444 !important;
background-color: black !important;
}
code.ng-binding{
color: #f99 !important;
background-color: #444 !important;
}
.well, .form-control[readonly="readonly"], .popover { /* read-only fields*/
color: #666 !important;
border-color: #444 !important;
background-color: #111 !important;
}
/* buttons for pagination */
.pagination>li>a, .pagination>li>span {
background-color: #333 !important;
border-color: #484848 !important;
}
.pagination>li>a:hover, .pagination>li>a:focus, .pagination>li>a.focus {
background-color: #484848 !important;
}
/* progress bars */
.progress-bar {
background-color: #217dbb !important;
}
.progress-bar-success {
background-color: #0A8522 !important;
}
.progress-bar-info {
background-color: #9b59b6 !important;
}
.progress-bar-warning {
background-color: #c29d0b !important;
}
.progress-bar-danger {
background-color: #d62c1a !important;
}

View File

@@ -0,0 +1,12 @@
.dev-top-bar{
display: none;
background-color: yellow;
}
.dev-error .hasCount{
background-color: red;
}
.dev-warn .hasCount{
background-color: yellow;
}

View File

@@ -26,7 +26,6 @@ ul+h5 {
}
.panel-progress {
background: #3498db;
height: 3px;
left: 0;
position: absolute;
@@ -40,7 +39,7 @@ ul+h5 {
overflow: hidden;
}
.panel-title a:hover {
text-decoration: none;
text-decoration: none;
}
identicon {
@@ -57,10 +56,6 @@ identicon {
height: 1em;
}
.identicon rect {
fill: #333;
}
.checkbox {
margin-top: 0px;
}
@@ -74,6 +69,10 @@ identicon {
min-width: 250px;
}
.tooltip {
word-wrap:break-word;
}
.panel-heading .fa, .modal-header .fa {
margin-right: 10px;
}
@@ -99,15 +98,13 @@ identicon {
}
.list-no-bullet {
list-style-type: none
list-style-type: none;
}
.li-column {
display: inline-block;
min-width: 7em;
margin-right: 1em;
background-color: rgb(236, 240, 241);
border-radius: 3px;
padding: 1px 4px;
margin: 2px 2px;
}
@@ -143,64 +140,37 @@ table.table-condensed td {
text-overflow: ellipsis;
white-space: nowrap;
}
@media (max-width:767px) {
table.table-condensed td {
/* for mobile phones to allow linebreaks in long repro folder/shared with
* columns. */
white-space: normal;
}
table.table-condensed td.no-overflow-ellipse {
white-space: normal;
}
.folder-advanced {
padding: 1rem;
margin-bottom: 15px;
}
.folder-advanced-toggle {
cursor: pointer;
}
.folder-advanced-toggle .collapse,
.folder-advanced-toggle.collapsed .expand {
display: inline-block;
}
.folder-advanced-toggle.collapsed .collapse,
.folder-advanced-toggle .expand{
display: none;
}
.nav>li{
float: left;
}
.navbar-right {
/* to align with panel */
padding-right: 15px;
}
/**
* Menu for select language
*/
@media (min-width:480px) and (max-width:649px) {
*[language-select] > .dropdown-menu {
width: 230px;
}
}
@media (min-width:650px) {
*[language-select] > .dropdown-menu > li {
width: 50%;
float: left;
}
*[language-select] > .dropdown-menu {
width: 440px;
}
}
@media (max-width:479px) {
.dropdown-menu {
padding-top: 55px;
}
nav .dropdown-toggle {
font-size: 14px;
}
.dropdown-toggle {
float: left;
}
.navbar-brand {
padding-left: 0;
padding-top: 16px;
}
.navbar-nav .open .dropdown-menu > li > a {
padding: 12px 15px 12px 25px;
}
}
.panel-body .table-condensed {
margin-bottom: 0;
}
@@ -247,7 +217,6 @@ ul.three-columns li, ul.two-columns li {
}
/** Footer nav on small devices **/
@media (max-width: 1199px) {
/* Stay at the end of the page, with space reserved for the footer
usually taking up two rows. */
@@ -277,4 +246,59 @@ ul.three-columns li, ul.two-columns li {
.navbar-fixed-bottom {
position: relative;
}
.nav>li {
float:right;
}
table.table-condensed td {
/* for mobile phones to allow linebreaks in long repro folder/shared with
* columns. */
white-space: normal;
}
}
@media (min-width:650px) {
*[language-select] > .dropdown-menu > li {
width: 50%;
float: left;
}
*[language-select] > .dropdown-menu {
width: 440px;
}
}
/**
* Menu for select language
*/
@media (min-width:480px) and (max-width:649px) {
*[language-select] > .dropdown-menu {
width: 230px;
}
}
@media (max-width:479px) {
.dropdown-menu {
padding-top: 55px;
}
nav .dropdown-toggle {
font-size: 1em;
}
.dropdown-toggle {
float: left;
}
.logo{
margin:auto;
}
.navbar-nav .open .dropdown-menu > li > a {
padding: 12px 15px 12px 25px;
}
.navbar-fixed-bottom li{
width:100%;
}
}

View File

@@ -0,0 +1,21 @@
/*
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.panel-progress {
background: #3498db;
}
.identicon rect {
fill: #333;
}
.li-column {
background-color: rgb(236, 240, 241);
border-radius: 3px;
}

View File

@@ -39,6 +39,7 @@
"Danger!": "Danger!",
"Delete": "Delete",
"Deleted": "Deleted",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
"Device ID": "Device ID",
"Device Identification": "Device Identification",
"Device Name": "Device Name",
@@ -54,6 +55,7 @@
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Editing",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Enable Relaying",
"Enable UPnP": "Enable UPnP",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
@@ -228,6 +230,7 @@
"Version": "Version",
"Versions Path": "Versions Path",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a subdirectory of an existing folder \"{{otherFolder}}\".",
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
"Yes": "Yes",
@@ -236,5 +239,6 @@
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderLabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderLabel}}\" ({{folder}}).",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
}

View File

@@ -18,38 +18,44 @@
<link rel="mask-icon" href="assets/img/safari-pinned-tab.svg" color="#0882c8">
<title ng-bind="thisDeviceName() + ' | Syncthing'"></title>
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="vendor/bootstrap/css/bootstrap.css" rel="stylesheet">
<link href="assets/font/raleway.css" rel="stylesheet">
<link href="vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="vendor/font-awesome/css/font-awesome.css" rel="stylesheet">
<link href="assets/css/overrides.css" rel="stylesheet">
<link href="assets/css/theme.css" rel="stylesheet">
</head>
<body>
<script src="syncthing/development/logbar.js"></script>
<div ng-if="version.isDevelopmentVersion" ng-include="'syncthing/development/logbar.html'"></div>
<!-- Top bar -->
<nav class="navbar navbar-top navbar-default" role="navigation">
<div class="container">
<span class="navbar-brand"><img class="logo" src="assets/img/logo-horizontal.svg" height="32" width="117"/></span>
<span class="navbar-brand">
<img class="logo hidden-xs" src="assets/img/logo-horizontal.svg" height="32" width="117"/>
<img class="logo hidden visible-xs" src="assets/img/favicon.png" height="32"/>
</span>
<p class="navbar-text hidden-xs" ng-class="{'hidden-sm':upgradeInfo && upgradeInfo.newer}">{{thisDeviceName()}}</p>
<ul class="nav navbar-nav navbar-right">
<li ng-if="upgradeInfo && upgradeInfo.newer">
<li ng-if="upgradeInfo && upgradeInfo.newer" class="upgrade-newer">
<button type="button" class="btn navbar-btn btn-primary btn-sm" ng-click="upgrade()">
<span class="fa fa-arrow-circle-up"></span>&nbsp;<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
</button>
</li>
<li ng-if="upgradeInfo && upgradeInfo.majorNewer">
<li ng-if="upgradeInfo && upgradeInfo.majorNewer" class="upgrade-newer-major">
<button type="button" class="btn navbar-btn btn-danger btn-sm" ng-click="upgradeMajor()">
<span class="fa fa-arrow-circle-up"></span>&nbsp;<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
</button>
</li>
<li class="dropdown" language-select></li>
<li class="hidden-xs">
<a href="http://docs.syncthing.net/intro/gui.html" target="_blank">
<span class="fa fa-book"></span>&nbsp;<span translate>Help</span>
<li>
<a href="https://docs.syncthing.net/intro/gui.html" target="_blank">
<span class="fa fa-question-circle"></span>&nbsp;<span class="hidden-xs" translate>Help</span>
</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-cog"></span>&nbsp;<span translate>Actions</span> <span class="caret"></span></a>
<li class="dropdown action-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="fa fa-cog"></span>&nbsp;<span class="hidden-xs" translate>Actions</span> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="" ng-click="editSettings()"><span class="fa fa-fw fa-cog"></span>&nbsp;<span translate>Settings</span></a></li>
<li><a href="" ng-click="idDevice(thisDevice())"><span class="fa fa-fw fa-qrcode"></span>&nbsp;<span translate>Show ID</span></a></li>
@@ -58,7 +64,7 @@
<li><a href="" ng-click="restart()"><span class="fa fa-fw fa-refresh"></span>&nbsp;<span translate>Restart</span></a></li>
<li class="divider"></li>
<li class="visible-xs">
<a href="http://docs.syncthing.net/intro/gui.html" target="_blank">
<a href="https://docs.syncthing.net/intro/gui.html" target="_blank">
<span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span>
</a>
</li>
@@ -129,14 +135,14 @@
</div>
<div class="panel-body">
<p>
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
Device {%device%} ({%address%}) wants to connect. Add new device?
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}" translate-value-name="{{ event.data.name }}">
Device "{%name%}" ({%device%} at {%address%}) wants to connect. Add new device?
</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(device)">
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(device, event.data.name)">
<span class="fa fa-plus"></span>&nbsp;<span translate>Add Device</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)">
@@ -165,16 +171,19 @@
</div>
<div class="panel-body">
<p>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
<span ng-if="event.data.folderLabel.length == 0" translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span ng-if="event.data.folderLabel.length != 0" translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}" translate-value-folderlabel="{{ event.data.folderLabel }}">
{%device%} wants to share folder "{%folderlabel%}" ({%folder%}).
</span>
<span translate ng-if="folders[event.data.folder]">Share this folder?</span>
<span translate ng-if="!folders[event.data.folder]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.device)" ng-if="!folders[event.data.folder]">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.folderLabel, event.data.device)" ng-if="!folders[event.data.folder]">
<span class="fa fa-check"></span>&nbsp;<span translate>Add</span>
</button>
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]">
@@ -222,8 +231,11 @@
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id)}}%"></div>
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id)}}%"></div>
<h4 class="panel-title">
<span class="fa hidden-xs fa-fw" ng-class="[folder.readOnly ? 'fa-lock' : 'fa-folder']"></span>
<a href="#folder-{{$index}}">{{folder.id}}</a>
<span class="fa hidden-xs fa-fw" ng-class="[folder.type == 'readonly' ? 'fa-lock' : 'fa-folder']"></span>
<a href="#folder-{{$index}}">
<span ng-show="folder.label.length == 0">{{folder.id}}</span>
<span tooltip data-original-title="{{folder.id}}" ng-show="folder.label.length != 0">{{folder.label}}</span>
</a>
<span class="pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
@@ -248,9 +260,15 @@
<div class="panel-body">
<table class="table table-condensed table-striped">
<tbody>
<tr ng-show="folder.label != undefined && folder.label.length > 0">
<th><span class="fa fa-fw fa-folder-open"></span>&nbsp;<span translate>Folder ID</span></th>
<td class="text-right no-overflow-ellipse">{{folder.id}}</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-folder-open"></span>&nbsp;<span translate>Folder Path</span></th>
<td class="text-right" title="{{folder.path}}">{{folder.path}}</td>
<td class="text-right">
<span tooltip data-original-title="{{folder.path}}">{{folder.path}}</span>
</td>
</tr>
<tr ng-if="model[folder.id].invalid || model[folder.id].error">
<th><span class="fa fa-fw fa-exclamation-triangle"></span>&nbsp;<span translate>Error</span></th>
@@ -273,10 +291,10 @@
<tr ng-if="folderStatus(folder) === 'scanning' && scanRate(folder.id) > 0">
<th><span class="fa fa-fw fa-hourglass-2"></span>&nbsp;<span translate>Scan Time Remaining</span></th>
<td class="text-right">
<span title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span>
<span tooltip data-original-title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span>
</td>
</tr>
<tr ng-if="!folder.readOnly && (folderStatus(folder) === 'outofsync' || hasFailedFiles(folder.id))">
<tr ng-if="folder.type != 'readonly' && (folderStatus(folder) === 'outofsync' || hasFailedFiles(folder.id))">
<th><span class="fa fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Failed Items</span></th>
<!-- Show the number of failed items as a link to bring up the list. -->
<td ng-if="hasFailedFiles(folder.id)" class="text-right">
@@ -287,10 +305,11 @@
<span class="fa fa-spinner fa-pulse"></span>
</td>
</tr>
<tr ng-if="folder.readOnly">
<th><span class="fa fa-fw fa-lock"></span>&nbsp;<span translate>Folder Master</span></th>
<tr ng-if="folder.type != 'readwrite'">
<th><span class="fa fa-fw fa-lock"></span>&nbsp;<span translate>Folder Type</span></th>
<td class="text-right">
<span translate>Yes</span>
<span ng-if="folder.type == 'readonly'" translate>Master</span>
<span ng-if="folder.type != 'readonly'">{{ folder.type.charAt(0).toUpperCase() + folder.type.slice(1) }}</span>
</td>
</tr>
<tr ng-if="model[folder.id].ignorePatterns">
@@ -333,10 +352,10 @@
<th><span class="fa fa-fw fa-share-alt"></span>&nbsp;<span translate>Shared With</span></th>
<td class="text-right">{{sharesFolder(folder)}}</td>
</tr>
<tr ng-if="!folder.readOnly && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename">
<tr ng-if="folder.type != 'readonly' && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename">
<th><span class="fa fa-fw fa-exchange"></span>&nbsp;<span translate>Last File Received</span></th>
<td class="text-right">
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
<span tooltip data-original-title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
<span translate ng-if="!folderStats[folder.id].lastFile.deleted">Updated</span>
<span translate ng-if="folderStats[folder.id].lastFile.deleted">Deleted</span>
{{folderStats[folder.id].lastFile.filename | basename}}
@@ -347,7 +366,7 @@
</table>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="override(folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.readOnly">
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="override(folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.type == 'readonly'">
<span class="fa fa-arrow-circle-up"></span>&nbsp;<span translate>Override Changes</span>
</button>
<span class="pull-right">
@@ -411,6 +430,19 @@
<th><span class="fa fa-fw fa-tachometer"></span>&nbsp;<span translate>CPU Utilization</span></th>
<td class="text-right">{{system.cpuPercent | alwaysNumber | natural:1}}%</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-sitemap"></span>&nbsp;<span translate>Listeners</span></th>
<td class="text-right">
<span ng-if="listenersFailed.length == 0" class="data text-success">
<span>{{listenersTotal}}/{{listenersTotal}}</span>
</span>
<span ng-if="listenersFailed.length != 0" class="data" ng-class="{'text-danger': listenersFailed.length == listenersTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-html="true" data-content="{{listenersFailed.join('<br>\n')}}">
{{listenersTotal-listenersFailed.length}}/{{listenersTotal}}
</span>
</span>
</td>
</tr>
<tr ng-if="system.discoveryEnabled">
<th><span class="fa fa-fw fa-map-signs"></span>&nbsp;<span translate>Discovery</span></th>
<td class="text-right">
@@ -424,26 +456,15 @@
</span>
</td>
</tr>
<tr ng-if="system.relaysEnabled">
<th><span class="fa fa-fw fa-sitemap"></span>&nbsp;<span translate>Relays</span></th>
<td class="text-right">
<span ng-if="relaysFailed.length == 0" class="data text-success">
<span>{{relaysTotal}}/{{relaysTotal}}</span>
</span>
<span ng-if="relaysFailed.length != 0" class="data" ng-class="{'text-danger': relaysFailed.length == relaysTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-html="true" data-content="{{relaysFailed.join('<br>\n')}}">
{{relaysTotal-relaysFailed.length}}/{{relaysTotal}}
</span>
</span>
</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-clock-o"></span>&nbsp;<span translate>Uptime</span></th>
<td class="text-right">{{system.uptime | duration:"m"}}</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-tag"></span>&nbsp;<span translate>Version</span></th>
<td class="text-right" title="{{versionString()}}">{{versionString()}}</td>
<td class="text-right">
<span tooltip data-original-title="{{versionString()}}">{{versionString()}}</span>
</td>
</tr>
</tbody>
</table>
@@ -482,13 +503,17 @@
<th><span class="fa fa-fw fa-cloud-upload"></span>&nbsp;<span translate>Upload Rate</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].outbps | binary}}B/s ({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)</td>
</tr>
<tr>
<th>
<span class="fa fa-fw fa-link"></span>
<span translate ng-if="connections[deviceCfg.deviceID].type.indexOf('direct') == 0" >Address</span>
<span translate ng-if="connections[deviceCfg.deviceID].type.indexOf('relay') == 0" >Relayed via</span>
</th>
<td class="text-right">{{deviceAddr(deviceCfg)}}</td>
<tr ng-if="connections[deviceCfg.deviceID].connected">
<th><span class="fa fa-fw fa-link"></span>&nbsp<span translate>Address</span></th>
<td class="text-right">
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }}">
{{deviceAddr(deviceCfg)}}
</span>
</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID].connected && connections[deviceCfg.deviceID].type.indexOf('Relay') > -1" tooltip data-original-title="Connections via relays might be rate limited by the relay">
<th><span class="fa fa-fw fa-warning text-danger"></span>&nbsp<span translate>Connection Type</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].type}}</td>
</tr>
<tr ng-if="deviceCfg.compression != 'metadata'">
<th><span class="fa fa-fw fa-compress"></span>&nbsp;<span translate>Compression</span></th>
@@ -551,7 +576,7 @@
<div class="container">
<ul class="nav navbar-nav">
<li><a class="navbar-link" href="https://syncthing.net/" target="_blank"><span class="fa fa-home"></span>&nbsp;<span translate>Home page</span></a></li>
<li><a class="navbar-link" href="http://docs.syncthing.net/" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Documentation</span></a></li>
<li><a class="navbar-link" href="https://docs.syncthing.net/" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Documentation</span></a></li>
<li><a class="navbar-link" href="https://forum.syncthing.net" target="_blank"><span class="fa fa-question-circle"></span>&nbsp;<span translate>Support</span></a></li>
<li><a class="navbar-link" href="https://data.syncthing.net/" target="_blank"><span class="fa fa-bar-chart"></span>&nbsp;<span translate>Statistics</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases" target="_blank"><span class="fa fa-file-text-o"></span>&nbsp;<span translate>Changelog</span></a></li>
@@ -581,12 +606,12 @@
<div about-modal></div>
<!-- vendor scripts -->
<script src="vendor/jquery/jquery-2.0.3.min.js"></script>
<script src="vendor/jquery/jquery-2.2.2.js"></script>
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular/angular-translate.js"></script>
<script src="vendor/angular/angular-translate-loader-static-files.js"></script>
<script src="vendor/angular/angular-dirPagination.js"></script>
<script src="vendor/bootstrap/js/bootstrap.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.js"></script>
<!-- / vendor scripts -->
<!-- gui application code -->
@@ -606,11 +631,13 @@
<script src="syncthing/core/modalDirective.js"></script>
<script src="syncthing/core/naturalFilter.js"></script>
<script src="syncthing/core/networkErrorDialogDirective.js"></script>
<script src="syncthing/core/pathIsSubDirDirective.js"></script>
<script src="syncthing/core/popoverDirective.js"></script>
<script src="syncthing/core/restartingDialogDirective.js"></script>
<script src="syncthing/core/selectOnClickDirective.js"></script>
<script src="syncthing/core/shutdownDialogDirective.js"></script>
<script src="syncthing/core/syncthingController.js"></script>
<script src="syncthing/core/tooltipDirective.js"></script>
<script src="syncthing/core/uniqueFolderDirective.js"></script>
<script src="syncthing/core/upgradingDialogDirective.js"></script>
<script src="syncthing/core/validDeviceidDirective.js"></script>

View File

@@ -29,8 +29,6 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
return {
response: function onResponse(response) {
var headers = response.headers();
var responseVersion;
var deviceIdShort;
// angular template cache sends no headers
if(Object.keys(headers).length === 0) {
@@ -40,7 +38,7 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
if (!deviceId) {
deviceId = headers['x-syncthing-id'];
if (deviceId) {
deviceIdShort = deviceId.substring(0, 5);
var deviceIdShort = deviceId.substring(0, 5);
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIdShort;
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIdShort;
}
@@ -115,9 +113,14 @@ function decimals(val, num) {
}
function randomString(len) {
var i, result = '', chars = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-';
for (i = 0; i < len; i++) {
result += chars[Math.round(Math.random() * (chars.length - 1))];
var chars = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-';
return randomStringFromCharset(len, chars);
}
function randomStringFromCharset(len, charset) {
var result = '';
for (var i = 0; i < len; i++) {
result += charset[Math.round(Math.random() * (charset.length - 1))];
}
return result;
}
@@ -151,7 +154,6 @@ function debounce(func, wait) {
context = this;
args = arguments;
timestamp = Date.now();
var callNow = !timeout;
if (!timeout) {
timeout = setTimeout(later, wait);
result = func.apply(context, args);

View File

@@ -8,94 +8,10 @@
</h1>
<hr/>
<p translate>Copyright &copy; 2015 the following Contributors:</p>
<p translate>Copyright &copy; 2014-2016 the following Contributors:</p>
<div class="row">
<div class="col-md-12">
<ul class="list-unstyled three-columns" id="contributor-list">
<li class="auto-generated">Aaron Bieber</li>
<li class="auto-generated">Adam Piggott</li>
<li class="auto-generated">Alessandro G.</li>
<li class="auto-generated">Alexander Graf</li>
<li class="auto-generated">Anderson Mesquita</li>
<li class="auto-generated">Andrew Dunham</li>
<li class="auto-generated">Antony Male</li>
<li class="auto-generated">Arthur Axel fREW Schmidt</li>
<li class="auto-generated">Audrius Butkevicius</li>
<li class="auto-generated">Bart De Vries</li>
<li class="auto-generated">Ben Curthoys</li>
<li class="auto-generated">Ben Schulz</li>
<li class="auto-generated">Ben Sidhom</li>
<li class="auto-generated">Benny Ng</li>
<li class="auto-generated">Brandon Philips</li>
<li class="auto-generated">Brendan Long</li>
<li class="auto-generated">Brian R. Becker</li>
<li class="auto-generated">Caleb Callaway</li>
<li class="auto-generated">Carsten Hagemann</li>
<li class="auto-generated">Cathryne Linenweaver</li>
<li class="auto-generated">Chris Howie</li>
<li class="auto-generated">Chris Joel</li>
<li class="auto-generated">Colin Kennedy</li>
<li class="auto-generated">Daniel Bergmann</li>
<li class="auto-generated">Daniel Martí</li>
<li class="auto-generated">Denis A.</li>
<li class="auto-generated">Dennis Wilson</li>
<li class="auto-generated">Dominik Heidler</li>
<li class="auto-generated">Elias Jarlebring</li>
<li class="auto-generated">Emil Hessman</li>
<li class="auto-generated">Erik Meitner</li>
<li class="auto-generated">Federico Castagnini</li>
<li class="auto-generated">Felix Ableitner</li>
<li class="auto-generated">Felix Unterpaintner</li>
<li class="auto-generated">Francois-Xavier Gsell</li>
<li class="auto-generated">Frank Isemann</li>
<li class="auto-generated">Gilli Sigurdsson</li>
<li class="auto-generated">Jaakko Hannikainen</li>
<li class="auto-generated">Jacek Szafarkiewicz</li>
<li class="auto-generated">Jake Peterson</li>
<li class="auto-generated">Jakob Borg</li>
<li class="auto-generated">James Patterson</li>
<li class="auto-generated">Jaroslav Malec</li>
<li class="auto-generated">Jens Diemer</li>
<li class="auto-generated">Jochen Voss</li>
<li class="auto-generated">Johan Vromans</li>
<li class="auto-generated">Karol Różycki</li>
<li class="auto-generated">Ken'ichi Kamada</li>
<li class="auto-generated">Kevin Allen</li>
<li class="auto-generated">Laurent Etiemble</li>
<li class="auto-generated">Lode Hoste</li>
<li class="auto-generated">Lord Landon Agahnim</li>
<li class="auto-generated">Marc Laporte</li>
<li class="auto-generated">Marc Pujol</li>
<li class="auto-generated">Marcin Dziadus</li>
<li class="auto-generated">Mateusz Naściszewski</li>
<li class="auto-generated">Matt Burke</li>
<li class="auto-generated">Max Schulze</li>
<li class="auto-generated">Michael Jephcote</li>
<li class="auto-generated">Michael Ploujnikov</li>
<li class="auto-generated">Michael Tilli</li>
<li class="auto-generated">Nate Morrison</li>
<li class="auto-generated">Pascal Jungblut</li>
<li class="auto-generated">Peter Hoeg</li>
<li class="auto-generated">Philippe Schommers</li>
<li class="auto-generated">Phill Luby</li>
<li class="auto-generated">Piotr Bejda</li>
<li class="auto-generated">Ryan Sullivan</li>
<li class="auto-generated">Scott Klupfel</li>
<li class="auto-generated">Sergey Mishin</li>
<li class="auto-generated">Stefan Kuntz</li>
<li class="auto-generated">Stefan Tatschner</li>
<li class="auto-generated">Tim Abell</li>
<li class="auto-generated">Tobias Nygren</li>
<li class="auto-generated">Tomas Cerveny</li>
<li class="auto-generated">Tully Robinson</li>
<li class="auto-generated">Tyler Brazier</li>
<li class="auto-generated">Veeti Paananen</li>
<li class="auto-generated">Victor Buinsky</li>
<li class="auto-generated">Vil Brekin</li>
<li class="auto-generated">William A. Kennington III</li>
<li class="auto-generated">Wulf Weich</li>
<li class="auto-generated">Yannic A.</li>
</ul>
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Ben Schulz, Caleb Callaway, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Andrew Dunham, Antony Male, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Harte, Daniel Martí, David Rimmer, Denis A., Dennis Wilson, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jens Diemer, Jochen Voss, Johan Vromans, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Laurent Etiemble, Lord Landon Agahnim, Marc Laporte, Marc Pujol, Marcin Dziadus, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nate Morrison, Pascal Jungblut, Peter Hoeg, Phill Luby, Piotr Bejda, Scott Klupfel, Stefan Kuntz, Tim Abell, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Yannic A.
</div>
</div>
<hr/>

View File

@@ -4,7 +4,7 @@ angular.module('syncthing.core')
return {
restrict: 'EA',
template:
'<a ng-if="visible" href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true"><span class="fa fa-globe"></span>&nbsp;{{localesNames[currentLocale] || "English"}} <span class="caret"></span></a>'+
'<a ng-if="visible" href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true"><span class="fa fa-globe"></span><span class="hidden-xs">&nbsp;{{localesNames[currentLocale] || "English"}}</span> <span class="caret"></span></a>'+
'<ul ng-if="visible" class="dropdown-menu">'+
'<li ng-repeat="(i,name) in localesNames" ng-class="{active: i==currentLocale}">'+
'<a href="#" data-ng-click="changeLanguage(i)">{{name}}</a>'+

View File

@@ -0,0 +1,36 @@
angular.module('syncthing.core')
.directive('pathIsSubDir', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
// This function checks whether xdir is a subdirectory of ydir,
// e.g. it would return true if xdir = "/home/a", ydir = "/home/a/b".
function isSubDir(xdir, ydir) {
var xdirArr = xdir.split(scope.system.pathSeparator);
var ydirArr = ydir.split(scope.system.pathSeparator);
if (xdirArr.slice(-1).pop() === "") {
xdirArr = xdirArr.slice(0, -1);
}
if (xdirArr.length > ydirArr.length) {
return false;
}
return xdirArr.map(function(e, i) {
return xdirArr[i] === ydirArr[i];
}).every(function(e) { return e });
}
scope.pathIsSubFolder = false;
scope.otherFolder = "";
for (var folderID in scope.folders) {
if (isSubDir(scope.folders[folderID].path, viewValue)) {
scope.otherFolder = folderID;
scope.pathIsSubFolder = true;
break;
}
}
return viewValue;
});
}
};
});

View File

@@ -1,6 +1,6 @@
angular.module('syncthing.core')
.config(function($locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$locationProvider.html5Mode({enabled: true, requireBase: false}).hashPrefix('!');
})
.controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter) {
'use strict';
@@ -90,13 +90,14 @@ angular.module('syncthing.core')
refreshFolderStats();
$http.get(urlbase + '/system/version').success(function (data) {
if ($scope.version.version && $scope.version.version != data.version) {
if ($scope.version.version && $scope.version.version !== data.version) {
// We already have a version response, but it differs from
// the new one. Reload the full GUI in case it's changed.
document.location.reload(true);
}
$scope.version = data;
$scope.version.isDevelopmentVersion = data.version.indexOf('-')>0;
}).error($scope.emitHTTPError);
$http.get(urlbase + '/svc/report').success(function (data) {
@@ -271,10 +272,10 @@ angular.module('syncthing.core')
}
for (var folder in $scope.progress) {
if (!(folder in progress)) {
if ($scope.neededFolder == folder) {
if ($scope.neededFolder === folder) {
refreshNeed(folder);
}
} else if ($scope.neededFolder == folder) {
} else if ($scope.neededFolder === folder) {
for (file in $scope.progress[folder]) {
if (!(file in progress[folder])) {
refreshNeed(folder);
@@ -322,7 +323,7 @@ angular.module('syncthing.core')
$scope.scanProgress[data.folder] = {
current: data.current,
total: data.total,
rate: data.rate,
rate: data.rate
};
console.log("FolderScanProgress", data);
});
@@ -351,9 +352,8 @@ angular.module('syncthing.core')
var hasConfig = !isEmptyObject($scope.config);
$scope.config = config;
$scope.config.options._listenAddressStr = $scope.config.options.listenAddress.join(', ');
$scope.config.options._listenAddressesStr = $scope.config.options.listenAddresses.join(', ');
$scope.config.options._globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', ');
$scope.config.options._relayServersStr = $scope.config.options.relayServers.join(', ');
$scope.devices = $scope.config.devices;
$scope.devices.forEach(function (deviceCfg) {
@@ -374,8 +374,8 @@ angular.module('syncthing.core')
// authentication configured, and the magic setting to silence the
// warning isn't set, then yell at the user.
var guiCfg = $scope.config.gui;
$scope.openNoAuth = guiCfg.address.substr(0, 4) != "127."
&& guiCfg.address.substr(0, 6) != "[::1]:"
$scope.openNoAuth = guiCfg.address.substr(0, 4) !== "127."
&& guiCfg.address.substr(0, 6) !== "[::1]:"
&& (!guiCfg.user || !guiCfg.password)
&& !guiCfg.insecureAdminAccess;
@@ -389,6 +389,15 @@ angular.module('syncthing.core')
$scope.myID = data.myID;
$scope.system = data;
var listenersFailed = [];
for (var address in data.connectionServiceStatus) {
if (data.connectionServiceStatus[address].error) {
listenersFailed.push(address + ": " + data.connectionServiceStatus[address].error);
}
}
$scope.listenersFailed = listenersFailed;
$scope.listenersTotal = Object.keys(data.connectionServiceStatus).length;
$scope.discoveryTotal = data.discoveryMethods;
var discoveryFailed = [];
for (var disco in data.discoveryErrors) {
@@ -397,18 +406,6 @@ angular.module('syncthing.core')
}
}
$scope.discoveryFailed = discoveryFailed;
var relaysFailed = [];
var relaysTotal = 0;
for (var relay in data.relayClientStatus) {
if (!data.relayClientStatus[relay]) {
relaysFailed.push(relay);
}
relaysTotal++;
}
$scope.relaysFailed = relaysFailed;
$scope.relaysTotal = relaysTotal;
console.log("refreshSystem", data);
}).error($scope.emitHTTPError);
}
@@ -509,7 +506,7 @@ angular.module('syncthing.core')
url += "&page=" + $scope.neededCurrentPage;
url += "&perpage=" + $scope.neededPageSize;
$http.get(url).success(function (data) {
if ($scope.neededFolder == folder) {
if ($scope.neededFolder === folder) {
console.log("refreshNeed", folder, data);
parseNeeded(data);
}
@@ -631,10 +628,10 @@ angular.module('syncthing.core')
$scope.folderClass = function (folderCfg) {
var status = $scope.folderStatus(folderCfg);
if (status == 'idle') {
if (status === 'idle') {
return 'success';
}
if (status == 'syncing' || status == 'scanning') {
if (status === 'syncing' || status === 'scanning') {
return 'primary';
}
if (status === 'unknown') {
@@ -668,14 +665,14 @@ angular.module('syncthing.core')
}
var pct = 100 * $scope.scanProgress[folder].current / $scope.scanProgress[folder].total;
return Math.floor(pct);
}
};
$scope.scanRate = function (folder) {
if (!$scope.scanProgress[folder]) {
return 0;
}
return $scope.scanProgress[folder].rate;
}
};
$scope.scanRemaining = function (folder) {
// Formats the remaining scan time as a string. Includes days and
@@ -685,23 +682,22 @@ angular.module('syncthing.core')
// 2h 32m
// 4d 2h
var res = [];
if (!$scope.scanProgress[folder]) {
return "";
}
// Calculate remaining bytes and seconds based on our current
// rate.
var remainingBytes = $scope.scanProgress[folder].total - $scope.scanProgress[folder].current;
var seconds = remainingBytes / $scope.scanProgress[folder].rate;
// Round up to closest ten seconds to avoid flapping too much to
// and fro.
seconds = Math.ceil(seconds / 10) * 10;
// Separate out the number of days.
var days = 0;
var res = [];
if (seconds >= 86400) {
days = Math.floor(seconds / 86400);
res.push('' + days + 'd')
@@ -718,20 +714,20 @@ angular.module('syncthing.core')
var d = new Date(1970, 0, 1).setSeconds(seconds);
if (days == 0) {
if (days === 0) {
// Format minutes only if we're within a day of completion.
var f = $filter('date')(d, "m'm'");
res.push(f);
}
if (days == 0 && hours == 0) {
if (days === 0 && hours === 0) {
// Format seconds only when we're within an hour of completion.
var f = $filter('date')(d, "ss's'");
res.push(f);
}
return res.join(' ');
}
};
$scope.deviceStatus = function (deviceCfg) {
if ($scope.deviceFolders(deviceCfg).length === 0) {
@@ -802,16 +798,16 @@ angular.module('syncthing.core')
$scope.findDevice = function (deviceID) {
var matches = $scope.devices.filter(function (n) {
return n.deviceID == deviceID;
return n.deviceID === deviceID;
});
if (matches.length != 1) {
if (matches.length !== 1) {
return undefined;
}
return matches[0];
};
$scope.deviceName = function (deviceCfg) {
if (typeof deviceCfg === 'undefined') {
if (typeof deviceCfg === 'undefined' || typeof deviceCfg.deviceID === 'undefined') {
return "";
}
if (deviceCfg.name) {
@@ -892,7 +888,7 @@ angular.module('syncthing.core')
$scope.config.options = angular.copy($scope.tmpOptions);
$scope.config.gui = angular.copy($scope.tmpGUI);
['listenAddress', 'globalAnnounceServers', 'relayServers'].forEach(function (key) {
['listenAddresses', 'globalAnnounceServers'].forEach(function (key) {
$scope.config.options[key] = $scope.config.options["_" + key + "Str"].split(/[ ,]+/).map(function (x) {
return x.trim();
});
@@ -978,13 +974,14 @@ angular.module('syncthing.core')
$('#idqr').modal('show');
};
$scope.addDevice = function (deviceID) {
$scope.addDevice = function (deviceID, name) {
return $http.get(urlbase + '/system/discovery')
.success(function (registry) {
$scope.discovery = registry;
})
.then(function () {
$scope.currentDevice = {
name: name,
deviceID: deviceID,
_addressesStr: 'dynamic',
compression: 'metadata',
@@ -1029,17 +1026,15 @@ angular.module('syncthing.core')
};
$scope.saveDeviceConfig = function (deviceCfg) {
var done, i;
deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
return x.trim();
});
done = false;
for (i = 0; i < $scope.devices.length; i++) {
var done = false;
for (var i = 0; i < $scope.devices.length && !done; i++) {
if ($scope.devices[i].deviceID === deviceCfg.deviceID) {
$scope.devices[i] = deviceCfg;
done = true;
break;
}
}
@@ -1059,7 +1054,7 @@ angular.module('syncthing.core')
if (deviceCfg.selectedFolders[id]) {
var found = false;
for (i = 0; i < $scope.folders[id].devices.length; i++) {
if ($scope.folders[id].devices[i].deviceID == deviceCfg.deviceID) {
if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
found = true;
break;
}
@@ -1072,7 +1067,7 @@ angular.module('syncthing.core')
}
} else {
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
return n.deviceID != deviceCfg.deviceID;
return n.deviceID !== deviceCfg.deviceID;
});
}
}
@@ -1097,10 +1092,8 @@ angular.module('syncthing.core')
};
$scope.thisDevice = function () {
var i, n;
for (i = 0; i < $scope.devices.length; i++) {
n = $scope.devices[i];
for (var i = 0; i < $scope.devices.length; i++) {
var n = $scope.devices[i];
if (n.deviceID === $scope.myID) {
return n;
}
@@ -1142,7 +1135,7 @@ angular.module('syncthing.core')
$scope.directoryList = [];
$scope.$watch('currentFolder.path', function (newvalue) {
if (newvalue && newvalue.trim().charAt(0) == '~') {
if (newvalue && newvalue.trim().charAt(0) === '~') {
$scope.currentFolder.path = $scope.system.tilde + newvalue.trim().substring(1);
}
$http.get(urlbase + '/system/browse', {
@@ -1154,7 +1147,7 @@ angular.module('syncthing.core')
$scope.editFolder = function (folderCfg) {
$scope.currentFolder = angular.copy(folderCfg);
if ($scope.currentFolder.path.slice(-1) == $scope.system.pathSeparator) {
if ($scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
}
$scope.currentFolder.selectedDevices = {};
@@ -1202,30 +1195,9 @@ angular.module('syncthing.core')
$scope.addFolder = function () {
$scope.currentFolder = {
selectedDevices: {}
};
$scope.currentFolder.rescanIntervalS = 60;
$scope.currentFolder.minDiskFreePct = 1;
$scope.currentFolder.maxConflicts = 10;
$scope.currentFolder.order = "random";
$scope.currentFolder.fileVersioningSelector = "none";
$scope.currentFolder.trashcanClean = 0;
$scope.currentFolder.simpleKeep = 5;
$scope.currentFolder.staggeredMaxAge = 365;
$scope.currentFolder.staggeredCleanInterval = 3600;
$scope.currentFolder.staggeredVersionsPath = "";
$scope.currentFolder.externalCommand = "";
$scope.currentFolder.autoNormalize = true;
$scope.editingExisting = false;
$scope.folderEditor.$setPristine();
$('#editFolder').modal();
};
$scope.addFolderAndShare = function (folder, device) {
$scope.dismissFolderRejection(folder, device);
$scope.currentFolder = {
id: folder,
selectedDevices: {},
id: $scope.createRandomFolderId(),
type: "readwrite",
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: 10,
@@ -1239,6 +1211,33 @@ angular.module('syncthing.core')
externalCommand: "",
autoNormalize: true
};
$scope.editingExisting = false;
$scope.folderEditor.$setPristine();
$('#editFolder').modal();
};
$scope.addFolderAndShare = function (folder, folderLabel, device) {
$scope.dismissFolderRejection(folder, device);
$scope.currentFolder = {
id: folder,
label: folderLabel,
selectedDevices: {},
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: 10,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,
simpleKeep: 5,
staggeredMaxAge: 365,
staggeredCleanInterval: 3600,
staggeredVersionsPath: "",
externalCommand: "",
autoNormalize: true,
viewFlags: {
importFromOtherDevice: true
}
};
$scope.currentFolder.selectedDevices[device] = true;
$scope.editingExisting = false;
@@ -1256,10 +1255,8 @@ angular.module('syncthing.core')
};
$scope.saveFolder = function () {
var folderCfg, done, i;
$('#editFolder').modal('hide');
folderCfg = $scope.currentFolder;
var folderCfg = $scope.currentFolder;
folderCfg.devices = [];
folderCfg.selectedDevices[$scope.myID] = true;
for (var deviceID in folderCfg.selectedDevices) {
@@ -1329,7 +1326,7 @@ angular.module('syncthing.core')
$scope.sharesFolder = function (folderCfg) {
var names = [];
folderCfg.devices.forEach(function (device) {
if (device.deviceID != $scope.myID) {
if (device.deviceID !== $scope.myID) {
names.push($scope.deviceName($scope.findDevice(device.deviceID)));
}
});
@@ -1342,8 +1339,9 @@ angular.module('syncthing.core')
for (var folderID in $scope.folders) {
var devices = $scope.folders[folderID].devices;
for (var i = 0; i < devices.length; i++) {
if (devices[i].deviceID == deviceCfg.deviceID) {
folders.push(folderID);
if (devices[i].deviceID === deviceCfg.deviceID) {
var label = $scope.folders[folderID].label;
folders.push(label.length > 0 ? label : folderID);
break;
}
}
@@ -1445,7 +1443,7 @@ angular.module('syncthing.core')
};
$scope.showFailed = function (folder) {
$scope.failedCurrent = $scope.failed[folder]
$scope.failedCurrent = $scope.failed[folder];
$('#failed').modal().on('hidden.bs.modal', function () {
$scope.failedCurrent = undefined;
});
@@ -1455,10 +1453,10 @@ angular.module('syncthing.core')
if (!$scope.failed[folder]) {
return false;
}
if ($scope.failed[folder].length == 0) {
if ($scope.failed[folder].length === 0) {
return false;
}
return true
return true;
};
$scope.override = function (folder) {
@@ -1492,7 +1490,7 @@ angular.module('syncthing.core')
url += "&page=" + $scope.neededCurrentPage;
url += "&perpage=" + $scope.neededPageSize;
$http.post(url).success(function (data) {
if ($scope.neededFolder == folder) {
if ($scope.neededFolder === folder) {
console.log("bumpFile", folder, data);
parseNeeded(data);
}
@@ -1512,7 +1510,7 @@ angular.module('syncthing.core')
'netbsd': 'NetBSD',
'linux': 'Linux',
'windows': 'Windows',
'solaris': 'Solaris',
'solaris': 'Solaris'
}[$scope.version.os] || $scope.version.os;
var arch ={
@@ -1521,7 +1519,7 @@ angular.module('syncthing.core')
'arm': 'ARM',
'arm64': 'AArch64',
'ppc64': 'PowerPC',
'ppc64le': 'PowerPC (LE)',
'ppc64le': 'PowerPC (LE)'
}[$scope.version.arch] || $scope.version.arch;
return $scope.version.version + ', ' + os + ' (' + arch + ')';
@@ -1546,6 +1544,17 @@ angular.module('syncthing.core')
return 'text';
};
$scope.createRandomFolderId = function(){
var charset = '2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ';
return randomStringFromCharset(5, charset) + "-" + randomStringFromCharset(5, charset);
};
$scope.themeName = function (theme) {
return theme.replace('-', ' ').replace(/(?:^|\s)\S/g, function (a) {
return a.toUpperCase();
});
};
// pseudo main. called on all definitions assigned
initController();
});

View File

@@ -0,0 +1,11 @@
angular.module('syncthing.core')
.directive('tooltip', function () {
return {
restrict: 'A',
link: function (scope, element, attributes) {
$(element).tooltip({
html: 'true'
});
}
};
});

View File

@@ -0,0 +1,13 @@
<div class="dev-top-bar" id="dev-top-bar">
<link href="assets/css/dev.css" rel="stylesheet">
<div class="row">
<div class="col-xs-4"><b>DEV</b></div>
<div id="log" class="col-xs-8">
<span>
JS:
<span class="dev-error fa fa-exclamation-triangle"> errors <span id="log_error">0</span></span>
<span class="dev-warn fa fa-frown-o"> warn <span id="log_warn">0</span></span>
</span>
</div>
</div>
</div>

View File

@@ -0,0 +1,39 @@
'use strict';
function intercept(method, handler) {
var console = window.console;
var original = console[method];
console[method] = function () {
handler(method);
// do sneaky stuff
if (original.apply) {
// Do this for normal browsers
original.apply(console, arguments);
} else {
// Do this for IE
var message = Array.prototype.slice.apply(arguments).join(' ');
original(message);
}
};
}
function handleConsoleCall(type) {
var element = document.querySelector('#log_' + type);
if (element) {
if (!element.classList.contains("hasCount")) {
element.classList.add("hasCount");
}
var devTopBar = document.querySelector('#dev-top-bar');
devTopBar.style.display = 'block';
element.innerHTML = parseInt(element.innerHTML) + 1;
}
}
if (window.console) {
var methods = ['error', 'warn'];
for (var i = 0; i < methods.length; i++) {
intercept(methods[i], handleConsoleCall);
}
}

View File

@@ -60,8 +60,11 @@
<div class="row">
<div class="col-md-4" ng-repeat="folder in folderList()">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]"> {{folder.id}}
<label ng-if="folder.label.length == 0">
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]">&nbsp;{{folder.id}}
</label>
<label ng-if="folder.label.length != 0">
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]">&nbsp;<span title="{{folder.id}}">{{folder.label}}</span>
</label>
</div>
</div>

View File

@@ -11,138 +11,36 @@
<form role="form" name="folderEditor">
<div class="row">
<div class="col-md-12">
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty}">
<label for="folderLabel"><span translate>Folder Label</span></label>
<input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}"/>
<p class="help-block">
<span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
<label for="folderID"><span translate>Folder ID</span></label>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder></input>
<input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder value="{{currentFolder.id}}"/>
<p class="help-block">
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Short identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Required identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
<span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
<label translate for="folderPath">Folder Path</label>
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required />
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required path-is-sub-dir/>
<datalist id="directory-list">
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
</datalist>
<p class="help-block">
<span translate ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine">Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty">The folder path cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.rescanIntervalS.$invalid && folderEditor.rescanIntervalS.$dirty}">
<label for="rescanIntervalS"><span translate>Rescan Interval</span> (s)</label>
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentFolder.rescanIntervalS" required min="0"></input>
<p class="help-block">
<span translate ng-if="!folderEditor.rescanIntervalS.$valid && folderEditor.rescanIntervalS.$dirty">The rescan interval must be a non-negative number of seconds.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.minDiskFreePct.$invalid && folderEditor.minDiskFreePct.$dirty}">
<label for="minDiskFreePct"><span translate>Minimum Free Disk Space</span> (0.0 - 100.0%)</label>
<input name="minDiskFreePct" id="minDiskFreePct" class="form-control" type="number" ng-model="currentFolder.minDiskFreePct" required min="0.0" max="100.0"></input>
<p class="help-block">
<span translate ng-if="!folderEditor.minDiskFreePct.$valid && folderEditor.minDiskFreePct.$dirty">The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).</span>
<span class="text-danger" translate translate-value-other-folder="{{otherFolder}}" ng-if="pathIsSubFolder">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
</p>
</div>
</div>
</div>
<div class="row">
<!-- Left column -->
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.readOnly"> <span translate>Folder Master</span>
</label>
&nbsp;<a href="http://docs.syncthing.net/users/foldermaster.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
</div>
<p translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.ignorePerms"> <span translate>Ignore Permissions</span>
</label>
</div>
<p translate class="help-block">File permission bits are ignored when looking for changes. Use on FAT file systems.</p>
</div>
</div>
<!-- Right column-->
<div class="col-md-6">
<div class="form-group">
<label translate>File Pull Order</label>
<select class="form-control" ng-model="currentFolder.order">
<option value="random" translate>Random</option>
<option value="alphabetic" translate>Alphabetic</option>
<option value="smallestFirst" translate>Smallest First</option>
<option value="largestFirst" translate>Largest First</option>
<option value="oldestFirst" translate>Oldest First</option>
<option value="newestFirst" translate>Newest First</option>
</select>
</div>
<div class="form-group">
<label translate>File Versioning</label>&emsp;<a href="http://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.fileVersioningSelector">
<option value="none" translate>No File Versioning</option>
<option value="trashcan" translate>Trash Can File Versioning</option>
<option value="simple" translate>Simple File Versioning</option>
<option value="staggered" translate>Staggered File Versioning</option>
<option value="external" translate>External File Versioning</option>
</select>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
<p translate class="help-block">Files are moved to .stversions folder when replaced or deleted by Syncthing.</p>
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required min="0"></input>
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required min="1"></input>
<p class="help-block">
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required min="0"></input>
<p class="help-block">
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
<label translate for="staggeredVersionsPath">Versions Path</label>
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath"></input>
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions folder in the folder).</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
<p translate class="help-block">An external command handles the versioning. It has to remove the file from the synced folder.</p>
<label translate for="externalCommand">Command</label>
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required></input>
<p class="help-block">
<span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">The first command line parameter is the folder path and the second parameter is the relative path in the folder.</span>
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
@@ -160,9 +58,132 @@
</div>
</div>
</div>
<div translate ng-show="!editingExisting" class="help-block">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</div>
<div class="row">
<div class="col-md-12">
<label data-target="#folder-advanced" class="folder-advanced-toggle collapsed" data-toggle="collapse">
<span translate>Advanced settings</span>&nbsp;
<i class="expand fa fa-plus-square"></i>
<i class="collapse fa fa-minus-square"></i>
</label>
</div>
</div>
<div id="folder-advanced" class="folder-advanced collapse">
<div class="row">
<div class="col-md-12">
<div class="form-group" ng-class="{'has-error': folderEditor.rescanIntervalS.$invalid && folderEditor.rescanIntervalS.$dirty}">
<label for="rescanIntervalS"><span translate>Rescan Interval</span> (s)</label>
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentFolder.rescanIntervalS" required min="0">
<p class="help-block">
<span translate ng-if="!folderEditor.rescanIntervalS.$valid && folderEditor.rescanIntervalS.$dirty">The rescan interval must be a non-negative number of seconds.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.minDiskFreePct.$invalid && folderEditor.minDiskFreePct.$dirty}">
<label for="minDiskFreePct"><span translate>Minimum Free Disk Space</span> (0.0 - 100.0%)</label>
<input name="minDiskFreePct" id="minDiskFreePct" class="form-control" type="number" ng-model="currentFolder.minDiskFreePct" required min="0.0" max="100.0">
<p class="help-block">
<span translate ng-if="!folderEditor.minDiskFreePct.$valid && folderEditor.minDiskFreePct.$dirty">The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).</span>
</p>
</div>
</div>
</div>
<div class="row">
<!-- Left column -->
<div class="col-md-6">
<div class="form-group">
<label translate>Folder Type</label>
&nbsp;<a href="http://docs.syncthing.net/users/foldermaster.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.type">
<option value="readwrite" translate>Normal</option>
<option value="readonly" translate>Master</option>
</select>
<p ng-if="currentFolder.type == 'readonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.ignorePerms"> <span translate>Ignore Permissions</span>
</label>
</div>
<p translate class="help-block">File permission bits are ignored when looking for changes. Use on FAT file systems.</p>
</div>
</div>
<!-- Right column-->
<div class="col-md-6">
<div class="form-group">
<label translate>File Pull Order</label>
<select class="form-control" ng-model="currentFolder.order">
<option value="random" translate>Random</option>
<option value="alphabetic" translate>Alphabetic</option>
<option value="smallestFirst" translate>Smallest First</option>
<option value="largestFirst" translate>Largest First</option>
<option value="oldestFirst" translate>Oldest First</option>
<option value="newestFirst" translate>Newest First</option>
</select>
</div>
<div class="form-group">
<label translate>File Versioning</label>&emsp;<a href="http://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.fileVersioningSelector">
<option value="none" translate>No File Versioning</option>
<option value="trashcan" translate>Trash Can File Versioning</option>
<option value="simple" translate>Simple File Versioning</option>
<option value="staggered" translate>Staggered File Versioning</option>
<option value="external" translate>External File Versioning</option>
</select>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
<p translate class="help-block">Files are moved to .stversions folder when replaced or deleted by Syncthing.</p>
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required min="0">
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required min="1">
<p class="help-block">
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required min="0">
<p class="help-block">
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
<label translate for="staggeredVersionsPath">Versions Path</label>
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath">
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions folder in the folder).</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
<p translate class="help-block">An external command handles the versioning. It has to remove the file from the synced folder.</p>
<label translate for="externalCommand">Command</label>
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required>
<p class="help-block">
<span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">The first command line parameter is the folder path and the second parameter is the relative path in the folder.</span>
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
</div>
</div>
</div>
</form>
<div translate ng-show="!editingExisting">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" ng-click="saveFolder()" ng-disabled="folderEditor.$invalid">

View File

@@ -51,8 +51,11 @@
<div class="panel panel-default" ng-repeat="folder in advancedConfig.folders">
<div class="panel-heading" role="tab" id="folder{{$index}}Heading" data-toggle="collapse" data-parent="#advancedAccordion" href="#folder{{$index}}Config" aria-expanded="true" aria-controls="folder{{$index}}Config" style="cursor: pointer">
<h4 class="panel-title">
<span translate>Folder</span> "{{folder.id}}"
<h4 ng-if="folder.label.length == 0" class="panel-title">
<span translate>Folder</span> "{{folder.id}}"
</h4>
<h4 ng-if="folder.label.length != 0" class="panel-title">
<span translate>Folder</span> "{{folder.label}}" ({{folder.id}})
</h4>
</div>
<div id="folder{{$index}}Config" class="panel-collapse collapse" role="tabpanel" aria-labelledby="folder{{$index}}Heading">

View File

@@ -15,8 +15,8 @@
</div>
<div class="form-group">
<label translate for="ListenAddressStr">Sync Protocol Listen Addresses</label>
<input id="ListenAddressStr" class="form-control" type="text" ng-model="tmpOptions._listenAddressStr">
<label translate for="ListenAddressesStr">Sync Protocol Listen Addresses</label>
<input id="ListenAddressesStr" class="form-control" type="text" ng-model="tmpOptions._listenAddressesStr">
</div>
<div class="form-group" ng-class="{'has-error': settingsEditor.MaxRecvKbps.$invalid && settingsEditor.MaxRecvKbps.$dirty}">
@@ -40,7 +40,7 @@
<div class="form-group">
<div class="checkbox">
<label>
<input id="UPnPEnabled" type="checkbox" ng-model="tmpOptions.upnpEnabled"> <span translate>Enable UPnP</span>
<input id="NATEnabled" type="checkbox" ng-model="tmpOptions.natEnabled"> <span translate>Enable NAT traversal</span>
</label>
</div>
</div>
@@ -56,20 +56,6 @@
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="RelaysEnabled" type="checkbox" ng-model="tmpOptions.relaysEnabled"> <span translate>Enable Relaying</span>
</label>
</div>
</div>
<div class="clearfix"></div>
<div class="form-group">
<label translate for="RelayServersStr">Relay Servers</label>
<input ng-disabled="!tmpOptions.relaysEnabled" id="RelayServersStr" class="form-control" type="text" ng-model="tmpOptions._relayServersStr">
</div>
<div class="form-group">
<div class="checkbox">
<label>
@@ -139,9 +125,9 @@
<div class="form-group" ng-if="system.themes.length > 1">
<label>GUI Theme</label>
<select class="form-control" ng-model="tmpGUI.theme" ng->
<option ng-repeat="theme in system.themes" value="{{ theme }}">
{{ theme.charAt(0).toUpperCase() + theme.slice(1) }}
<select class="form-control" ng-model="tmpGUI.theme">
<option ng-repeat="theme in system.themes.sort()" value="{{ theme }}">
{{ themeName(theme) }}
</option>
</select>
</div>

View File

@@ -1 +0,0 @@
angular.module("pascalprecht.translate").factory("$translateStaticFilesLoader",["$q","$http",function(a,b){return function(c){if(!c||!c.prefix||!c.suffix)throw new Error("Couldn't load static files, no prefix or suffix specified!");var d=a.defer();return b({url:[c.prefix,c.key,c.suffix].join(""),method:"GET",params:""}).success(function(a){d.resolve(a)}).error(function(){d.reject(c.key)}),d.promise}}]);

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,202 +0,0 @@
/*
AngularJS v1.2.7
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(Z,Q,r){'use strict';function F(b){return function(){var a=arguments[0],c,a="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.2.7/"+(b?b+"/":"")+a;for(c=1;c<arguments.length;c++)a=a+(1==c?"?":"&")+"p"+(c-1)+"="+encodeURIComponent("function"==typeof arguments[c]?arguments[c].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[c]?"undefined":"string"!=typeof arguments[c]?JSON.stringify(arguments[c]):arguments[c]);return Error(a)}}function qb(b){if(null==b||za(b))return!1;var a=
b.length;return 1===b.nodeType&&a?!0:D(b)||K(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function q(b,a,c){var d;if(b)if(L(b))for(d in b)"prototype"==d||("length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d))||a.call(c,b[d],d);else if(b.forEach&&b.forEach!==q)b.forEach(a,c);else if(qb(b))for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function Ob(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function Oc(b,
a,c){for(var d=Ob(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function Pb(b){return function(a,c){b(c,a)}}function Ya(){for(var b=ka.length,a;b;){b--;a=ka[b].charCodeAt(0);if(57==a)return ka[b]="A",ka.join("");if(90==a)ka[b]="0";else return ka[b]=String.fromCharCode(a+1),ka.join("")}ka.unshift("0");return ka.join("")}function Qb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function t(b){var a=b.$$hashKey;q(arguments,function(a){a!==b&&q(a,function(a,c){b[c]=a})});Qb(b,a);return b}function S(b){return parseInt(b,
10)}function Rb(b,a){return t(new (t(function(){},{prototype:b})),a)}function w(){}function Aa(b){return b}function $(b){return function(){return b}}function z(b){return"undefined"===typeof b}function B(b){return"undefined"!==typeof b}function X(b){return null!=b&&"object"===typeof b}function D(b){return"string"===typeof b}function rb(b){return"number"===typeof b}function Ja(b){return"[object Date]"===Za.call(b)}function K(b){return"[object Array]"===Za.call(b)}function L(b){return"function"===typeof b}
function $a(b){return"[object RegExp]"===Za.call(b)}function za(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function Pc(b){return!(!b||!(b.nodeName||b.on&&b.find))}function Qc(b,a,c){var d=[];q(b,function(b,g,f){d.push(a.call(c,b,g,f))});return d}function ab(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function Ka(b,a){var c=ab(b,a);0<=c&&b.splice(c,1);return a}function fa(b,a){if(za(b)||b&&b.$evalAsync&&b.$watch)throw La("cpws");if(a){if(b===
a)throw La("cpi");if(K(b))for(var c=a.length=0;c<b.length;c++)a.push(fa(b[c]));else{c=a.$$hashKey;q(a,function(b,c){delete a[c]});for(var d in b)a[d]=fa(b[d]);Qb(a,c)}}else(a=b)&&(K(b)?a=fa(b,[]):Ja(b)?a=new Date(b.getTime()):$a(b)?a=RegExp(b.source):X(b)&&(a=fa(b,{})));return a}function Sb(b,a){a=a||{};for(var c in b)b.hasOwnProperty(c)&&("$"!==c.charAt(0)&&"$"!==c.charAt(1))&&(a[c]=b[c]);return a}function ua(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,
d;if(c==typeof a&&"object"==c)if(K(b)){if(!K(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ua(b[d],a[d]))return!1;return!0}}else{if(Ja(b))return Ja(a)&&b.getTime()==a.getTime();if($a(b)&&$a(a))return b.toString()==a.toString();if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||za(b)||za(a)||K(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!L(b[d])){if(!ua(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==r&&!L(a[d]))return!1;return!0}return!1}
function Tb(){return Q.securityPolicy&&Q.securityPolicy.isActive||Q.querySelector&&!(!Q.querySelector("[ng-csp]")&&!Q.querySelector("[data-ng-csp]"))}function bb(b,a){var c=2<arguments.length?va.call(arguments,2):[];return!L(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(va.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Rc(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)?c=r:za(a)?c="$WINDOW":
a&&Q===a?c="$DOCUMENT":a&&(a.$evalAsync&&a.$watch)&&(c="$SCOPE");return c}function pa(b,a){return"undefined"===typeof b?r:JSON.stringify(b,Rc,a?" ":null)}function Ub(b){return D(b)?JSON.parse(b):b}function Ma(b){"function"===typeof b?b=!0:b&&0!==b.length?(b=x(""+b),b=!("f"==b||"0"==b||"false"==b||"no"==b||"n"==b||"[]"==b)):b=!1;return b}function ga(b){b=A(b).clone();try{b.empty()}catch(a){}var c=A("<div>").append(b).html();try{return 3===b[0].nodeType?x(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,
function(a,b){return"<"+x(b)})}catch(d){return x(c)}}function Vb(b){try{return decodeURIComponent(b)}catch(a){}}function Wb(b){var a={},c,d;q((b||"").split("&"),function(b){b&&(c=b.split("="),d=Vb(c[0]),B(d)&&(b=B(c[1])?Vb(c[1]):!0,a[d]?K(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Xb(b){var a=[];q(b,function(b,d){K(b)?q(b,function(b){a.push(wa(d,!0)+(!0===b?"":"="+wa(b,!0)))}):a.push(wa(d,!0)+(!0===b?"":"="+wa(b,!0)))});return a.length?a.join("&"):""}function sb(b){return wa(b,
!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function Sc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,f=["ng:app","ng-app","x-ng-app","data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;q(f,function(a){f[a]=!0;c(Q.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(q(b.querySelectorAll("."+a),c),q(b.querySelectorAll("."+
a+"\\:"),c),q(b.querySelectorAll("["+a+"]"),c))});q(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):q(a.attributes,function(b){!e&&f[b.name]&&(e=a,g=b.value)})}});e&&a(e,g?[g]:[])}function Yb(b,a){var c=function(){b=A(b);if(b.injector()){var c=b[0]===Q?"document":ga(b);throw La("btstrpd",c);}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");c=Zb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",
function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(Z&&!d.test(Z.name))return c();Z.name=Z.name.replace(d,"");Na.resumeBootstrap=function(b){q(b,function(b){a.push(b)});c()}}function cb(b,a){a=a||"_";return b.replace(Tc,function(b,d){return(d?a:"")+b.toLowerCase()})}function tb(b,a,c){if(!b)throw La("areq",a||"?",c||"required");return b}function Oa(b,a,c){c&&K(b)&&(b=b[b.length-1]);tb(L(b),a,"not a function, got "+(b&&"object"==typeof b?
b.constructor.name||"Object":typeof b));return b}function xa(b,a){if("hasOwnProperty"===b)throw La("badname",a);}function ub(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,g=a.length,f=0;f<g;f++)d=a[f],b&&(b=(e=b)[d]);return!c&&L(b)?bb(e,b):b}function vb(b){var a=b[0];b=b[b.length-1];if(a===b)return A(a);var c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b);return A(c)}function Uc(b){var a=F("$injector"),c=F("ng");b=b.angular||(b.angular={});b.$$minErr=b.$$minErr||F;return b.module||
(b.module=function(){var b={};return function(e,g,f){if("hasOwnProperty"===e)throw c("badname","module");g&&b.hasOwnProperty(e)&&(b[e]=null);return b[e]||(b[e]=function(){function b(a,d,e){return function(){c[e||"push"]([a,d,arguments]);return n}}if(!g)throw a("nomod",e);var c=[],d=[],l=b("$injector","invoke"),n={_invokeQueue:c,_runBlocks:d,requires:g,name:e,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:b("$provide","value"),constant:b("$provide",
"constant","unshift"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),config:l,run:function(a){d.push(a);return this}};f&&l(f);return n}())}}())}function Pa(b){return b.replace(Vc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(Wc,"Moz$1")}function wb(b,a,c,d){function e(b){var e=c&&b?[this.filter(b)]:[this],m=a,k,l,n,p,s,C;if(!d||null!=b)for(;e.length;)for(k=e.shift(),
l=0,n=k.length;l<n;l++)for(p=A(k[l]),m?p.triggerHandler("$destroy"):m=!m,s=0,p=(C=p.children()).length;s<p;s++)e.push(Ba(C[s]));return g.apply(this,arguments)}var g=Ba.fn[b],g=g.$original||g;e.$original=g;Ba.fn[b]=e}function O(b){if(b instanceof O)return b;if(!(this instanceof O)){if(D(b)&&"<"!=b.charAt(0))throw xb("nosel");return new O(b)}if(D(b)){var a=Q.createElement("div");a.innerHTML="<div>&#160;</div>"+b;a.removeChild(a.firstChild);yb(this,a.childNodes);A(Q.createDocumentFragment()).append(this)}else yb(this,
b)}function zb(b){return b.cloneNode(!0)}function Ca(b){$b(b);var a=0;for(b=b.childNodes||[];a<b.length;a++)Ca(b[a])}function ac(b,a,c,d){if(B(d))throw xb("offargs");var e=la(b,"events");la(b,"handle")&&(z(a)?q(e,function(a,c){Ab(b,c,a);delete e[c]}):q(a.split(" "),function(a){z(c)?(Ab(b,a,e[a]),delete e[a]):Ka(e[a]||[],c)}))}function $b(b,a){var c=b[db],d=Qa[c];d&&(a?delete Qa[c].data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),ac(b)),delete Qa[c],b[db]=r))}function la(b,a,c){var d=
b[db],d=Qa[d||-1];if(B(c))d||(b[db]=d=++Xc,d=Qa[d]={}),d[a]=c;else return d&&d[a]}function bc(b,a,c){var d=la(b,"data"),e=B(c),g=!e&&B(a),f=g&&!X(a);d||f||la(b,"data",d={});if(e)d[a]=c;else if(g){if(f)return d&&d[a];t(d,a)}else return d}function Bb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function Cb(b,a){a&&b.setAttribute&&q(a.split(" "),function(a){b.setAttribute("class",aa((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g,
" ").replace(" "+aa(a)+" "," ")))})}function Db(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(a.split(" "),function(a){a=aa(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",aa(c))}}function yb(b,a){if(a){a=a.nodeName||!B(a.length)||za(a)?[a]:a;for(var c=0;c<a.length;c++)b.push(a[c])}}function cc(b,a){return eb(b,"$"+(a||"ngController")+"Controller")}function eb(b,a,c){b=A(b);9==b[0].nodeType&&(b=b.find("html"));for(a=K(a)?a:[a];b.length;){for(var d=
0,e=a.length;d<e;d++)if((c=b.data(a[d]))!==r)return c;b=b.parent()}}function dc(b){for(var a=0,c=b.childNodes;a<c.length;a++)Ca(c[a]);for(;b.firstChild;)b.removeChild(b.firstChild)}function ec(b,a){var c=fb[a.toLowerCase()];return c&&fc[b.nodeName]&&c}function Yc(b,a){var c=function(c,e){c.preventDefault||(c.preventDefault=function(){c.returnValue=!1});c.stopPropagation||(c.stopPropagation=function(){c.cancelBubble=!0});c.target||(c.target=c.srcElement||Q);if(z(c.defaultPrevented)){var g=c.preventDefault;
c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented||!1===c.returnValue};var f=Sb(a[e||c.type]||[]);q(f,function(a){a.call(b,c)});8>=M?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Da(b){var a=typeof b,c;"object"==a&&null!==b?"function"==typeof(c=b.$$hashKey)?c=b.$$hashKey():c===
r&&(c=b.$$hashKey=Ya()):c=b;return a+":"+c}function Ra(b){q(b,this.put,this)}function gc(b){var a,c;"function"==typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace(Zc,""),c=c.match($c),q(c[1].split(ad),function(b){b.replace(bd,function(b,c,d){a.push(d)})})),b.$inject=a):K(b)?(c=b.length-1,Oa(b[c],"fn"),a=b.slice(0,c)):Oa(b,"fn",!0);return a}function Zb(b){function a(a){return function(b,c){if(X(b))q(b,Pb(a));else return a(b,c)}}function c(a,b){xa(a,"service");if(L(b)||K(b))b=n.instantiate(b);
if(!b.$get)throw Sa("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],c,d,g,h;q(a,function(a){if(!k.get(a)){k.put(a,!0);try{if(D(a))for(c=Ta(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,g=0,h=d.length;g<h;g++){var f=d[g],m=n.get(f[0]);m[f[1]].apply(m,f[2])}else L(a)?b.push(n.invoke(a)):K(a)?b.push(n.invoke(a)):Oa(a,"module")}catch(l){throw K(a)&&(a=a[a.length-1]),l.message&&(l.stack&&-1==l.stack.indexOf(l.message))&&(l=l.message+"\n"+l.stack),
Sa("modulerr",a,l.stack||l.message||l);}}});return b}function g(a,b){function c(d){if(a.hasOwnProperty(d)){if(a[d]===f)throw Sa("cdep",m.join(" <- "));return a[d]}try{return m.unshift(d),a[d]=f,a[d]=b(d)}catch(e){throw a[d]===f&&delete a[d],e;}finally{m.shift()}}function d(a,b,e){var g=[],h=gc(a),f,k,m;k=0;for(f=h.length;k<f;k++){m=h[k];if("string"!==typeof m)throw Sa("itkn",m);g.push(e&&e.hasOwnProperty(m)?e[m]:c(m))}a.$inject||(a=a[f]);return a.apply(b,g)}return{invoke:d,instantiate:function(a,
b){var c=function(){},e;c.prototype=(K(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return X(e)||L(e)?e:c},get:c,annotate:gc,has:function(b){return l.hasOwnProperty(b+h)||a.hasOwnProperty(b)}}}var f={},h="Provider",m=[],k=new Ra,l={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,$(b))}),constant:a(function(a,b){xa(a,"constant");l[a]=b;p[a]=b}),decorator:function(a,b){var c=n.get(a+h),
d=c.$get;c.$get=function(){var a=s.invoke(d,c);return s.invoke(b,null,{$delegate:a})}}}},n=l.$injector=g(l,function(){throw Sa("unpr",m.join(" <- "));}),p={},s=p.$injector=g(p,function(a){a=n.get(a+h);return s.invoke(a.$get,a)});q(e(b),function(a){s.invoke(a||w)});return s}function cd(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;q(a,function(a){b||"a"!==x(a.nodeName)||(b=a)});return b}function g(){var b=
c.hash(),d;b?(d=f.getElementById(b))?d.scrollIntoView():(d=e(f.getElementsByName(b)))?d.scrollIntoView():"top"===b&&a.scrollTo(0,0):a.scrollTo(0,0)}var f=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(g)});return g}]}function dd(b,a,c,d){function e(a){try{a.apply(null,va.call(arguments,1))}finally{if(C--,0===C)for(;y.length;)try{y.pop()()}catch(b){c.error(b)}}}function g(a,b){(function T(){q(E,function(a){a()});u=b(T,a)})()}function f(){v=null;R!=h.url()&&(R=h.url(),q(ha,
function(a){a(h.url())}))}var h=this,m=a[0],k=b.location,l=b.history,n=b.setTimeout,p=b.clearTimeout,s={};h.isMock=!1;var C=0,y=[];h.$$completeOutstandingRequest=e;h.$$incOutstandingRequestCount=function(){C++};h.notifyWhenNoOutstandingRequests=function(a){q(E,function(a){a()});0===C?a():y.push(a)};var E=[],u;h.addPollFn=function(a){z(u)&&g(100,n);E.push(a);return a};var R=k.href,H=a.find("base"),v=null;h.url=function(a,c){k!==b.location&&(k=b.location);l!==b.history&&(l=b.history);if(a){if(R!=a)return R=
a,d.history?c?l.replaceState(null,"",a):(l.pushState(null,"",a),H.attr("href",H.attr("href"))):(v=a,c?k.replace(a):k.href=a),h}else return v||k.href.replace(/%27/g,"'")};var ha=[],N=!1;h.onUrlChange=function(a){if(!N){if(d.history)A(b).on("popstate",f);if(d.hashchange)A(b).on("hashchange",f);else h.addPollFn(f);N=!0}ha.push(a);return a};h.baseHref=function(){var a=H.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};var V={},J="",ba=h.baseHref();h.cookies=function(a,b){var d,e,g,h;if(a)b===
r?m.cookie=escape(a)+"=;path="+ba+";expires=Thu, 01 Jan 1970 00:00:00 GMT":D(b)&&(d=(m.cookie=escape(a)+"="+escape(b)+";path="+ba).length+1,4096<d&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"));else{if(m.cookie!==J)for(J=m.cookie,d=J.split("; "),V={},g=0;g<d.length;g++)e=d[g],h=e.indexOf("="),0<h&&(a=unescape(e.substring(0,h)),V[a]===r&&(V[a]=unescape(e.substring(h+1))));return V}};h.defer=function(a,b){var c;C++;c=n(function(){delete s[c];
e(a)},b||0);s[c]=!0;return c};h.defer.cancel=function(a){return s[a]?(delete s[a],p(a),e(w),!0):!1}}function ed(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new dd(b,d,a,c)}]}function fd(){this.$get=function(){function b(b,d){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,g(a.n,a.p),g(a,n),n=a,n.n=null)}function g(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw F("$cacheFactory")("iid",b);var f=0,h=t({},d,{id:b}),m={},k=d&&d.capacity||Number.MAX_VALUE,l={},n=null,p=null;
return a[b]={put:function(a,b){var c=l[a]||(l[a]={key:a});e(c);if(!z(b))return a in m||f++,m[a]=b,f>k&&this.remove(p.key),b},get:function(a){var b=l[a];if(b)return e(b),m[a]},remove:function(a){var b=l[a];b&&(b==n&&(n=b.p),b==p&&(p=b.n),g(b.n,b.p),delete l[a],delete m[a],f--)},removeAll:function(){m={};f=0;l={};n=p=null},destroy:function(){l=h=m=null;delete a[b]},info:function(){return t({},h,{size:f})}}}var a={};b.info=function(){var b={};q(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};
return b}}function gd(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function ic(b,a){var c={},d="Directive",e=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,g=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,f=/^(on[a-z]+|formaction)$/;this.directive=function m(a,e){xa(a,"directive");D(a)?(tb(e,"directiveFactory"),c.hasOwnProperty(a)||(c[a]=[],b.factory(a+d,["$injector","$exceptionHandler",function(b,d){var e=[];q(c[a],function(c,g){try{var f=b.invoke(c);L(f)?f={compile:$(f)}:!f.compile&&f.link&&(f.compile=
$(f.link));f.priority=f.priority||0;f.index=g;f.name=f.name||a;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(m){d(m)}});return e}])),c[a].push(e)):q(a,Pb(m));return this};this.aHrefSanitizationWhitelist=function(b){return B(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};this.$get=["$injector","$interpolate",
"$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",function(a,b,l,n,p,s,C,y,E,u,R,H){function v(a,b,c,d,e){a instanceof A||(a=A(a));q(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=A(b).wrap("<span></span>").parent()[0])});var g=N(a,b,a,c,d,e);ha(a,"ng-scope");return function(b,c,d){tb(b,"scope");var e=c?Ea.clone.call(a):a;q(d,function(a,b){e.data("$"+b+"Controller",a)});d=0;for(var f=e.length;d<f;d++){var k=
e[d].nodeType;1!==k&&9!==k||e.eq(d).data("$scope",b)}c&&c(e,b);g&&g(b,e,e);return e}}function ha(a,b){try{a.addClass(b)}catch(c){}}function N(a,b,c,d,e,g){function f(a,c,d,e){var g,m,l,s,n,p,I;g=c.length;var C=Array(g);for(n=0;n<g;n++)C[n]=c[n];I=n=0;for(p=k.length;n<p;I++)m=C[I],c=k[n++],g=k[n++],l=A(m),c?(c.scope?(s=a.$new(),l.data("$scope",s)):s=a,(l=c.transclude)||!e&&b?c(g,s,m,d,V(a,l||b)):c(g,s,m,d,e)):g&&g(a,m.childNodes,r,e)}for(var k=[],m,l,s,n,p=0;p<a.length;p++)m=new Eb,l=J(a[p],[],m,0===
p?d:r,e),(g=l.length?ia(l,a[p],m,b,c,null,[],[],g):null)&&g.scope&&ha(A(a[p]),"ng-scope"),m=g&&g.terminal||!(s=a[p].childNodes)||!s.length?null:N(s,g?g.transclude:b),k.push(g,m),n=n||g||m,g=null;return n?f:null}function V(a,b){return function(c,d,e){var g=!1;c||(c=a.$new(),g=c.$$transcluded=!0);d=b(c,d,e);if(g)d.on("$destroy",bb(c,c.$destroy));return d}}function J(a,b,c,d,f){var k=c.$attr,m;switch(a.nodeType){case 1:T(b,ma(Fa(a).toLowerCase()),"E",d,f);var l,s,n;m=a.attributes;for(var p=0,C=m&&m.length;p<
C;p++){var y=!1,R=!1;l=m[p];if(!M||8<=M||l.specified){s=l.name;n=ma(s);W.test(n)&&(s=cb(n.substr(6),"-"));var v=n.replace(/(Start|End)$/,"");n===v+"Start"&&(y=s,R=s.substr(0,s.length-5)+"end",s=s.substr(0,s.length-6));n=ma(s.toLowerCase());k[n]=s;c[n]=l=aa(l.value);ec(a,n)&&(c[n]=!0);S(a,b,l,n);T(b,n,"A",d,f,y,R)}}a=a.className;if(D(a)&&""!==a)for(;m=g.exec(a);)n=ma(m[2]),T(b,n,"C",d,f)&&(c[n]=aa(m[3])),a=a.substr(m.index+m[0].length);break;case 3:F(b,a.nodeValue);break;case 8:try{if(m=e.exec(a.nodeValue))n=
ma(m[1]),T(b,n,"M",d,f)&&(c[n]=aa(m[2]))}catch(E){}}b.sort(z);return b}function ba(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ja("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return A(d)}function P(a,b,c){return function(d,e,g,f,m){e=ba(e[0],b,c);return a(d,e,g,f,m)}}function ia(a,c,d,e,g,f,m,n,p){function y(a,b,c,d){if(a){c&&(a=P(a,c,d));a.require=G.require;if(H===G||G.$$isolateScope)a=
jc(a,{isolateScope:!0});m.push(a)}if(b){c&&(b=P(b,c,d));b.require=G.require;if(H===G||G.$$isolateScope)b=jc(b,{isolateScope:!0});n.push(b)}}function R(a,b,c){var d,e="data",g=!1;if(D(a)){for(;"^"==(d=a.charAt(0))||"?"==d;)a=a.substr(1),"^"==d&&(e="inheritedData"),g=g||"?"==d;d=null;c&&"data"===e&&(d=c[a]);d=d||b[e]("$"+a+"Controller");if(!d&&!g)throw ja("ctreq",a,ca);}else K(a)&&(d=[],q(a,function(a){d.push(R(a,b,c))}));return d}function E(a,e,g,f,p){function y(a,b){var c;2>arguments.length&&(b=a,
a=r);z&&(c=ba);return p(a,b,c)}var I,v,N,u,P,J,ba={},gb;I=c===g?d:Sb(d,new Eb(A(g),d.$attr));v=I.$$element;if(H){var T=/^\s*([@=&])(\??)\s*(\w*)\s*$/;f=A(g);J=e.$new(!0);ia&&ia===H.$$originalDirective?f.data("$isolateScope",J):f.data("$isolateScopeNoTemplate",J);ha(f,"ng-isolate-scope");q(H.scope,function(a,c){var d=a.match(T)||[],g=d[3]||c,f="?"==d[2],d=d[1],m,l,n,p;J.$$isolateBindings[c]=d+g;switch(d){case "@":I.$observe(g,function(a){J[c]=a});I.$$observers[g].$$scope=e;I[g]&&(J[c]=b(I[g])(e));
break;case "=":if(f&&!I[g])break;l=s(I[g]);p=l.literal?ua:function(a,b){return a===b};n=l.assign||function(){m=J[c]=l(e);throw ja("nonassign",I[g],H.name);};m=J[c]=l(e);J.$watch(function(){var a=l(e);p(a,J[c])||(p(a,m)?n(e,a=J[c]):J[c]=a);return m=a},null,l.literal);break;case "&":l=s(I[g]);J[c]=function(a){return l(e,a)};break;default:throw ja("iscp",H.name,c,a);}})}gb=p&&y;V&&q(V,function(a){var b={$scope:a===H||a.$$isolateScope?J:e,$element:v,$attrs:I,$transclude:gb},c;P=a.controller;"@"==P&&(P=
I[a.name]);c=C(P,b);ba[a.name]=c;z||v.data("$"+a.name+"Controller",c);a.controllerAs&&(b.$scope[a.controllerAs]=c)});f=0;for(N=m.length;f<N;f++)try{u=m[f],u(u.isolateScope?J:e,v,I,u.require&&R(u.require,v,ba),gb)}catch(G){l(G,ga(v))}f=e;H&&(H.template||null===H.templateUrl)&&(f=J);a&&a(f,g.childNodes,r,p);for(f=n.length-1;0<=f;f--)try{u=n[f],u(u.isolateScope?J:e,v,I,u.require&&R(u.require,v,ba),gb)}catch(B){l(B,ga(v))}}p=p||{};var N=-Number.MAX_VALUE,u,V=p.controllerDirectives,H=p.newIsolateScopeDirective,
ia=p.templateDirective;p=p.nonTlbTranscludeDirective;for(var T=!1,z=!1,t=d.$$element=A(c),G,ca,U,F=e,O,M=0,na=a.length;M<na;M++){G=a[M];var Ua=G.$$start,S=G.$$end;Ua&&(t=ba(c,Ua,S));U=r;if(N>G.priority)break;if(U=G.scope)u=u||G,G.templateUrl||(x("new/isolated scope",H,G,t),X(U)&&(H=G));ca=G.name;!G.templateUrl&&G.controller&&(U=G.controller,V=V||{},x("'"+ca+"' controller",V[ca],G,t),V[ca]=G);if(U=G.transclude)T=!0,G.$$tlb||(x("transclusion",p,G,t),p=G),"element"==U?(z=!0,N=G.priority,U=ba(c,Ua,S),
t=d.$$element=A(Q.createComment(" "+ca+": "+d[ca]+" ")),c=t[0],hb(g,A(va.call(U,0)),c),F=v(U,e,N,f&&f.name,{nonTlbTranscludeDirective:p})):(U=A(zb(c)).contents(),t.empty(),F=v(U,e));if(G.template)if(x("template",ia,G,t),ia=G,U=L(G.template)?G.template(t,d):G.template,U=Y(U),G.replace){f=G;U=A("<div>"+aa(U)+"</div>").contents();c=U[0];if(1!=U.length||1!==c.nodeType)throw ja("tplrt",ca,"");hb(g,t,c);na={$attr:{}};U=J(c,[],na);var W=a.splice(M+1,a.length-(M+1));H&&hc(U);a=a.concat(U).concat(W);B(d,na);
na=a.length}else t.html(U);if(G.templateUrl)x("template",ia,G,t),ia=G,G.replace&&(f=G),E=w(a.splice(M,a.length-M),t,d,g,F,m,n,{controllerDirectives:V,newIsolateScopeDirective:H,templateDirective:ia,nonTlbTranscludeDirective:p}),na=a.length;else if(G.compile)try{O=G.compile(t,d,F),L(O)?y(null,O,Ua,S):O&&y(O.pre,O.post,Ua,S)}catch(Z){l(Z,ga(t))}G.terminal&&(E.terminal=!0,N=Math.max(N,G.priority))}E.scope=u&&!0===u.scope;E.transclude=T&&F;return E}function hc(a){for(var b=0,c=a.length;b<c;b++)a[b]=Rb(a[b],
{$$isolateScope:!0})}function T(b,e,g,f,k,s,n){if(e===k)return null;k=null;if(c.hasOwnProperty(e)){var p;e=a.get(e+d);for(var C=0,y=e.length;C<y;C++)try{p=e[C],(f===r||f>p.priority)&&-1!=p.restrict.indexOf(g)&&(s&&(p=Rb(p,{$$start:s,$$end:n})),b.push(p),k=p)}catch(v){l(v)}}return k}function B(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,g){"class"==g?(ha(e,b),a["class"]=(a["class"]?a["class"]+
" ":"")+b):"style"==g?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==g.charAt(0)||a.hasOwnProperty(g)||(a[g]=b,d[g]=c[g])})}function w(a,b,c,d,e,g,f,m){var k=[],l,s,C=b[0],y=a.shift(),v=t({},y,{templateUrl:null,transclude:null,replace:null,$$originalDirective:y}),R=L(y.templateUrl)?y.templateUrl(b,c):y.templateUrl;b.empty();n.get(u.getTrustedResourceUrl(R),{cache:p}).success(function(n){var p,E;n=Y(n);if(y.replace){n=A("<div>"+aa(n)+"</div>").contents();p=n[0];if(1!=
n.length||1!==p.nodeType)throw ja("tplrt",y.name,R);n={$attr:{}};hb(d,b,p);var u=J(p,[],n);X(y.scope)&&hc(u);a=u.concat(a);B(c,n)}else p=C,b.html(n);a.unshift(v);l=ia(a,p,c,e,b,y,g,f,m);q(d,function(a,c){a==p&&(d[c]=b[0])});for(s=N(b[0].childNodes,e);k.length;){n=k.shift();E=k.shift();var H=k.shift(),ha=k.shift(),u=b[0];E!==C&&(u=zb(p),hb(H,A(E),u));E=l.transclude?V(n,l.transclude):ha;l(s,n,u,d,E)}k=null}).error(function(a,b,c,d){throw ja("tpload",d.url);});return function(a,b,c,d,e){k?(k.push(b),
k.push(c),k.push(d),k.push(e)):l(s,b,c,d,e)}}function z(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function x(a,b,c,d){if(b)throw ja("multidir",b.name,c.name,a,ga(d));}function F(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:$(function(a,b){var c=b.parent(),e=c.data("$binding")||[];e.push(d);ha(c.data("$binding",e),"ng-binding");a.$watch(d,function(a){b[0].nodeValue=a})})})}function O(a,b){if("srcdoc"==b)return u.HTML;var c=Fa(a);if("xlinkHref"==
b||"FORM"==c&&"action"==b||"IMG"!=c&&("src"==b||"ngSrc"==b))return u.RESOURCE_URL}function S(a,c,d,e){var g=b(d,!0);if(g){if("multiple"===e&&"SELECT"===Fa(a))throw ja("selmulti",ga(a));c.push({priority:100,compile:function(){return{pre:function(c,d,m){d=m.$$observers||(m.$$observers={});if(f.test(e))throw ja("nodomevents");if(g=b(m[e],!0,O(a,e)))m[e]=g(c),(d[e]||(d[e]=[])).$$inter=!0,(m.$$observers&&m.$$observers[e].$$scope||c).$watch(g,function(a,b){"class"===e&&a!=b?m.$updateClass(a,b):m.$set(e,
a)})}}}})}}function hb(a,b,c){var d=b[0],e=b.length,g=d.parentNode,f,m;if(a)for(f=0,m=a.length;f<m;f++)if(a[f]==d){a[f++]=c;m=f+e-1;for(var k=a.length;f<k;f++,m++)m<k?a[f]=a[m]:delete a[f];a.length-=e-1;break}g&&g.replaceChild(c,d);a=Q.createDocumentFragment();a.appendChild(d);c[A.expando]=d[A.expando];d=1;for(e=b.length;d<e;d++)g=b[d],A(g).remove(),a.appendChild(g),delete b[d];b[0]=c;b.length=1}function jc(a,b){return t(function(){return a.apply(null,arguments)},a,b)}var Eb=function(a,b){this.$$element=
a;this.$attr=b||{}};Eb.prototype={$normalize:ma,$addClass:function(a){a&&0<a.length&&R.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&R.removeClass(this.$$element,a)},$updateClass:function(a,b){this.$removeClass(kc(b,a));this.$addClass(kc(a,b))},$set:function(a,b,c,d){var e=ec(this.$$element[0],a);e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=cb(a,"-"));e=Fa(this.$$element);if("A"===e&&"href"===a||"IMG"===e&&"src"===a)this[a]=
b=H(b,"src"===a);!1!==c&&(null===b||b===r?this.$$element.removeAttr(d):this.$$element.attr(d,b));(c=this.$$observers)&&q(c[a],function(a){try{a(b)}catch(c){l(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);y.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var ca=b.startSymbol(),na=b.endSymbol(),Y="{{"==ca||"}}"==na?Aa:function(a){return a.replace(/\{\{/g,ca).replace(/}}/g,na)},W=/^ngAttr[A-Z]/;return v}]}function ma(b){return Pa(b.replace(hd,
""))}function kc(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),g=0;a:for(;g<d.length;g++){for(var f=d[g],h=0;h<e.length;h++)if(f==e[h])continue a;c+=(0<c.length?" ":"")+f}return c}function id(){var b={},a=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,d){xa(a,"controller");X(a)?t(b,a):b[a]=d};this.$get=["$injector","$window",function(c,d){return function(e,g){var f,h,m;D(e)&&(f=e.match(a),h=f[1],m=f[3],e=b.hasOwnProperty(h)?b[h]:ub(g.$scope,h,!0)||ub(d,h,!0),Oa(e,h,!0));f=c.instantiate(e,g);
if(m){if(!g||"object"!=typeof g.$scope)throw F("$controller")("noscp",h||e.name,m);g.$scope[m]=f}return f}}]}function jd(){this.$get=["$window",function(b){return A(b.document)}]}function kd(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function lc(b){var a={},c,d,e;if(!b)return a;q(b.split("\n"),function(b){e=b.indexOf(":");c=x(aa(b.substr(0,e)));d=aa(b.substr(e+1));c&&(a[c]=a[c]?a[c]+(", "+d):d)});return a}function mc(b){var a=X(b)?b:r;return function(c){a||
(a=lc(b));return c?a[x(c)]||null:a}}function nc(b,a,c){if(L(c))return c(b,a);q(c,function(c){b=c(b,a)});return b}function ld(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults={transformResponse:[function(d){D(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=Ub(d)));return d}],transformRequest:[function(a){return X(a)&&"[object File]"!==Za.call(a)?pa(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:d,
put:d,patch:d},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},g=this.interceptors=[],f=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,n,p){function s(a){function c(a){var b=t({},a,{data:nc(a.data,a.headers,d.transformResponse)});return 200<=a.status&&300>a.status?b:n.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},g=function(a){function b(a){var c;q(a,function(b,
d){L(b)&&(c=b(),null!=c?a[d]=c:delete a[d])})}var c=e.headers,d=t({},a.headers),g,f,c=t({},c.common,c[x(a.method)]);b(c);b(d);a:for(g in c){a=x(g);for(f in d)if(x(f)===a)continue a;d[g]=c[g]}return d}(a);t(d,a);d.headers=g;d.method=Ga(d.method);(a=Fb(d.url)?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:r)&&(g[d.xsrfHeaderName||e.xsrfHeaderName]=a);var f=[function(a){g=a.headers;var b=nc(a.data,mc(g),a.transformRequest);z(a.data)&&q(g,function(a,b){"content-type"===x(b)&&delete g[b]});z(a.withCredentials)&&
!z(e.withCredentials)&&(a.withCredentials=e.withCredentials);return C(a,b,g).then(c,c)},r],h=n.when(d);for(q(u,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var k=f.shift(),h=h.then(a,k)}h.success=function(a){h.then(function(b){a(b.data,b.status,b.headers,d)});return h};h.error=function(a){h.then(null,function(b){a(b.data,b.status,b.headers,d)});return h};return h}function C(b,
c,g){function f(a,b,c){u&&(200<=a&&300>a?u.put(r,[a,b,lc(c)]):u.remove(r));m(b,a,c);d.$$phase||d.$apply()}function m(a,c,d){c=Math.max(c,0);(200<=c&&300>c?p.resolve:p.reject)({data:a,status:c,headers:mc(d),config:b})}function k(){var a=ab(s.pendingRequests,b);-1!==a&&s.pendingRequests.splice(a,1)}var p=n.defer(),C=p.promise,u,q,r=y(b.url,b.params);s.pendingRequests.push(b);C.then(k,k);(b.cache||e.cache)&&(!1!==b.cache&&"GET"==b.method)&&(u=X(b.cache)?b.cache:X(e.cache)?e.cache:E);if(u)if(q=u.get(r),
B(q)){if(q.then)return q.then(k,k),q;K(q)?m(q[1],q[0],fa(q[2])):m(q,200,{})}else u.put(r,C);z(q)&&a(b.method,r,c,f,g,b.timeout,b.withCredentials,b.responseType);return C}function y(a,b){if(!b)return a;var c=[];Oc(b,function(a,b){null===a||z(a)||(K(a)||(a=[a]),q(a,function(a){X(a)&&(a=pa(a));c.push(wa(b)+"="+wa(a))}))});return a+(-1==a.indexOf("?")?"?":"&")+c.join("&")}var E=c("$http"),u=[];q(g,function(a){u.unshift(D(a)?p.get(a):p.invoke(a))});q(f,function(a,b){var c=D(a)?p.get(a):p.invoke(a);u.splice(b,
0,{response:function(a){return c(n.when(a))},responseError:function(a){return c(n.reject(a))}})});s.pendingRequests=[];(function(a){q(arguments,function(a){s[a]=function(b,c){return s(t(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){s[a]=function(b,c,d){return s(t(d||{},{method:a,url:b,data:c}))}})})("post","put");s.defaults=e;return s}]}function md(b){return 8>=M&&"patch"===x(b)?new ActiveXObject("Microsoft.XMLHTTP"):new Z.XMLHttpRequest}function nd(){this.$get=
["$browser","$window","$document",function(b,a,c){return od(b,md,b.defer,a.angular.callbacks,c[0])}]}function od(b,a,c,d,e){function g(a,b){var c=e.createElement("script"),d=function(){c.onreadystatechange=c.onload=c.onerror=null;e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;M&&8>=M?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=function(){d()};e.body.appendChild(c);return d}var f=-1;return function(e,m,k,l,n,p,s,C){function y(){u=f;
H&&H();v&&v.abort()}function E(a,d,e,g){var f=qa(m).protocol;r&&c.cancel(r);H=v=null;d="file"==f&&0===d?e?200:404:d;a(1223==d?204:d,e,g);b.$$completeOutstandingRequest(w)}var u;b.$$incOutstandingRequestCount();m=m||b.url();if("jsonp"==x(e)){var R="_"+(d.counter++).toString(36);d[R]=function(a){d[R].data=a};var H=g(m.replace("JSON_CALLBACK","angular.callbacks."+R),function(){d[R].data?E(l,200,d[R].data):E(l,u||-2);delete d[R]})}else{var v=a(e);v.open(e,m,!0);q(n,function(a,b){B(a)&&v.setRequestHeader(b,
a)});v.onreadystatechange=function(){if(v&&4==v.readyState){var a=null,b=null;u!==f&&(a=v.getAllResponseHeaders(),b=v.responseType?v.response:v.responseText);E(l,u||v.status,b,a)}};s&&(v.withCredentials=!0);C&&(v.responseType=C);v.send(k||null)}if(0<p)var r=c(y,p);else p&&p.then&&p.then(y)}}function pd(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function g(g,
k,l){for(var n,p,s=0,C=[],y=g.length,E=!1,u=[];s<y;)-1!=(n=g.indexOf(b,s))&&-1!=(p=g.indexOf(a,n+f))?(s!=n&&C.push(g.substring(s,n)),C.push(s=c(E=g.substring(n+f,p))),s.exp=E,s=p+h,E=!0):(s!=y&&C.push(g.substring(s)),s=y);(y=C.length)||(C.push(""),y=1);if(l&&1<C.length)throw oc("noconcat",g);if(!k||E)return u.length=y,s=function(a){try{for(var b=0,c=y,f;b<c;b++)"function"==typeof(f=C[b])&&(f=f(a),f=l?e.getTrusted(l,f):e.valueOf(f),null===f||z(f)?f="":"string"!=typeof f&&(f=pa(f))),u[b]=f;return u.join("")}catch(h){a=
oc("interr",g,h.toString()),d(a)}},s.exp=g,s.parts=C,s}var f=b.length,h=a.length;g.startSymbol=function(){return b};g.endSymbol=function(){return a};return g}]}function qd(){this.$get=["$rootScope","$window","$q",function(b,a,c){function d(d,f,h,m){var k=a.setInterval,l=a.clearInterval,n=c.defer(),p=n.promise,s=0,C=B(m)&&!m;h=B(h)?h:0;p.then(null,null,d);p.$$intervalId=k(function(){n.notify(s++);0<h&&s>=h&&(n.resolve(s),l(p.$$intervalId),delete e[p.$$intervalId]);C||b.$apply()},f);e[p.$$intervalId]=
n;return p}var e={};d.cancel=function(a){return a&&a.$$intervalId in e?(e[a.$$intervalId].reject("canceled"),clearInterval(a.$$intervalId),delete e[a.$$intervalId],!0):!1};return d}]}function rd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},
DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",
shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function pc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=sb(b[a]);return b.join("/")}function qc(b,a,c){b=qa(b,c);a.$$protocol=b.protocol;a.$$host=b.hostname;a.$$port=S(b.port)||sd[b.protocol]||null}function rc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=qa(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=Wb(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&
"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function oa(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Va(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Gb(b){return b.substr(0,Va(b).lastIndexOf("/")+1)}function sc(b,a){this.$$html5=!0;a=a||"";var c=Gb(b);qc(b,this,b);this.$$parse=function(a){var e=oa(c,a);if(!D(e))throw Hb("ipthprfx",a,c);rc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Xb(this.$$search),b=this.$$hash?"#"+
sb(this.$$hash):"";this.$$url=pc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$rewrite=function(d){var e;if((e=oa(b,d))!==r)return d=e,(e=oa(a,e))!==r?c+(oa("/",e)||e):b+d;if((e=oa(c,d))!==r)return c+e;if(c==d+"/")return c}}function Ib(b,a){var c=Gb(b);qc(b,this,b);this.$$parse=function(d){var e=oa(b,d)||oa(c,d),e="#"==e.charAt(0)?oa(a,e):this.$$html5?e:"";if(!D(e))throw Hb("ihshprfx",d,a);rc(e,this,b);d=this.$$path;var g=/^\/?.*?:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,
""));g.exec(e)||(d=(e=g.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Xb(this.$$search),e=this.$$hash?"#"+sb(this.$$hash):"";this.$$url=pc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$rewrite=function(a){if(Va(b)==Va(a))return a}}function tc(b,a){this.$$html5=!0;Ib.apply(this,arguments);var c=Gb(b);this.$$rewrite=function(d){var e;if(b==Va(d))return d;if(e=oa(c,d))return b+a+e;if(c===d+"/")return c}}function ib(b){return function(){return this[b]}}
function uc(b,a){return function(c){if(z(c))return this[b];this[b]=a(c);this.$$compose();return this}}function td(){var b="",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=function(b){return B(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function f(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,m=d.baseHref(),k=d.url();a?(m=k.substring(0,k.indexOf("/",k.indexOf("//")+2))+(m||"/"),e=e.history?sc:tc):(m=Va(k),
e=Ib);h=new e(m,"#"+b);h.$$parse(h.$$rewrite(k));g.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=A(a.target);"a"!==x(b[0].nodeName);)if(b[0]===g[0]||!(b=b.parent())[0])return;var e=b.prop("href");X(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=qa(e.animVal).href);var f=h.$$rewrite(e);e&&(!b.attr("target")&&f&&!a.isDefaultPrevented())&&(a.preventDefault(),f!=d.url()&&(h.$$parse(f),c.$apply(),Z.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=k&&d.url(h.absUrl(),
!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);c.$broadcast("$locationChangeStart",a,b).defaultPrevented?(h.$$parse(b),d.url(b)):f(b)}),c.$$phase||c.$digest())});var l=0;c.$watch(function(){var a=d.url(),b=h.$$replace;l&&a==h.absUrl()||(l++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),f(a))}));h.$$replace=!1;return l});return h}]}function ud(){var b=!0,a=this;this.debugEnabled=
function(a){return B(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||w;a=!1;try{a=!!e.apply}catch(m){}return a?function(){var a=[];q(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),
warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function da(b,a){if("constructor"===b)throw ya("isecfld",a);return b}function Wa(b,a){if(b){if(b.constructor===b)throw ya("isecfn",a);if(b.document&&b.location&&b.alert&&b.setInterval)throw ya("isecwindow",a);if(b.children&&(b.nodeName||b.on&&b.find))throw ya("isecdom",a);}return b}function jb(b,a,c,d,e){e=e||{};a=a.split(".");for(var g,f=0;1<a.length;f++){g=da(a.shift(),d);var h=b[g];
h||(h={},b[g]=h);b=h;b.then&&e.unwrapPromises&&(ra(d),"$$v"in b||function(a){a.then(function(b){a.$$v=b})}(b),b.$$v===r&&(b.$$v={}),b=b.$$v)}g=da(a.shift(),d);return b[g]=c}function vc(b,a,c,d,e,g,f){da(b,g);da(a,g);da(c,g);da(d,g);da(e,g);return f.unwrapPromises?function(f,m){var k=m&&m.hasOwnProperty(b)?m:f,l;if(null==k)return k;(k=k[b])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);if(null==k)return a?r:k;(k=k[a])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=
a})),k=k.$$v);if(null==k)return c?r:k;(k=k[c])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);if(null==k)return d?r:k;(k=k[d])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);if(null==k)return e?r:k;(k=k[e])&&k.then&&(ra(g),"$$v"in k||(l=k,l.$$v=r,l.then(function(a){l.$$v=a})),k=k.$$v);return k}:function(g,f){var k=f&&f.hasOwnProperty(b)?f:g;if(null==k)return k;k=k[b];if(null==k)return a?r:k;k=k[a];if(null==k)return c?r:k;k=k[c];if(null==
k)return d?r:k;k=k[d];return null==k?e?r:k:k=k[e]}}function vd(b,a){da(b,a);return function(a,d){return null==a?r:(d&&d.hasOwnProperty(b)?d:a)[b]}}function wd(b,a,c){da(b,c);da(a,c);return function(c,e){if(null==c)return r;c=(e&&e.hasOwnProperty(b)?e:c)[b];return null==c?r:c[a]}}function wc(b,a,c){if(Jb.hasOwnProperty(b))return Jb[b];var d=b.split("."),e=d.length,g;if(a.unwrapPromises||1!==e)if(a.unwrapPromises||2!==e)if(a.csp)g=6>e?vc(d[0],d[1],d[2],d[3],d[4],c,a):function(b,g){var f=0,h;do h=vc(d[f++],
d[f++],d[f++],d[f++],d[f++],c,a)(b,g),g=r,b=h;while(f<e);return h};else{var f="var p;\n";q(d,function(b,d){da(b,c);f+="if(s == null) return undefined;\ns="+(d?"s":'((k&&k.hasOwnProperty("'+b+'"))?k:s)')+'["'+b+'"];\n'+(a.unwrapPromises?'if (s && s.then) {\n pw("'+c.replace(/(["\r\n])/g,"\\$1")+'");\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n':"")});var f=f+"return s;",h=new Function("s","k","pw",f);h.toString=$(f);g=a.unwrapPromises?function(a,
b){return h(a,b,ra)}:h}else g=wd(d[0],d[1],c);else g=vd(d[0],c);"hasOwnProperty"!==b&&(Jb[b]=g);return g}function xd(){var b={},a={csp:!1,unwrapPromises:!1,logPromiseWarnings:!0};this.unwrapPromises=function(b){return B(b)?(a.unwrapPromises=!!b,this):a.unwrapPromises};this.logPromiseWarnings=function(b){return B(b)?(a.logPromiseWarnings=b,this):a.logPromiseWarnings};this.$get=["$filter","$sniffer","$log",function(c,d,e){a.csp=d.csp;ra=function(b){a.logPromiseWarnings&&!xc.hasOwnProperty(b)&&(xc[b]=
!0,e.warn("[$parse] Promise found in the expression `"+b+"`. Automatic unwrapping of promises in Angular expressions is deprecated."))};return function(d){var e;switch(typeof d){case "string":if(b.hasOwnProperty(d))return b[d];e=new Kb(a);e=(new Xa(e,c,a)).parse(d,!1);"hasOwnProperty"!==d&&(b[d]=e);return e;case "function":return d;default:return w}}}]}function yd(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return zd(function(a){b.$evalAsync(a)},a)}]}function zd(b,a){function c(a){return a}
function d(a){return f(a)}var e=function(){var h=[],m,k;return k={resolve:function(a){if(h){var c=h;h=r;m=g(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],m.then(a[0],a[1],a[2])})}},reject:function(a){k.resolve(f(a))},notify:function(a){if(h){var c=h;h.length&&b(function(){for(var b,d=0,e=c.length;d<e;d++)b=c[d],b[2](a)})}},promise:{then:function(b,g,f){var k=e(),C=function(d){try{k.resolve((L(b)?b:c)(d))}catch(e){k.reject(e),a(e)}},y=function(b){try{k.resolve((L(g)?g:d)(b))}catch(c){k.reject(c),
a(c)}},E=function(b){try{k.notify((L(f)?f:c)(b))}catch(d){a(d)}};h?h.push([C,y,E]):m.then(C,y,E);return k.promise},"catch":function(a){return this.then(null,a)},"finally":function(a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,g){var f=null;try{f=(a||c)()}catch(h){return b(h,!1)}return f&&L(f.then)?f.then(function(){return b(e,g)},function(a){return b(a,!1)}):b(e,g)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}}}},g=function(a){return a&&
L(a.then)?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},f=function(c){return{then:function(g,f){var l=e();b(function(){try{l.resolve((L(f)?f:d)(c))}catch(b){l.reject(b),a(b)}});return l.promise}}};return{defer:e,reject:f,when:function(h,m,k,l){var n=e(),p,s=function(b){try{return(L(m)?m:c)(b)}catch(d){return a(d),f(d)}},C=function(b){try{return(L(k)?k:d)(b)}catch(c){return a(c),f(c)}},y=function(b){try{return(L(l)?l:c)(b)}catch(d){a(d)}};b(function(){g(h).then(function(a){p||
(p=!0,n.resolve(g(a).then(s,C,y)))},function(a){p||(p=!0,n.resolve(C(a)))},function(a){p||n.notify(y(a))})});return n.promise},all:function(a){var b=e(),c=0,d=K(a)?[]:{};q(a,function(a,e){c++;g(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise}}}function Ad(){var b=10,a=F("$rootScope"),c=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler",
"$parse","$browser",function(d,e,g,f){function h(){this.$id=Ya();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$postDigestQueue=[];this.$$listeners={};this.$$listenerCount={};this.$$isolateBindings={}}function m(b){if(p.$$phase)throw a("inprog",p.$$phase);p.$$phase=b}function k(a,b){var c=g(a);Oa(c,b);return c}function l(a,b,c){do a.$$listenerCount[c]-=
b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function n(){}h.prototype={constructor:h,$new:function(a){a?(a=new h,a.$root=this.$root,a.$$asyncQueue=this.$$asyncQueue,a.$$postDigestQueue=this.$$postDigestQueue):(a=function(){},a.prototype=this,a=new a,a.$id=Ya());a["this"]=a;a.$$listeners={};a.$$listenerCount={};a.$parent=this;a.$$watchers=a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=
a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,d){var e=k(a,"watch"),g=this.$$watchers,f={fn:b,last:n,get:e,exp:a,eq:!!d};c=null;if(!L(b)){var h=k(b||w,"listener");f.fn=function(a,b,c){h(c)}}if("string"==typeof a&&e.constant){var m=f.fn;f.fn=function(a,b,c){m.call(this,a,b,c);Ka(g,f)}}g||(g=this.$$watchers=[]);g.unshift(f);return function(){Ka(g,f);c=null}},$watchCollection:function(a,b){var c=this,d,e,f=0,h=g(a),m=[],k={},l=0;return this.$watch(function(){e=h(c);var a,b;if(X(e))if(qb(e))for(d!==
m&&(d=m,l=d.length=0,f++),a=e.length,l!==a&&(f++,d.length=l=a),b=0;b<a;b++)d[b]!==e[b]&&(f++,d[b]=e[b]);else{d!==k&&(d=k={},l=0,f++);a=0;for(b in e)e.hasOwnProperty(b)&&(a++,d.hasOwnProperty(b)?d[b]!==e[b]&&(f++,d[b]=e[b]):(l++,d[b]=e[b],f++));if(l>a)for(b in f++,d)d.hasOwnProperty(b)&&!e.hasOwnProperty(b)&&(l--,delete d[b])}else d!==e&&(d=e,f++);return f},function(){b(e,d,c)})},$digest:function(){var d,f,g,h,k=this.$$asyncQueue,l=this.$$postDigestQueue,q,v,r=b,N,V=[],J,A,P;m("$digest");c=null;do{v=
!1;for(N=this;k.length;){try{P=k.shift(),P.scope.$eval(P.expression)}catch(B){p.$$phase=null,e(B)}c=null}a:do{if(h=N.$$watchers)for(q=h.length;q--;)try{if(d=h[q])if((f=d.get(N))!==(g=d.last)&&!(d.eq?ua(f,g):"number"==typeof f&&"number"==typeof g&&isNaN(f)&&isNaN(g)))v=!0,c=d,d.last=d.eq?fa(f):f,d.fn(f,g===n?f:g,N),5>r&&(J=4-r,V[J]||(V[J]=[]),A=L(d.exp)?"fn: "+(d.exp.name||d.exp.toString()):d.exp,A+="; newVal: "+pa(f)+"; oldVal: "+pa(g),V[J].push(A));else if(d===c){v=!1;break a}}catch(t){p.$$phase=
null,e(t)}if(!(h=N.$$childHead||N!==this&&N.$$nextSibling))for(;N!==this&&!(h=N.$$nextSibling);)N=N.$parent}while(N=h);if(v&&!r--)throw p.$$phase=null,a("infdig",b,pa(V));}while(v||k.length);for(p.$$phase=null;l.length;)try{l.shift()()}catch(z){e(z)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this!==p&&(q(this.$$listenerCount,bb(null,l,this)),a.$$childHead==this&&(a.$$childHead=this.$$nextSibling),a.$$childTail==this&&(a.$$childTail=
this.$$prevSibling),this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling),this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling),this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a){p.$$phase||p.$$asyncQueue.length||f.defer(function(){p.$$asyncQueue.length&&p.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},
$apply:function(a){try{return m("$apply"),this.$eval(a)}catch(b){e(b)}finally{p.$$phase=null;try{p.$digest()}catch(c){throw e(c),c;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){c[ab(c,b)]=null;l(e,1,a)}},$emit:function(a,b){var c=[],d,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=
!0},defaultPrevented:!1},m=[h].concat(va.call(arguments,1)),k,l;do{d=f.$$listeners[a]||c;h.currentScope=f;k=0;for(l=d.length;k<l;k++)if(d[k])try{d[k].apply(null,m)}catch(p){e(p)}else d.splice(k,1),k--,l--;if(g)break;f=f.$parent}while(f);return h},$broadcast:function(a,b){for(var c=this,d=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},g=[f].concat(va.call(arguments,1)),h,k;c=d;){f.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,
g)}catch(m){e(m)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}return f}};var p=new h;return p}]}function Bd(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*(https?|ftp|file):|data:image\//;this.aHrefSanitizationWhitelist=function(a){return B(a)?(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return B(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,g;if(!M||8<=
M)if(g=qa(c).href,""!==g&&!g.match(e))return"unsafe:"+g;return c}}}function Cd(b){if("self"===b)return b;if(D(b)){if(-1<b.indexOf("***"))throw sa("iwcard",b);b=b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08").replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return RegExp("^"+b+"$")}if($a(b))return RegExp("^"+b.source+"$");throw sa("imatcher");}function yc(b){var a=[];B(b)&&q(b,function(b){a.push(Cd(b))});return a}function Dd(){this.SCE_CONTEXTS=ea;var b=["self"],a=[];this.resourceUrlWhitelist=
function(a){arguments.length&&(b=yc(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=yc(b));return a};this.$get=["$injector",function(c){function d(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var e=function(a){throw sa("unsafe");};c.has("$sanitize")&&(e=c.get("$sanitize"));
var g=d(),f={};f[ea.HTML]=d(g);f[ea.CSS]=d(g);f[ea.URL]=d(g);f[ea.JS]=d(g);f[ea.RESOURCE_URL]=d(f[ea.URL]);return{trustAs:function(a,b){var c=f.hasOwnProperty(a)?f[a]:null;if(!c)throw sa("icontext",a,b);if(null===b||b===r||""===b)return b;if("string"!==typeof b)throw sa("itype",a);return new c(b)},getTrusted:function(c,d){if(null===d||d===r||""===d)return d;var g=f.hasOwnProperty(c)?f[c]:null;if(g&&d instanceof g)return d.$$unwrapTrustedValue();if(c===ea.RESOURCE_URL){var g=qa(d.toString()),l,n,p=
!1;l=0;for(n=b.length;l<n;l++)if("self"===b[l]?Fb(g):b[l].exec(g.href)){p=!0;break}if(p)for(l=0,n=a.length;l<n;l++)if("self"===a[l]?Fb(g):a[l].exec(g.href)){p=!1;break}if(p)return d;throw sa("insecurl",d.toString());}if(c===ea.HTML)return e(d);throw sa("unsafe");},valueOf:function(a){return a instanceof g?a.$$unwrapTrustedValue():a}}}]}function Ed(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sniffer","$sceDelegate",function(a,c,d){if(b&&c.msie&&8>c.msieDocumentMode)throw sa("iequirks");
var e=fa(ea);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=Aa);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:function(a,c){return e.getTrusted(b,d(a,c))}};var g=e.parseAs,f=e.getTrusted,h=e.trustAs;q(ea,function(a,b){var c=x(b);e[Pa("parse_as_"+c)]=function(b){return g(a,b)};e[Pa("get_trusted_"+c)]=function(b){return f(a,b)};e[Pa("trust_as_"+c)]=function(b){return h(a,
b)}});return e}]}function Fd(){this.$get=["$window","$document",function(b,a){var c={},d=S((/android (\d+)/.exec(x((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),g=a[0]||{},f=g.documentMode,h,m=/^(Moz|webkit|O|ms)(?=[A-Z])/,k=g.body&&g.body.style,l=!1,n=!1;if(k){for(var p in k)if(l=m.exec(p)){h=l[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in k&&"webkit");l=!!("transition"in k||h+"Transition"in k);n=!!("animation"in k||h+"Animation"in
k);!d||l&&n||(l=D(g.body.style.webkitTransition),n=D(g.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!f||7<f),hasEvent:function(a){if("input"==a&&9==M)return!1;if(z(c[a])){var b=g.createElement("div");c[a]="on"+a in b}return c[a]},csp:Tb(),vendorPrefix:h,transitions:l,animations:n,android:d,msie:M,msieDocumentMode:f}}]}function Gd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,h,
m){var k=c.defer(),l=k.promise,n=B(m)&&!m;h=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),d(a)}finally{delete g[l.$$timeoutId]}n||b.$apply()},h);l.$$timeoutId=h;g[h]=k;return l}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),delete g[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return e}]}function qa(b,a){var c=b;M&&(Y.setAttribute("href",c),c=Y.href);Y.setAttribute("href",c);return{href:Y.href,protocol:Y.protocol?Y.protocol.replace(/:$/,
""):"",host:Y.host,search:Y.search?Y.search.replace(/^\?/,""):"",hash:Y.hash?Y.hash.replace(/^#/,""):"",hostname:Y.hostname,port:Y.port,pathname:"/"===Y.pathname.charAt(0)?Y.pathname:"/"+Y.pathname}}function Fb(b){b=D(b)?qa(b):b;return b.protocol===zc.protocol&&b.host===zc.host}function Hd(){this.$get=$(Z)}function Ac(b){function a(d,e){if(X(d)){var g={};q(d,function(b,c){g[c]=a(c,b)});return g}return b.factory(d+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+
c)}}];a("currency",Bc);a("date",Cc);a("filter",Id);a("json",Jd);a("limitTo",Kd);a("lowercase",Ld);a("number",Dc);a("orderBy",Ec);a("uppercase",Md)}function Id(){return function(b,a,c){if(!K(b))return b;var d=typeof c,e=[];e.check=function(a){for(var b=0;b<e.length;b++)if(!e[b](a))return!1;return!0};"function"!==d&&(c="boolean"===d&&c?function(a,b){return Na.equals(a,b)}:function(a,b){b=(""+b).toLowerCase();return-1<(""+a).toLowerCase().indexOf(b)});var g=function(a,b){if("string"==typeof b&&"!"===
b.charAt(0))return!g(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if("$"!==d.charAt(0)&&g(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(g(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var f in a)"$"==f?function(){if(a[f]){var b=f;e.push(function(c){return g(c,a[b])})}}():
function(){if("undefined"!=typeof a[f]){var b=f;e.push(function(c){return g(ub(c,b),a[b])})}}();break;case "function":e.push(a);break;default:return b}for(var d=[],h=0;h<b.length;h++){var m=b[h];e.check(m)&&d.push(m)}return d}}function Bc(b){var a=b.NUMBER_FORMATS;return function(b,d){z(d)&&(d=a.CURRENCY_SYM);return Fc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Dc(b){var a=b.NUMBER_FORMATS;return function(b,d){return Fc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}
function Fc(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var g=0>b;b=Math.abs(b);var f=b+"",h="",m=[],k=!1;if(-1!==f.indexOf("e")){var l=f.match(/([\d\.]+)e(-?)(\d+)/);l&&"-"==l[2]&&l[3]>e+1?f="0":(h=f,k=!0)}if(k)0<e&&(-1<b&&1>b)&&(h=b.toFixed(e));else{f=(f.split(Gc)[1]||"").length;z(e)&&(e=Math.min(Math.max(a.minFrac,f),a.maxFrac));f=Math.pow(10,e);b=Math.round(b*f)/f;b=(""+b).split(Gc);f=b[0];b=b[1]||"";var l=0,n=a.lgSize,p=a.gSize;if(f.length>=n+p)for(l=f.length-n,k=0;k<l;k++)0===(l-k)%p&&0!==
k&&(h+=c),h+=f.charAt(k);for(k=l;k<f.length;k++)0===(f.length-k)%n&&0!==k&&(h+=c),h+=f.charAt(k);for(;b.length<e;)b+="0";e&&"0"!==e&&(h+=d+b.substr(0,e))}m.push(g?a.negPre:a.posPre);m.push(h);m.push(g?a.negSuf:a.posSuf);return m.join("")}function Lb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function W(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return Lb(e,a,d)}}function kb(b,a){return function(c,
d){var e=c["get"+b](),g=Ga(a?"SHORT"+b:b);return d[g][e]}}function Cc(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var g=0,f=0,h=b[8]?a.setUTCFullYear:a.setFullYear,m=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=S(b[9]+b[10]),f=S(b[9]+b[11]));h.call(a,S(b[1]),S(b[2])-1,S(b[3]));g=S(b[4]||0)-g;f=S(b[5]||0)-f;h=S(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));m.call(a,g,f,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
return function(c,e){var g="",f=[],h,m;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;D(c)&&(c=Nd.test(c)?S(c):a(c));rb(c)&&(c=new Date(c));if(!Ja(c))return c;for(;e;)(m=Od.exec(e))?(f=f.concat(va.call(m,1)),e=f.pop()):(f.push(e),e=null);q(f,function(a){h=Pd[a];g+=h?h(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Jd(){return function(b){return pa(b,!0)}}function Kd(){return function(b,a){if(!K(b)&&!D(b))return b;a=S(a);if(D(b))return a?0<=a?b.slice(0,a):b.slice(a,
b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);0<a?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Ec(b){return function(a,c,d){function e(a,b){return Ma(b)?function(b,c){return a(c,b)}:a}if(!K(a)||!c)return a;c=K(c)?c:[c];c=Qc(c,function(a){var c=!1,d=a||Aa;if(D(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))c="-"==a.charAt(0),a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,g=typeof e;f==g?("string"==f&&(c=
c.toLowerCase(),e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<g?-1:1;return c},c)});for(var g=[],f=0;f<a.length;f++)g.push(a[f]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(0!==e)return e}return 0},d))}}function ta(b){L(b)&&(b={link:b});b.restrict=b.restrict||"AC";return $(b)}function Hc(b,a){function c(a,c){c=c?"-"+cb(c,"-"):"";b.removeClass((a?lb:mb)+c).addClass((a?mb:lb)+c)}var d=this,e=b.parent().controller("form")||nb,g=0,f=d.$error={},h=[];d.$name=a.name||a.ngForm;
d.$dirty=!1;d.$pristine=!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(Ha);c(!0);d.$addControl=function(a){xa(a.$name,"input");h.push(a);a.$name&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];q(f,function(b,c){d.$setValidity(c,!0,a)});Ka(h,a)};d.$setValidity=function(a,b,h){var n=f[a];if(b)n&&(Ka(n,h),n.length||(g--,g||(c(b),d.$valid=!0,d.$invalid=!1),f[a]=!1,c(!0,a),e.$setValidity(a,!0,d)));else{g||c(b);if(n){if(-1!=ab(n,h))return}else f[a]=n=[],
g++,c(!1,a),e.$setValidity(a,!1,d);n.push(h);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(Ha).addClass(ob);d.$dirty=!0;d.$pristine=!1;e.$setDirty()};d.$setPristine=function(){b.removeClass(ob).addClass(Ha);d.$dirty=!1;d.$pristine=!0;q(h,function(a){a.$setPristine()})}}function pb(b,a,c,d,e,g){if(!e.android){var f=!1;a.on("compositionstart",function(a){f=!0});a.on("compositionend",function(){f=!1})}var h=function(){if(!f){var e=a.val();Ma(c.ngTrim||"T")&&(e=aa(e));d.$viewValue!==
e&&(b.$$phase?d.$setViewValue(e):b.$apply(function(){d.$setViewValue(e)}))}};if(e.hasEvent("input"))a.on("input",h);else{var m,k=function(){m||(m=g.defer(function(){h();m=null}))};a.on("keydown",function(a){a=a.keyCode;91===a||(15<a&&19>a||37<=a&&40>=a)||k()});if(e.hasEvent("paste"))a.on("paste cut",k)}a.on("change",h);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)};var l=c.ngPattern,n=function(a,b){if(d.$isEmpty(b)||a.test(b))return d.$setValidity("pattern",!0),b;d.$setValidity("pattern",
!1);return r};l&&((e=l.match(/^\/(.*)\/([gim]*)$/))?(l=RegExp(e[1],e[2]),e=function(a){return n(l,a)}):e=function(c){var d=b.$eval(l);if(!d||!d.test)throw F("ngPattern")("noregexp",l,d,ga(a));return n(d,c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var p=S(c.ngMinlength);e=function(a){if(!d.$isEmpty(a)&&a.length<p)return d.$setValidity("minlength",!1),r;d.$setValidity("minlength",!0);return a};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var s=S(c.ngMaxlength);e=function(a){if(!d.$isEmpty(a)&&
a.length>s)return d.$setValidity("maxlength",!1),r;d.$setValidity("maxlength",!0);return a};d.$parsers.push(e);d.$formatters.push(e)}}function Mb(b,a){b="ngClass"+b;return function(){return{restrict:"AC",link:function(c,d,e){function g(b){if(!0===a||c.$index%2===a){var d=f(b||"");h?ua(b,h)||e.$updateClass(d,f(h)):e.$addClass(d)}h=fa(b)}function f(a){if(K(a))return a.join(" ");if(X(a)){var b=[];q(a,function(a,c){a&&b.push(c)});return b.join(" ")}return a}var h;c.$watch(e[b],g,!0);e.$observe("class",
function(a){g(c.$eval(e[b]))});"ngClass"!==b&&c.$watch("$index",function(d,g){var h=d&1;if(h!==g&1){var n=f(c.$eval(e[b]));h===a?e.$addClass(n):e.$removeClass(n)}})}}}}var x=function(b){return D(b)?b.toLowerCase():b},Ga=function(b){return D(b)?b.toUpperCase():b},M,A,Ba,va=[].slice,Qd=[].push,Za=Object.prototype.toString,La=F("ng"),Na=Z.angular||(Z.angular={}),Ta,Fa,ka=["0","0","0"];M=S((/msie (\d+)/.exec(x(navigator.userAgent))||[])[1]);isNaN(M)&&(M=S((/trident\/.*; rv:(\d+)/.exec(x(navigator.userAgent))||
[])[1]));w.$inject=[];Aa.$inject=[];var aa=function(){return String.prototype.trim?function(b){return D(b)?b.trim():b}:function(b){return D(b)?b.replace(/^\s\s*/,"").replace(/\s\s*$/,""):b}}();Fa=9>M?function(b){b=b.nodeName?b:b[0];return b.scopeName&&"HTML"!=b.scopeName?Ga(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var Tc=/[A-Z]/g,Rd={full:"1.2.7",major:1,minor:2,dot:7,codeName:"emoji-clairvoyance"},Qa=O.cache={},db=O.expando="ng-"+(new Date).getTime(),
Xc=1,Ic=Z.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},Ab=Z.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},Vc=/([\:\-\_]+(.))/g,Wc=/^moz([A-Z])/,xb=F("jqLite"),Ea=O.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===Q.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),O(Z).on("load",a))},toString:function(){var b=[];q(this,function(a){b.push(""+
a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?A(this[b]):A(this[this.length+b])},length:0,push:Qd,sort:[].sort,splice:[].splice},fb={};q("multiple selected checked disabled readOnly required open".split(" "),function(b){fb[x(b)]=b});var fc={};q("input select option textarea button form details".split(" "),function(b){fc[Ga(b)]=!0});q({data:bc,inheritedData:eb,scope:function(b){return A(b).data("$scope")||eb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return A(b).data("$isolateScope")||
A(b).data("$isolateScopeNoTemplate")},controller:cc,injector:function(b){return eb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Bb,css:function(b,a,c){a=Pa(a);if(B(c))b.style[a]=c;else{var d;8>=M&&(d=b.currentStyle&&b.currentStyle[a],""===d&&(d="auto"));d=d||b.style[a];8>=M&&(d=""===d?r:d);return d}},attr:function(b,a,c){var d=x(a);if(fb[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||w).specified?
d:r;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?r:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:function(){function b(b,d){var e=a[b.nodeType];if(z(d))return e?b[e]:"";b[e]=d}var a=[];9>M?(a[1]="innerText",a[3]="nodeValue"):a[1]=a[3]="textContent";b.$dv="";return b}(),val:function(b,a){if(z(a)){if("SELECT"===Fa(b)&&b.multiple){var c=[];q(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=
a},html:function(b,a){if(z(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)Ca(d[c]);b.innerHTML=a},empty:dc},function(b,a){O.prototype[a]=function(a,d){var e,g;if(b!==dc&&(2==b.length&&b!==Bb&&b!==cc?a:d)===r){if(X(a)){for(e=0;e<this.length;e++)if(b===bc)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}e=b.$dv;g=e===r?Math.min(this.length,1):this.length;for(var f=0;f<g;f++){var h=b(this[f],a,d);e=e?e+h:h}return e}for(e=0;e<this.length;e++)b(this[e],a,d);return this}});q({removeData:$b,
dealoc:Ca,on:function a(c,d,e,g){if(B(g))throw xb("onargs");var f=la(c,"events"),h=la(c,"handle");f||la(c,"events",f={});h||la(c,"handle",h=Yc(c,f));q(d.split(" "),function(d){var g=f[d];if(!g){if("mouseenter"==d||"mouseleave"==d){var l=Q.body.contains||Q.body.compareDocumentPosition?function(a,c){var d=9===a.nodeType?a.documentElement:a,e=c&&c.parentNode;return a===e||!!(e&&1===e.nodeType&&(d.contains?d.contains(e):a.compareDocumentPosition&&a.compareDocumentPosition(e)&16))}:function(a,c){if(c)for(;c=
c.parentNode;)if(c===a)return!0;return!1};f[d]=[];a(c,{mouseleave:"mouseout",mouseenter:"mouseover"}[d],function(a){var c=a.relatedTarget;c&&(c===this||l(this,c))||h(a,d)})}else Ic(c,d,h),f[d]=[];g=f[d]}g.push(e)})},off:ac,one:function(a,c,d){a=A(a);a.on(c,function g(){a.off(c,d);a.off(c,g)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;Ca(a);q(new O(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];q(a.childNodes,function(a){1===
a.nodeType&&c.push(a)});return c},contents:function(a){return a.childNodes||[]},append:function(a,c){q(new O(c),function(c){1!==a.nodeType&&11!==a.nodeType||a.appendChild(c)})},prepend:function(a,c){if(1===a.nodeType){var d=a.firstChild;q(new O(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=A(c)[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){Ca(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;q(new O(c),function(a){e.insertBefore(a,
d.nextSibling);d=a})},addClass:Db,removeClass:Cb,toggleClass:function(a,c,d){z(d)&&(d=!Bb(a,c));(d?Db:Cb)(a,c)},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;null!=a&&1!==a.nodeType;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:zb,triggerHandler:function(a,c,d){c=(la(a,"events")||{})[c];d=d||[];var e=[{preventDefault:w,stopPropagation:w}];
q(c,function(c){c.apply(a,e.concat(d))})}},function(a,c){O.prototype[c]=function(c,e,g){for(var f,h=0;h<this.length;h++)z(f)?(f=a(this[h],c,e,g),B(f)&&(f=A(f))):yb(f,a(this[h],c,e,g));return B(f)?f:this};O.prototype.bind=O.prototype.on;O.prototype.unbind=O.prototype.off});Ra.prototype={put:function(a,c){this[Da(a)]=c},get:function(a){return this[Da(a)]},remove:function(a){var c=this[a=Da(a)];delete this[a];return c}};var $c=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,ad=/,/,bd=/^\s*(_?)(\S+?)\1\s*$/,Zc=
/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Sa=F("$injector"),Sd=F("$animate"),Td=["$provide",function(a){this.$$selectors={};this.register=function(c,d){var e=c+"-animation";if(c&&"."!=c.charAt(0))throw Sd("notcsel",c);this.$$selectors[c.substr(1)]=e;a.factory(e,d)};this.classNameFilter=function(a){1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null);return this.$$classNameFilter};this.$get=["$timeout",function(a){return{enter:function(d,e,g,f){g?g.after(d):(e&&e[0]||(e=g.parent()),e.append(d));
f&&a(f,0,!1)},leave:function(d,e){d.remove();e&&a(e,0,!1)},move:function(a,c,g,f){this.enter(a,c,g,f)},addClass:function(d,e,g){e=D(e)?e:K(e)?e.join(" "):"";q(d,function(a){Db(a,e)});g&&a(g,0,!1)},removeClass:function(d,e,g){e=D(e)?e:K(e)?e.join(" "):"";q(d,function(a){Cb(a,e)});g&&a(g,0,!1)},enabled:w}}]}],ja=F("$compile");ic.$inject=["$provide","$$sanitizeUriProvider"];var hd=/^(x[\:\-_]|data[\:\-_])/i,oc=F("$interpolate"),Ud=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,sd={http:80,https:443,ftp:21},Hb=F("$location");
tc.prototype=Ib.prototype=sc.prototype={$$html5:!1,$$replace:!1,absUrl:ib("$$absUrl"),url:function(a,c){if(z(a))return this.$$url;var d=Ud.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));(d[2]||d[1])&&this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:ib("$$protocol"),host:ib("$$host"),port:ib("$$port"),path:uc("$$path",function(a){return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(D(a))this.$$search=Wb(a);else if(X(a))this.$$search=
a;else throw Hb("isrcharg");break;default:z(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:uc("$$hash",Aa),replace:function(){this.$$replace=!0;return this}};var ya=F("$parse"),xc={},ra,Ia={"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:w,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:r},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,c,d,e){return d(a,
c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":w,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,
c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Vd={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Kb=function(a){this.options=a};Kb.prototype={constructor:Kb,lex:function(a){this.text=a;this.index=0;this.ch=r;this.lastCh=":";this.tokens=[];var c;for(a=[];this.index<this.text.length;){this.ch=this.text.charAt(this.index);
if(this.is("\"'"))this.readString(this.ch);else if(this.isNumber(this.ch)||this.is(".")&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(this.ch))this.readIdent(),this.was("{,")&&("{"===a[0]&&(c=this.tokens[this.tokens.length-1]))&&(c.json=-1===c.text.indexOf("."));else if(this.is("(){}[].,;:?"))this.tokens.push({index:this.index,text:this.ch,json:this.was(":[,")&&this.is("{[")||this.is("}]:,")}),this.is("{[")&&a.unshift(this.ch),this.is("}]")&&a.shift(),this.index++;else if(this.isWhitespace(this.ch)){this.index++;
continue}else{var d=this.ch+this.peek(),e=d+this.peek(2),g=Ia[this.ch],f=Ia[d],h=Ia[e];h?(this.tokens.push({index:this.index,text:e,fn:h}),this.index+=3):f?(this.tokens.push({index:this.index,text:d,fn:f}),this.index+=2):g?(this.tokens.push({index:this.index,text:this.ch,fn:g,json:this.was("[,:")&&this.is("+-")}),this.index+=1):this.throwError("Unexpected next character ",this.index,this.index+1)}this.lastCh=this.ch}return this.tokens},is:function(a){return-1!==a.indexOf(this.ch)},was:function(a){return-1!==
a.indexOf(this.lastCh)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=B(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+
"]":" "+d;throw ya("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=x(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}a*=1;this.tokens.push({index:c,
text:a,json:!0,fn:function(){return a}})},readIdent:function(){for(var a=this,c="",d=this.index,e,g,f,h;this.index<this.text.length;){h=this.text.charAt(this.index);if("."===h||this.isIdent(h)||this.isNumber(h))"."===h&&(e=this.index),c+=h;else break;this.index++}if(e)for(g=this.index;g<this.text.length;){h=this.text.charAt(g);if("("===h){f=c.substr(e-d+1);c=c.substr(0,e-d);this.index=g;break}if(this.isWhitespace(h))g++;else break}d={index:d,text:c};if(Ia.hasOwnProperty(c))d.fn=Ia[c],d.json=Ia[c];
else{var m=wc(c,this.options,this.text);d.fn=t(function(a,c){return m(a,c)},{assign:function(d,e){return jb(d,c,e,a.text,a.options)}})}this.tokens.push(d);f&&(this.tokens.push({index:e,text:".",json:!1}),this.tokens.push({index:e+1,text:f,json:!1}))},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,g=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),e=e+f;if(g)"u"===f?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+
f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d=(g=Vd[f])?d+g:d+f,g=!1;else if("\\"===f)g=!0;else{if(f===a){this.index++;this.tokens.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}d+=f}this.index++}this.throwError("Unterminated quote",c)}};var Xa=function(a,c,d){this.lexer=a;this.$filter=c;this.options=d};Xa.ZERO=function(){return 0};Xa.prototype={constructor:Xa,parse:function(a,c){this.text=a;this.json=c;this.tokens=this.lexer.lex(a);c&&(this.assignment=this.logicalOR,
this.functionCall=this.fieldAccess=this.objectIndex=this.filterChain=function(){this.throwError("is not valid json",{text:a,index:0})});var d=c?this.primary():this.statements();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);d.literal=!!d.literal;d.constant=!!d.constant;return d},primary:function(){var a;if(this.expect("("))a=this.filterChain(),this.consume(")");else if(this.expect("["))a=this.arrayDeclaration();else if(this.expect("{"))a=this.object();else{var c=
this.expect();(a=c.fn)||this.throwError("not a primary expression",c);c.json&&(a.constant=!0,a.literal=!0)}for(var d;c=this.expect("(","[",".");)"("===c.text?(a=this.functionCall(a,d),d=null):"["===c.text?(d=a,a=this.objectIndex(a)):"."===c.text?(d=a,a=this.fieldAccess(a)):this.throwError("IMPOSSIBLE");return a},throwError:function(a,c){throw ya("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},peekToken:function(){if(0===this.tokens.length)throw ya("ueoe",this.text);return this.tokens[0]},
peek:function(a,c,d,e){if(0<this.tokens.length){var g=this.tokens[0],f=g.text;if(f===a||f===c||f===d||f===e||!(a||c||d||e))return g}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.json&&!a.json&&this.throwError("is not valid json",a),this.tokens.shift(),a):!1},consume:function(a){this.expect(a)||this.throwError("is unexpected, expecting ["+a+"]",this.peek())},unaryFn:function(a,c){return t(function(d,e){return a(d,e,c)},{constant:c.constant})},ternaryFn:function(a,c,d){return t(function(e,
g){return a(e,g)?c(e,g):d(e,g)},{constant:a.constant&&c.constant&&d.constant})},binaryFn:function(a,c,d){return t(function(e,g){return c(e,g,a,d)},{constant:a.constant&&d.constant})},statements:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.filterChain()),!this.expect(";"))return 1===a.length?a[0]:function(c,d){for(var e,g=0;g<a.length;g++){var f=a[g];f&&(e=f(c,d))}return e}},filterChain:function(){for(var a=this.expression(),c;;)if(c=this.expect("|"))a=
this.binaryFn(a,c.fn,this.filter());else return a},filter:function(){for(var a=this.expect(),c=this.$filter(a.text),d=[];;)if(a=this.expect(":"))d.push(this.expression());else{var e=function(a,e,h){h=[h];for(var m=0;m<d.length;m++)h.push(d[m](a,e));return c.apply(a,h)};return function(){return e}}},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary(),c,d;return(d=this.expect("="))?(a.assign||this.throwError("implies assignment but ["+this.text.substring(0,d.index)+
"] can not be assigned to",d),c=this.ternary(),function(d,g){return a.assign(d,c(d,g),g)}):a},ternary:function(){var a=this.logicalOR(),c,d;if(this.expect("?")){c=this.ternary();if(d=this.expect(":"))return this.ternaryFn(a,c,this.ternary());this.throwError("expected :",d)}else return a},logicalOR:function(){for(var a=this.logicalAND(),c;;)if(c=this.expect("||"))a=this.binaryFn(a,c.fn,this.logicalAND());else return a},logicalAND:function(){var a=this.equality(),c;if(c=this.expect("&&"))a=this.binaryFn(a,
c.fn,this.logicalAND());return a},equality:function(){var a=this.relational(),c;if(c=this.expect("==","!=","===","!=="))a=this.binaryFn(a,c.fn,this.equality());return a},relational:function(){var a=this.additive(),c;if(c=this.expect("<",">","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*",
"/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(Xa.ZERO,a.fn,this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this,d=this.expect().text,e=wc(d,this.options,this.text);return t(function(c,d,h){return e(h||a(c,d),d)},{assign:function(e,f,h){return jb(a(e,h),d,f,c.text,c.options)}})},objectIndex:function(a){var c=this,d=this.expression();
this.consume("]");return t(function(e,g){var f=a(e,g),h=d(e,g),m;if(!f)return r;(f=Wa(f[h],c.text))&&(f.then&&c.options.unwrapPromises)&&(m=f,"$$v"in f||(m.$$v=r,m.then(function(a){m.$$v=a})),f=f.$$v);return f},{assign:function(e,g,f){var h=d(e,f);return Wa(a(e,f),c.text)[h]=g}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this;return function(g,f){for(var h=[],m=c?c(g,f):g,k=0;k<d.length;k++)h.push(d[k](g,
f));k=a(g,f,m)||w;Wa(m,e.text);Wa(k,e.text);h=k.apply?k.apply(m,h):k(h[0],h[1],h[2],h[3],h[4]);return Wa(h,e.text)}},arrayDeclaration:function(){var a=[],c=!0;if("]"!==this.peekToken().text){do{var d=this.expression();a.push(d);d.constant||(c=!1)}while(this.expect(","))}this.consume("]");return t(function(c,d){for(var f=[],h=0;h<a.length;h++)f.push(a[h](c,d));return f},{literal:!0,constant:c})},object:function(){var a=[],c=!0;if("}"!==this.peekToken().text){do{var d=this.expect(),d=d.string||d.text;
this.consume(":");var e=this.expression();a.push({key:d,value:e});e.constant||(c=!1)}while(this.expect(","))}this.consume("}");return t(function(c,d){for(var e={},m=0;m<a.length;m++){var k=a[m];e[k.key]=k.value(c,d)}return e},{literal:!0,constant:c})}};var Jb={},sa=F("$sce"),ea={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},Y=Q.createElement("a"),zc=qa(Z.location.href,!0);Ac.$inject=["$provide"];Bc.$inject=["$locale"];Dc.$inject=["$locale"];var Gc=".",Pd={yyyy:W("FullYear",4),
yy:W("FullYear",2,0,!0),y:W("FullYear",1),MMMM:kb("Month"),MMM:kb("Month",!0),MM:W("Month",2,1),M:W("Month",1,1),dd:W("Date",2),d:W("Date",1),HH:W("Hours",2),H:W("Hours",1),hh:W("Hours",2,-12),h:W("Hours",1,-12),mm:W("Minutes",2),m:W("Minutes",1),ss:W("Seconds",2),s:W("Seconds",1),sss:W("Milliseconds",3),EEEE:kb("Day"),EEE:kb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Lb(Math[0<a?"floor":"ceil"](a/60),2)+
Lb(Math.abs(a%60),2))}},Od=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,Nd=/^\-?\d+$/;Cc.$inject=["$locale"];var Ld=$(x),Md=$(Ga);Ec.$inject=["$parse"];var Wd=$({restrict:"E",compile:function(a,c){8>=M&&(c.href||c.name||c.$set("href",""),a.append(Q.createComment("IE fix")));if(!c.href&&!c.name)return function(a,c){c.on("click",function(a){c.attr("href")||a.preventDefault()})}}}),Nb={};q(fb,function(a,c){if("multiple"!=a){var d=ma("ng-"+c);Nb[d]=function(){return{priority:100,
compile:function(){return function(a,g,f){a.$watch(f[d],function(a){f.$set(c,!!a)})}}}}}});q(["src","srcset","href"],function(a){var c=ma("ng-"+a);Nb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),M&&e.prop(a,g[a]))})}}}});var nb={$addControl:w,$removeControl:w,$setValidity:w,$setDirty:w,$setPristine:w};Hc.$inject=["$element","$attrs","$scope"];var Jc=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:Hc,compile:function(){return{pre:function(a,
e,g,f){if(!g.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};Ic(e[0],"submit",h);e.on("$destroy",function(){c(function(){Ab(e[0],"submit",h)},0,!1)})}var m=e.parent().controller("form"),k=g.name||g.ngForm;k&&jb(a,k,f,k);if(m)e.on("$destroy",function(){m.$removeControl(f);k&&jb(a,k,r,k);t(f,nb)})}}}}}]},Xd=Jc(),Yd=Jc(!0),Zd=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,$d=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/,ae=
/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,Kc={text:pb,number:function(a,c,d,e,g,f){pb(a,c,d,e,g,f);e.$parsers.push(function(a){var c=e.$isEmpty(a);if(c||ae.test(a))return e.$setValidity("number",!0),""===a?null:c?a:parseFloat(a);e.$setValidity("number",!1);return r});e.$formatters.push(function(a){return e.$isEmpty(a)?"":""+a});d.min&&(a=function(a){var c=parseFloat(d.min);if(!e.$isEmpty(a)&&a<c)return e.$setValidity("min",!1),r;e.$setValidity("min",!0);return a},e.$parsers.push(a),e.$formatters.push(a));
d.max&&(a=function(a){var c=parseFloat(d.max);if(!e.$isEmpty(a)&&a>c)return e.$setValidity("max",!1),r;e.$setValidity("max",!0);return a},e.$parsers.push(a),e.$formatters.push(a));e.$formatters.push(function(a){if(e.$isEmpty(a)||rb(a))return e.$setValidity("number",!0),a;e.$setValidity("number",!1);return r})},url:function(a,c,d,e,g,f){pb(a,c,d,e,g,f);a=function(a){if(e.$isEmpty(a)||Zd.test(a))return e.$setValidity("url",!0),a;e.$setValidity("url",!1);return r};e.$formatters.push(a);e.$parsers.push(a)},
email:function(a,c,d,e,g,f){pb(a,c,d,e,g,f);a=function(a){if(e.$isEmpty(a)||$d.test(a))return e.$setValidity("email",!0),a;e.$setValidity("email",!1);return r};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){z(d.name)&&c.attr("name",Ya());c.on("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,f=d.ngFalseValue;D(g)||
(g=!0);D(f)||(f=!1);c.on("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==g};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:f})},hidden:w,button:w,submit:w,reset:w},Lc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,f){f&&(Kc[x(g.type)]||Kc.text)(d,e,g,f,c,a)}}}],mb="ng-valid",lb="ng-invalid",Ha="ng-pristine",
ob="ng-dirty",be=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function f(a,c){c=c?"-"+cb(c,"-"):"";e.removeClass((a?lb:mb)+c).addClass((a?mb:lb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var h=g(d.ngModel),m=h.assign;if(!m)throw F("ngModel")("nonassign",d.ngModel,ga(e));this.$render=w;this.$isEmpty=function(a){return z(a)||
""===a||null===a||a!==a};var k=e.inheritedData("$formController")||nb,l=0,n=this.$error={};e.addClass(Ha);f(!0);this.$setValidity=function(a,c){n[a]!==!c&&(c?(n[a]&&l--,l||(f(!0),this.$valid=!0,this.$invalid=!1)):(f(!1),this.$invalid=!0,this.$valid=!1,l++),n[a]=!c,f(c,a),k.$setValidity(a,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(ob).addClass(Ha)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&(this.$dirty=!0,this.$pristine=!1,e.removeClass(Ha).addClass(ob),
k.$setDirty());q(this.$parsers,function(a){d=a(d)});this.$modelValue!==d&&(this.$modelValue=d,m(a,d),q(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}}))};var p=this;a.$watch(function(){var c=h(a);if(p.$modelValue!==c){var d=p.$formatters,e=d.length;for(p.$modelValue=c;e--;)c=d[e](c);p.$viewValue!==c&&(p.$viewValue=c,p.$render())}return c})}],ce=function(){return{require:["ngModel","^?form"],controller:be,link:function(a,c,d,e){var g=e[0],f=e[1]||nb;f.$addControl(g);a.$on("$destroy",
function(){f.$removeControl(g)})}}},de=$({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Mc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&e.$isEmpty(a))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},ee=function(){return{require:"ngModel",link:function(a,
c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){if(!z(a)){var c=[];a&&q(a.split(g),function(a){a&&c.push(aa(a))});return c}});e.$formatters.push(function(a){return K(a)?a.join(", "):r});e.$isEmpty=function(a){return!a||!a.length}}}},fe=/^(true|false|\d+)$/,ge=function(){return{priority:100,compile:function(a,c){return fe.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",
a)})}}}},he=ta(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==r?"":a)})}),ie=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],je=["$sce","$parse",function(a,c){return function(d,e,g){e.addClass("ng-binding").data("$binding",g.ngBindHtml);var f=c(g.ngBindHtml);d.$watch(function(){return(f(d)||"").toString()},
function(c){e.html(a.getTrustedHtml(f(d))||"")})}}],ke=Mb("",!0),le=Mb("Odd",0),me=Mb("Even",1),ne=ta({compile:function(a,c){c.$set("ngCloak",r);a.removeClass("ng-cloak")}}),oe=[function(){return{scope:!0,controller:"@",priority:500}}],Nc={};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ma("ng-"+a);Nc[c]=["$parse",function(d){return{compile:function(e,g){var f=d(g[c]);return function(c,
d,e){d.on(x(a),function(a){c.$apply(function(){f(c,{$event:a})})})}}}}]});var pe=["$animate",function(a){return{transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,g,f){var h,m;c.$watch(e.ngIf,function(g){Ma(g)?m||(m=c.$new(),f(m,function(c){c[c.length++]=Q.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)})):(m&&(m.$destroy(),m=null),h&&(a.leave(vb(h.clone)),h=null))})}}}],qe=["$http","$templateCache","$anchorScroll","$animate","$sce",
function(a,c,d,e,g){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:Na.noop,compile:function(f,h){var m=h.ngInclude||h.src,k=h.onload||"",l=h.autoscroll;return function(f,h,q,r,y){var A=0,u,t,H=function(){u&&(u.$destroy(),u=null);t&&(e.leave(t),t=null)};f.$watch(g.parseAsResourceUrl(m),function(g){var m=function(){!B(l)||l&&!f.$eval(l)||d()},q=++A;g?(a.get(g,{cache:c}).success(function(a){if(q===A){var c=f.$new();r.template=a;a=y(c,function(a){H();e.enter(a,null,h,m)});
u=c;t=a;u.$emit("$includeContentLoaded");f.$eval(k)}}).error(function(){q===A&&H()}),f.$emit("$includeContentRequested")):(H(),r.template=null)})}}}}],re=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,g){d.html(g.template);a(d.contents())(c)}}}],se=ta({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),te=ta({terminal:!0,priority:1E3}),ue=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,
g,f){var h=f.count,m=f.$attr.when&&g.attr(f.$attr.when),k=f.offset||0,l=e.$eval(m)||{},n={},p=c.startSymbol(),s=c.endSymbol(),r=/^when(Minus)?(.+)$/;q(f,function(a,c){r.test(c)&&(l[x(c.replace("when","").replace("Minus","-"))]=g.attr(f.$attr[c]))});q(l,function(a,e){n[e]=c(a.replace(d,p+h+"-"+k+s))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in l||(c=a.pluralCat(c-k));return n[c](e,g,!0)},function(a){g.text(a)})}}}],ve=["$parse","$animate",function(a,c){var d=F("ngRepeat");
return{transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,link:function(e,g,f,h,m){var k=f.ngRepeat,l=k.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),n,p,s,r,y,t,u={$id:Da};if(!l)throw d("iexp",k);f=l[1];h=l[2];(l=l[3])?(n=a(l),p=function(a,c,d){t&&(u[t]=a);u[y]=c;u.$index=d;return n(e,u)}):(s=function(a,c){return Da(c)},r=function(a){return a});l=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!l)throw d("iidexp",f);y=l[3]||l[1];t=l[2];var B={};e.$watchCollection(h,
function(a){var f,h,l=g[0],n,u={},z,P,D,x,T,w,F=[];if(qb(a))T=a,n=p||s;else{n=p||r;T=[];for(D in a)a.hasOwnProperty(D)&&"$"!=D.charAt(0)&&T.push(D);T.sort()}z=T.length;h=F.length=T.length;for(f=0;f<h;f++)if(D=a===T?f:T[f],x=a[D],x=n(D,x,f),xa(x,"`track by` id"),B.hasOwnProperty(x))w=B[x],delete B[x],u[x]=w,F[f]=w;else{if(u.hasOwnProperty(x))throw q(F,function(a){a&&a.scope&&(B[a.id]=a)}),d("dupes",k,x);F[f]={id:x};u[x]=!1}for(D in B)B.hasOwnProperty(D)&&(w=B[D],f=vb(w.clone),c.leave(f),q(f,function(a){a.$$NG_REMOVED=
!0}),w.scope.$destroy());f=0;for(h=T.length;f<h;f++){D=a===T?f:T[f];x=a[D];w=F[f];F[f-1]&&(l=F[f-1].clone[F[f-1].clone.length-1]);if(w.scope){P=w.scope;n=l;do n=n.nextSibling;while(n&&n.$$NG_REMOVED);w.clone[0]!=n&&c.move(vb(w.clone),null,A(l));l=w.clone[w.clone.length-1]}else P=e.$new();P[y]=x;t&&(P[t]=D);P.$index=f;P.$first=0===f;P.$last=f===z-1;P.$middle=!(P.$first||P.$last);P.$odd=!(P.$even=0===(f&1));w.scope||m(P,function(a){a[a.length++]=Q.createComment(" end ngRepeat: "+k+" ");c.enter(a,null,
A(l));l=a;w.scope=P;w.clone=a;u[w.id]=w})}B=u})}}}],we=["$animate",function(a){return function(c,d,e){c.$watch(e.ngShow,function(c){a[Ma(c)?"removeClass":"addClass"](d,"ng-hide")})}}],xe=["$animate",function(a){return function(c,d,e){c.$watch(e.ngHide,function(c){a[Ma(c)?"addClass":"removeClass"](d,"ng-hide")})}}],ye=ta(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),ze=["$animate",function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",
function(){this.cases={}}],link:function(c,d,e,g){var f,h,m=[];c.$watch(e.ngSwitch||e.on,function(d){for(var l=0,n=m.length;l<n;l++)m[l].$destroy(),a.leave(h[l]);h=[];m=[];if(f=g.cases["!"+d]||g.cases["?"])c.$eval(e.change),q(f,function(d){var e=c.$new();m.push(e);d.transclude(e,function(c){var e=d.element;h.push(c);a.enter(c,e.parent(),e)})})})}}}],Ae=ta({transclude:"element",priority:800,require:"^ngSwitch",compile:function(a,c){return function(a,e,g,f,h){f.cases["!"+c.ngSwitchWhen]=f.cases["!"+
c.ngSwitchWhen]||[];f.cases["!"+c.ngSwitchWhen].push({transclude:h,element:e})}}}),Be=ta({transclude:"element",priority:800,require:"^ngSwitch",link:function(a,c,d,e,g){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:g,element:c})}}),Ce=ta({controller:["$element","$transclude",function(a,c){if(!c)throw F("ngTransclude")("orphan",ga(a));this.$transclude=c}],link:function(a,c,d,e){e.$transclude(function(a){c.empty();c.append(a)})}}),De=["$templateCache",function(a){return{restrict:"E",terminal:!0,
compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],Ee=F("ngOptions"),Fe=$({terminal:!0}),Ge=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/,e={$setViewValue:w};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var m=this,k={},l=e,n;m.databound=d.ngModel;
m.init=function(a,c,d){l=a;n=d};m.addOption=function(c){xa(c,'"option value"');k[c]=!0;l.$viewValue==c&&(a.val(c),n.parent()&&n.remove())};m.removeOption=function(a){this.hasOption(a)&&(delete k[a],l.$viewValue==a&&this.renderUnknownOption(a))};m.renderUnknownOption=function(c){c="? "+Da(c)+" ?";n.val(c);a.prepend(n);a.val(c);n.prop("selected",!0)};m.hasOption=function(a){return k.hasOwnProperty(a)};c.$on("$destroy",function(){m.renderUnknownOption=w})}],link:function(e,f,h,m){function k(a,c,d,e){d.$render=
function(){var a=d.$viewValue;e.hasOption(a)?(x.parent()&&x.remove(),c.val(a),""===a&&w.prop("selected",!0)):z(a)&&w?c.val(""):e.renderUnknownOption(a)};c.on("change",function(){a.$apply(function(){x.parent()&&x.remove();d.$setViewValue(c.val())})})}function l(a,c,d){var e;d.$render=function(){var a=new Ra(d.$viewValue);q(c.find("option"),function(c){c.selected=B(a.get(c.value))})};a.$watch(function(){ua(e,d.$viewValue)||(e=fa(d.$viewValue),d.$render())});c.on("change",function(){a.$apply(function(){var a=
[];q(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function n(e,f,g){function h(){var a={"":[]},c=[""],d,k,r,t,v;t=g.$modelValue;v=A(e)||[];var C=n?Ob(v):v,F,I,z;I={};r=!1;var E,H;if(s)if(w&&K(t))for(r=new Ra([]),z=0;z<t.length;z++)I[l]=t[z],r.put(w(e,I),t[z]);else r=new Ra(t);for(z=0;F=C.length,z<F;z++){k=z;if(n){k=C[z];if("$"===k.charAt(0))continue;I[n]=k}I[l]=v[k];d=p(e,I)||"";(k=a[d])||(k=a[d]=[],c.push(d));s?d=B(r.remove(w?w(e,I):q(e,I))):(w?(d={},d[l]=t,d=
w(e,d)===w(e,I)):d=t===q(e,I),r=r||d);E=m(e,I);E=B(E)?E:"";k.push({id:w?w(e,I):n?C[z]:z,label:E,selected:d})}s||(y||null===t?a[""].unshift({id:"",label:"",selected:!r}):r||a[""].unshift({id:"?",label:"",selected:!0}));I=0;for(C=c.length;I<C;I++){d=c[I];k=a[d];x.length<=I?(t={element:D.clone().attr("label",d),label:k.label},v=[t],x.push(v),f.append(t.element)):(v=x[I],t=v[0],t.label!=d&&t.element.attr("label",t.label=d));E=null;z=0;for(F=k.length;z<F;z++)r=k[z],(d=v[z+1])?(E=d.element,d.label!==r.label&&
E.text(d.label=r.label),d.id!==r.id&&E.val(d.id=r.id),E[0].selected!==r.selected&&E.prop("selected",d.selected=r.selected)):(""===r.id&&y?H=y:(H=u.clone()).val(r.id).attr("selected",r.selected).text(r.label),v.push({element:H,label:r.label,id:r.id,selected:r.selected}),E?E.after(H):t.element.append(H),E=H);for(z++;v.length>z;)v.pop().element.remove()}for(;x.length>I;)x.pop()[0].element.remove()}var k;if(!(k=t.match(d)))throw Ee("iexp",t,ga(f));var m=c(k[2]||k[1]),l=k[4]||k[6],n=k[5],p=c(k[3]||""),
q=c(k[2]?k[1]:l),A=c(k[7]),w=k[8]?c(k[8]):null,x=[[{element:f,label:""}]];y&&(a(y)(e),y.removeClass("ng-scope"),y.remove());f.empty();f.on("change",function(){e.$apply(function(){var a,c=A(e)||[],d={},h,k,m,p,t,u,v;if(s)for(k=[],p=0,u=x.length;p<u;p++)for(a=x[p],m=1,t=a.length;m<t;m++){if((h=a[m].element)[0].selected){h=h.val();n&&(d[n]=h);if(w)for(v=0;v<c.length&&(d[l]=c[v],w(e,d)!=h);v++);else d[l]=c[h];k.push(q(e,d))}}else if(h=f.val(),"?"==h)k=r;else if(""===h)k=null;else if(w)for(v=0;v<c.length;v++){if(d[l]=
c[v],w(e,d)==h){k=q(e,d);break}}else d[l]=c[h],n&&(d[n]=h),k=q(e,d);g.$setViewValue(k)})});g.$render=h;e.$watch(h)}if(m[1]){var p=m[0];m=m[1];var s=h.multiple,t=h.ngOptions,y=!1,w,u=A(Q.createElement("option")),D=A(Q.createElement("optgroup")),x=u.clone();h=0;for(var v=f.children(),F=v.length;h<F;h++)if(""===v[h].value){w=y=v.eq(h);break}p.init(m,y,x);s&&(m.$isEmpty=function(a){return!a||0===a.length});t?n(e,f,m):s?l(e,f,m):k(e,f,m,p)}}}}],He=["$interpolate",function(a){var c={addOption:w,removeOption:w};
return{restrict:"E",priority:100,compile:function(d,e){if(z(e.value)){var g=a(d.text(),!0);g||e.$set("value",d.text())}return function(a,d,e){var k=d.parent(),l=k.data("$selectController")||k.parent().data("$selectController");l&&l.databound?d.prop("selected",!1):l=c;g?a.$watch(g,function(a,c){e.$set("value",a);a!==c&&l.removeOption(c);l.addOption(a)}):l.addOption(e.value);d.on("$destroy",function(){l.removeOption(e.value)})}}}}],Ie=$({restrict:"E",terminal:!0});(Ba=Z.jQuery)?(A=Ba,t(Ba.fn,{scope:Ea.scope,
isolateScope:Ea.isolateScope,controller:Ea.controller,injector:Ea.injector,inheritedData:Ea.inheritedData}),wb("remove",!0,!0,!1),wb("empty",!1,!1,!1),wb("html",!1,!1,!0)):A=O;Na.element=A;(function(a){t(a,{bootstrap:Yb,copy:fa,extend:t,equals:ua,element:A,forEach:q,injector:Zb,noop:w,bind:bb,toJson:pa,fromJson:Ub,identity:Aa,isUndefined:z,isDefined:B,isString:D,isFunction:L,isObject:X,isNumber:rb,isElement:Pc,isArray:K,version:Rd,isDate:Ja,lowercase:x,uppercase:Ga,callbacks:{counter:0},$$minErr:F,
$$csp:Tb});Ta=Uc(Z);try{Ta("ngLocale")}catch(c){Ta("ngLocale",[]).provider("$locale",rd)}Ta("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:Bd});a.provider("$compile",ic).directive({a:Wd,input:Lc,textarea:Lc,form:Xd,script:De,select:Ge,style:Ie,option:He,ngBind:he,ngBindHtml:je,ngBindTemplate:ie,ngClass:ke,ngClassEven:me,ngClassOdd:le,ngCloak:ne,ngController:oe,ngForm:Yd,ngHide:xe,ngIf:pe,ngInclude:qe,ngInit:se,ngNonBindable:te,ngPluralize:ue,ngRepeat:ve,ngShow:we,ngStyle:ye,ngSwitch:ze,
ngSwitchWhen:Ae,ngSwitchDefault:Be,ngOptions:Fe,ngTransclude:Ce,ngModel:ce,ngList:ee,ngChange:de,required:Mc,ngRequired:Mc,ngValue:ge}).directive({ngInclude:re}).directive(Nb).directive(Nc);a.provider({$anchorScroll:cd,$animate:Td,$browser:ed,$cacheFactory:fd,$controller:id,$document:jd,$exceptionHandler:kd,$filter:Ac,$interpolate:pd,$interval:qd,$http:ld,$httpBackend:nd,$location:td,$log:ud,$parse:xd,$rootScope:Ad,$q:yd,$sce:Ed,$sceDelegate:Dd,$sniffer:Fd,$templateCache:gd,$timeout:Gd,$window:Hd})}])})(Na);
A(Q).ready(function(){Sc(Q,Yb)})})(window,document);!angular.$$csp()&&angular.element(document).find("head").prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}</style>');

6
gui/default/vendor/bootstrap/config.json vendored Normal file → Executable file
View File

@@ -35,6 +35,9 @@
"@headings-font-weight": "500",
"@headings-line-height": "1.1",
"@headings-color": "inherit",
"@icon-font-path": "\"../fonts/\"",
"@icon-font-name": "\"glyphicons-halflings-regular\"",
"@icon-font-svg-id": "\"glyphicons_halflingsregular\"",
"@padding-base-vertical": "6px",
"@padding-base-horizontal": "12px",
"@padding-large-vertical": "10px",
@@ -375,6 +378,7 @@
"@blockquote-border-color": "@gray-lighter",
"@page-header-border-color": "@gray-lighter",
"@dl-horizontal-offset": "@component-offset-horizontal",
"@dl-horizontal-breakpoint": "@grid-float-breakpoint",
"@hr-border": "@gray-lighter"
},
"css": [
@@ -419,5 +423,5 @@
"collapse.js",
"transition.js"
],
"customizerUrl": "http://getbootstrap.com/customize/?id=7ee3aa2c980c52345937"
"customizerUrl": "http://getbootstrap.com/customize/?id=2534c630d2cba4ad9c65"
}

8
gui/default/vendor/bootstrap/css/bootstrap-theme.css vendored Normal file → Executable file
View File

@@ -1,15 +1,15 @@
/*!
* Bootstrap v3.3.5 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=7ee3aa2c980c52345937)
* Config saved to config.json and https://gist.github.com/7ee3aa2c980c52345937
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=2534c630d2cba4ad9c65)
* Config saved to config.json and https://gist.github.com/2534c630d2cba4ad9c65
*/
/*!
* Bootstrap v3.3.5 (http://getbootstrap.com)
* Bootstrap v3.3.6 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/

View File

File diff suppressed because one or more lines are too long

95
gui/default/vendor/bootstrap/css/bootstrap.css vendored Normal file → Executable file
View File

@@ -1,15 +1,15 @@
/*!
* Bootstrap v3.3.5 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=7ee3aa2c980c52345937)
* Config saved to config.json and https://gist.github.com/7ee3aa2c980c52345937
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=2534c630d2cba4ad9c65)
* Config saved to config.json and https://gist.github.com/2534c630d2cba4ad9c65
*/
/*!
* Bootstrap v3.3.5 (http://getbootstrap.com)
* Bootstrap v3.3.6 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
@@ -1711,6 +1711,10 @@ output {
.form-control::-webkit-input-placeholder {
color: #999999;
}
.form-control::-ms-expand {
border: 0;
background-color: transparent;
}
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
@@ -2117,7 +2121,7 @@ select[multiple].input-lg {
}
@media (min-width: 768px) {
.form-horizontal .form-group-lg .control-label {
padding-top: 14.333333px;
padding-top: 11px;
font-size: 20px;
}
}
@@ -2225,9 +2229,6 @@ fieldset[disabled] a.btn {
.open > .dropdown-toggle.btn-default {
background-image: none;
}
.btn-default.disabled,
.btn-default[disabled],
fieldset[disabled] .btn-default,
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
@@ -2236,13 +2237,7 @@ fieldset[disabled] .btn-default:hover,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus,
.btn-default.disabled:active,
.btn-default[disabled]:active,
fieldset[disabled] .btn-default:active,
.btn-default.disabled.active,
.btn-default[disabled].active,
fieldset[disabled] .btn-default.active {
fieldset[disabled] .btn-default.focus {
background-color: #ffffff;
border-color: #cccccc;
}
@@ -2291,9 +2286,6 @@ fieldset[disabled] .btn-default.active {
.open > .dropdown-toggle.btn-primary {
background-image: none;
}
.btn-primary.disabled,
.btn-primary[disabled],
fieldset[disabled] .btn-primary,
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
@@ -2302,13 +2294,7 @@ fieldset[disabled] .btn-primary:hover,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus,
.btn-primary.disabled:active,
.btn-primary[disabled]:active,
fieldset[disabled] .btn-primary:active,
.btn-primary.disabled.active,
.btn-primary[disabled].active,
fieldset[disabled] .btn-primary.active {
fieldset[disabled] .btn-primary.focus {
background-color: #3498db;
border-color: #258cd1;
}
@@ -2357,9 +2343,6 @@ fieldset[disabled] .btn-primary.active {
.open > .dropdown-toggle.btn-success {
background-image: none;
}
.btn-success.disabled,
.btn-success[disabled],
fieldset[disabled] .btn-success,
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
@@ -2368,13 +2351,7 @@ fieldset[disabled] .btn-success:hover,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus,
.btn-success.disabled:active,
.btn-success[disabled]:active,
fieldset[disabled] .btn-success:active,
.btn-success.disabled.active,
.btn-success[disabled].active,
fieldset[disabled] .btn-success.active {
fieldset[disabled] .btn-success.focus {
background-color: #2ecc71;
border-color: #29b765;
}
@@ -2423,9 +2400,6 @@ fieldset[disabled] .btn-success.active {
.open > .dropdown-toggle.btn-info {
background-image: none;
}
.btn-info.disabled,
.btn-info[disabled],
fieldset[disabled] .btn-info,
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
@@ -2434,13 +2408,7 @@ fieldset[disabled] .btn-info:hover,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus,
.btn-info.disabled:active,
.btn-info[disabled]:active,
fieldset[disabled] .btn-info:active,
.btn-info.disabled.active,
.btn-info[disabled].active,
fieldset[disabled] .btn-info.active {
fieldset[disabled] .btn-info.focus {
background-color: #9b59b6;
border-color: #8f4bab;
}
@@ -2489,9 +2457,6 @@ fieldset[disabled] .btn-info.active {
.open > .dropdown-toggle.btn-warning {
background-image: none;
}
.btn-warning.disabled,
.btn-warning[disabled],
fieldset[disabled] .btn-warning,
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
@@ -2500,13 +2465,7 @@ fieldset[disabled] .btn-warning:hover,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus,
.btn-warning.disabled:active,
.btn-warning[disabled]:active,
fieldset[disabled] .btn-warning:active,
.btn-warning.disabled.active,
.btn-warning[disabled].active,
fieldset[disabled] .btn-warning.active {
fieldset[disabled] .btn-warning.focus {
background-color: #f1c40f;
border-color: #dab10d;
}
@@ -2555,9 +2514,6 @@ fieldset[disabled] .btn-warning.active {
.open > .dropdown-toggle.btn-danger {
background-image: none;
}
.btn-danger.disabled,
.btn-danger[disabled],
fieldset[disabled] .btn-danger,
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
@@ -2566,13 +2522,7 @@ fieldset[disabled] .btn-danger:hover,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus,
.btn-danger.disabled:active,
.btn-danger[disabled]:active,
fieldset[disabled] .btn-danger:active,
.btn-danger.disabled.active,
.btn-danger[disabled].active,
fieldset[disabled] .btn-danger.active {
fieldset[disabled] .btn-danger.focus {
background-color: #e74c3c;
border-color: #e43725;
}
@@ -2947,13 +2897,15 @@ tbody.collapse.in {
}
.btn-group-vertical > .btn:first-child:not(:last-child) {
border-top-right-radius: 2px;
border-top-left-radius: 2px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.btn-group-vertical > .btn:last-child:not(:first-child) {
border-bottom-left-radius: 2px;
border-top-right-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
}
.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0;
@@ -3010,6 +2962,9 @@ tbody.collapse.in {
width: 100%;
margin-bottom: 0;
}
.input-group .form-control:focus {
z-index: 3;
}
.input-group-lg > .form-control,
.input-group-lg > .input-group-addon,
.input-group-lg > .input-group-btn > .btn {
@@ -3903,7 +3858,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
z-index: 3;
z-index: 2;
color: #1d6fa5;
background-color: #eeeeee;
border-color: #dddddd;
@@ -3914,7 +3869,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.pagination > .active > span:hover,
.pagination > .active > a:focus,
.pagination > .active > span:focus {
z-index: 2;
z-index: 3;
color: #ffffff;
background-color: #3498db;
border-color: #3498db;
@@ -5050,7 +5005,6 @@ button.close {
.modal-header {
padding: 15px;
border-bottom: 1px solid #e5e5e5;
min-height: 16.5px;
}
.modal-header .close {
margin-top: -2px;
@@ -5377,6 +5331,8 @@ button.close {
.pager:after,
.panel-body:before,
.panel-body:after,
.modal-header:before,
.modal-header:after,
.modal-footer:before,
.modal-footer:after {
content: " ";
@@ -5396,6 +5352,7 @@ button.close {
.navbar-collapse:after,
.pager:after,
.panel-body:after,
.modal-header:after,
.modal-footer:after {
clear: both;
}

View File

File diff suppressed because one or more lines are too long

48
gui/default/vendor/bootstrap/js/bootstrap.js vendored Normal file → Executable file
View File

@@ -1,12 +1,12 @@
/*!
* Bootstrap v3.3.5 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=7ee3aa2c980c52345937)
* Config saved to config.json and https://gist.github.com/7ee3aa2c980c52345937
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=2534c630d2cba4ad9c65)
* Config saved to config.json and https://gist.github.com/2534c630d2cba4ad9c65
*/
if (typeof jQuery === 'undefined') {
throw new Error('Bootstrap\'s JavaScript requires jQuery')
@@ -14,13 +14,13 @@ if (typeof jQuery === 'undefined') {
+function ($) {
'use strict';
var version = $.fn.jquery.split(' ')[0].split('.')
if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 2)) {
throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3')
}
}(jQuery);
/* ========================================================================
* Bootstrap: alert.js v3.3.5
* Bootstrap: alert.js v3.3.6
* http://getbootstrap.com/javascript/#alerts
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
@@ -39,7 +39,7 @@ if (typeof jQuery === 'undefined') {
$(el).on('click', dismiss, this.close)
}
Alert.VERSION = '3.3.5'
Alert.VERSION = '3.3.6'
Alert.TRANSITION_DURATION = 150
@@ -115,7 +115,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: button.js v3.3.5
* Bootstrap: button.js v3.3.6
* http://getbootstrap.com/javascript/#buttons
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
@@ -135,7 +135,7 @@ if (typeof jQuery === 'undefined') {
this.isLoading = false
}
Button.VERSION = '3.3.5'
Button.VERSION = '3.3.6'
Button.DEFAULTS = {
loadingText: 'loading...'
@@ -236,7 +236,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: dropdown.js v3.3.5
* Bootstrap: dropdown.js v3.3.6
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
@@ -256,7 +256,7 @@ if (typeof jQuery === 'undefined') {
$(element).on('click.bs.dropdown', this.toggle)
}
Dropdown.VERSION = '3.3.5'
Dropdown.VERSION = '3.3.6'
function getParent($this) {
var selector = $this.attr('data-target')
@@ -288,7 +288,7 @@ if (typeof jQuery === 'undefined') {
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
})
}
@@ -322,7 +322,7 @@ if (typeof jQuery === 'undefined') {
$parent
.toggleClass('open')
.trigger('shown.bs.dropdown', relatedTarget)
.trigger($.Event('shown.bs.dropdown', relatedTarget))
}
return false
@@ -402,7 +402,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: modal.js v3.3.5
* Bootstrap: modal.js v3.3.6
* http://getbootstrap.com/javascript/#modals
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
@@ -436,7 +436,7 @@ if (typeof jQuery === 'undefined') {
}
}
Modal.VERSION = '3.3.5'
Modal.VERSION = '3.3.6'
Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150
@@ -740,7 +740,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: tooltip.js v3.3.5
* Bootstrap: tooltip.js v3.3.6
* http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame
* ========================================================================
@@ -767,7 +767,7 @@ if (typeof jQuery === 'undefined') {
this.init('tooltip', element, options)
}
Tooltip.VERSION = '3.3.5'
Tooltip.VERSION = '3.3.6'
Tooltip.TRANSITION_DURATION = 150
@@ -1255,7 +1255,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: popover.js v3.3.5
* Bootstrap: popover.js v3.3.6
* http://getbootstrap.com/javascript/#popovers
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
@@ -1275,7 +1275,7 @@ if (typeof jQuery === 'undefined') {
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
Popover.VERSION = '3.3.5'
Popover.VERSION = '3.3.6'
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right',
@@ -1364,7 +1364,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: tab.js v3.3.5
* Bootstrap: tab.js v3.3.6
* http://getbootstrap.com/javascript/#tabs
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
@@ -1384,7 +1384,7 @@ if (typeof jQuery === 'undefined') {
// jscs:enable requireDollarBeforejQueryAssignment
}
Tab.VERSION = '3.3.5'
Tab.VERSION = '3.3.6'
Tab.TRANSITION_DURATION = 150
@@ -1520,7 +1520,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: collapse.js v3.3.5
* Bootstrap: collapse.js v3.3.6
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
@@ -1550,7 +1550,7 @@ if (typeof jQuery === 'undefined') {
if (this.options.toggle) this.toggle()
}
Collapse.VERSION = '3.3.5'
Collapse.VERSION = '3.3.6'
Collapse.TRANSITION_DURATION = 350
@@ -1732,7 +1732,7 @@ if (typeof jQuery === 'undefined') {
}(jQuery);
/* ========================================================================
* Bootstrap: transition.js v3.3.5
* Bootstrap: transition.js v3.3.6
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More