From 1a25ae32caeab45946674dce0b42a06b899eb3f5 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 29 Mar 2025 12:20:35 +0000 Subject: [PATCH 01/62] chore: remove abandoned next-gen-gui experiment (#10004) It didn't go anywhere, it adds no value where it is. --- .gitignore | 1 - build.go | 82 +- next-gen-gui/.editorconfig | 13 - next-gen-gui/.gitignore | 46 - next-gen-gui/LICENSE | 373 - next-gen-gui/README.md | 65 - next-gen-gui/angular.json | 128 - next-gen-gui/browserslist | 12 - next-gen-gui/e2e/protractor.conf.js | 32 - next-gen-gui/e2e/src/app.e2e-spec.ts | 23 - next-gen-gui/e2e/src/app.po.ts | 11 - next-gen-gui/e2e/tsconfig.json | 13 - next-gen-gui/karma.conf.js | 32 - next-gen-gui/package-lock.json | 37977 ---------------- next-gen-gui/package.json | 53 - next-gen-gui/screenshot.png | Bin 752159 -> 0 bytes next-gen-gui/src/app/api-utils.ts | 9 - next-gen-gui/src/app/app-routing.module.ts | 11 - next-gen-gui/src/app/app.component.html | 9 - next-gen-gui/src/app/app.component.scss | 13 - next-gen-gui/src/app/app.component.spec.ts | 22 - next-gen-gui/src/app/app.component.ts | 23 - next-gen-gui/src/app/app.module.ts | 87 - next-gen-gui/src/app/card/card.component.scss | 37 - .../src/app/card/card.component.spec.ts | 25 - next-gen-gui/src/app/card/card.component.ts | 35 - .../chart-item/chart-item.component.html | 4 - .../chart-item/chart-item.component.scss | 27 - .../chart-item/chart-item.component.spec.ts | 25 - .../charts/chart-item/chart-item.component.ts | 19 - .../src/app/charts/chart/chart.component.html | 14 - .../src/app/charts/chart/chart.component.scss | 0 .../app/charts/chart/chart.component.spec.ts | 26 - .../src/app/charts/chart/chart.component.ts | 115 - .../donut-chart/donut-chart.component.html | 7 - .../donut-chart/donut-chart.component.scss | 48 - .../donut-chart/donut-chart.component.spec.ts | 21 - .../donut-chart/donut-chart.component.ts | 87 - next-gen-gui/src/app/charts/tooltip.ts | 62 - next-gen-gui/src/app/completion.ts | 7 - next-gen-gui/src/app/connections.ts | 16 - .../app/dashboard/dashboard.component.html | 16 - .../app/dashboard/dashboard.component.scss | 8 - .../app/dashboard/dashboard.component.spec.ts | 34 - .../src/app/dashboard/dashboard.component.ts | 117 - next-gen-gui/src/app/device.ts | 126 - .../src/app/dialog/dialog.component.html | 6 - .../src/app/dialog/dialog.component.scss | 0 .../src/app/dialog/dialog.component.spec.ts | 25 - .../src/app/dialog/dialog.component.ts | 26 - next-gen-gui/src/app/folder.ts | 200 - .../caching.interceptor.spec.ts | 16 - .../http-interceptors/caching.interceptor.ts | 58 - .../csrf.interceptor.spec.ts | 16 - .../app/http-interceptors/csrf.interceptor.ts | 29 - .../error.interceptor.spec.ts | 16 - .../http-interceptors/error.interceptor.ts | 39 - .../src/app/http-interceptors/index.ts | 14 - .../list-toggle/list-toggle.component.html | 4 - .../list-toggle/list-toggle.component.scss | 0 .../list-toggle/list-toggle.component.spec.ts | 25 - .../app/list-toggle/list-toggle.component.ts | 26 - .../device-list/device-list.component.html | 31 - .../device-list/device-list.component.spec.ts | 28 - .../device-list/device-list.component.ts | 96 - .../folder-list/folder-list.component.html | 32 - .../folder-list/folder-list.component.spec.ts | 25 - .../folder-list/folder-list.component.ts | 102 - .../status-list/status-list.component.html | 10 - .../status-list/status-list.component.scss | 70 - .../status-list/status-list.component.spec.ts | 25 - .../status-list/status-list.component.ts | 47 - .../src/app/mocks/mock-db-completion.ts | 26 - next-gen-gui/src/app/mocks/mock-db-status.ts | 46 - .../src/app/mocks/mock-system-config.ts | 67 - .../src/app/mocks/mock-system-connections.ts | 44 - .../src/app/mocks/mock-system-status.ts | 59 - .../src/app/services/cookie.service.spec.ts | 16 - .../src/app/services/cookie.service.ts | 35 - .../services/db-completion.service.spec.ts | 20 - .../src/app/services/db-completion.service.ts | 51 - .../app/services/db-status.service.spec.ts | 21 - .../src/app/services/db-status.service.ts | 43 - .../src/app/services/device.service.spec.ts | 20 - .../src/app/services/device.service.ts | 128 - .../src/app/services/filter.service.spec.ts | 16 - .../src/app/services/filter.service.ts | 30 - .../src/app/services/folder.service.spec.ts | 20 - .../src/app/services/folder.service.ts | 98 - .../in-memory-config-data.service.spec.ts | 16 - .../services/in-memory-config-data.service.ts | 17 - .../src/app/services/message.service.spec.ts | 16 - .../src/app/services/message.service.ts | 20 - .../src/app/services/progress.service.spec.ts | 40 - .../src/app/services/progress.service.ts | 50 - .../services/request-cache.service.spec.ts | 16 - .../src/app/services/request-cache.service.ts | 50 - .../services/system-config.service.spec.ts | 20 - .../src/app/services/system-config.service.ts | 60 - .../system-connections.service.spec.ts | 20 - .../services/system-connections.service.ts | 26 - .../services/system-status.service.spec.ts | 20 - .../src/app/services/system-status.service.ts | 27 - next-gen-gui/src/app/style.ts | 11 - next-gen-gui/src/app/system-status.ts | 17 - next-gen-gui/src/app/trim.pipe.spec.ts | 8 - next-gen-gui/src/app/trim.pipe.ts | 12 - next-gen-gui/src/app/type.ts | 4 - next-gen-gui/src/assets/.gitkeep | 0 next-gen-gui/src/assets/logo-horizontal.svg | 54 - .../src/environments/environment.prod.ts | 3 - next-gen-gui/src/environments/environment.ts | 16 - next-gen-gui/src/favicon.ico | Bin 948 -> 0 bytes next-gen-gui/src/index.html | 20 - next-gen-gui/src/main.ts | 12 - next-gen-gui/src/polyfills.ts | 63 - next-gen-gui/src/styles.scss | 140 - next-gen-gui/src/test.ts | 25 - next-gen-gui/tsconfig.app.json | 14 - next-gen-gui/tsconfig.json | 23 - next-gen-gui/tsconfig.spec.json | 18 - next-gen-gui/tslint.json | 91 - 122 files changed, 20 insertions(+), 42526 deletions(-) delete mode 100644 next-gen-gui/.editorconfig delete mode 100644 next-gen-gui/.gitignore delete mode 100644 next-gen-gui/LICENSE delete mode 100644 next-gen-gui/README.md delete mode 100644 next-gen-gui/angular.json delete mode 100644 next-gen-gui/browserslist delete mode 100644 next-gen-gui/e2e/protractor.conf.js delete mode 100644 next-gen-gui/e2e/src/app.e2e-spec.ts delete mode 100644 next-gen-gui/e2e/src/app.po.ts delete mode 100644 next-gen-gui/e2e/tsconfig.json delete mode 100644 next-gen-gui/karma.conf.js delete mode 100644 next-gen-gui/package-lock.json delete mode 100644 next-gen-gui/package.json delete mode 100644 next-gen-gui/screenshot.png delete mode 100644 next-gen-gui/src/app/api-utils.ts delete mode 100644 next-gen-gui/src/app/app-routing.module.ts delete mode 100644 next-gen-gui/src/app/app.component.html delete mode 100644 next-gen-gui/src/app/app.component.scss delete mode 100644 next-gen-gui/src/app/app.component.spec.ts delete mode 100644 next-gen-gui/src/app/app.component.ts delete mode 100644 next-gen-gui/src/app/app.module.ts delete mode 100644 next-gen-gui/src/app/card/card.component.scss delete mode 100644 next-gen-gui/src/app/card/card.component.spec.ts delete mode 100644 next-gen-gui/src/app/card/card.component.ts delete mode 100644 next-gen-gui/src/app/charts/chart-item/chart-item.component.html delete mode 100644 next-gen-gui/src/app/charts/chart-item/chart-item.component.scss delete mode 100644 next-gen-gui/src/app/charts/chart-item/chart-item.component.spec.ts delete mode 100644 next-gen-gui/src/app/charts/chart-item/chart-item.component.ts delete mode 100644 next-gen-gui/src/app/charts/chart/chart.component.html delete mode 100644 next-gen-gui/src/app/charts/chart/chart.component.scss delete mode 100644 next-gen-gui/src/app/charts/chart/chart.component.spec.ts delete mode 100644 next-gen-gui/src/app/charts/chart/chart.component.ts delete mode 100644 next-gen-gui/src/app/charts/donut-chart/donut-chart.component.html delete mode 100644 next-gen-gui/src/app/charts/donut-chart/donut-chart.component.scss delete mode 100644 next-gen-gui/src/app/charts/donut-chart/donut-chart.component.spec.ts delete mode 100644 next-gen-gui/src/app/charts/donut-chart/donut-chart.component.ts delete mode 100644 next-gen-gui/src/app/charts/tooltip.ts delete mode 100644 next-gen-gui/src/app/completion.ts delete mode 100644 next-gen-gui/src/app/connections.ts delete mode 100644 next-gen-gui/src/app/dashboard/dashboard.component.html delete mode 100644 next-gen-gui/src/app/dashboard/dashboard.component.scss delete mode 100644 next-gen-gui/src/app/dashboard/dashboard.component.spec.ts delete mode 100644 next-gen-gui/src/app/dashboard/dashboard.component.ts delete mode 100644 next-gen-gui/src/app/device.ts delete mode 100644 next-gen-gui/src/app/dialog/dialog.component.html delete mode 100644 next-gen-gui/src/app/dialog/dialog.component.scss delete mode 100644 next-gen-gui/src/app/dialog/dialog.component.spec.ts delete mode 100644 next-gen-gui/src/app/dialog/dialog.component.ts delete mode 100644 next-gen-gui/src/app/folder.ts delete mode 100644 next-gen-gui/src/app/http-interceptors/caching.interceptor.spec.ts delete mode 100644 next-gen-gui/src/app/http-interceptors/caching.interceptor.ts delete mode 100644 next-gen-gui/src/app/http-interceptors/csrf.interceptor.spec.ts delete mode 100644 next-gen-gui/src/app/http-interceptors/csrf.interceptor.ts delete mode 100644 next-gen-gui/src/app/http-interceptors/error.interceptor.spec.ts delete mode 100644 next-gen-gui/src/app/http-interceptors/error.interceptor.ts delete mode 100644 next-gen-gui/src/app/http-interceptors/index.ts delete mode 100644 next-gen-gui/src/app/list-toggle/list-toggle.component.html delete mode 100644 next-gen-gui/src/app/list-toggle/list-toggle.component.scss delete mode 100644 next-gen-gui/src/app/list-toggle/list-toggle.component.spec.ts delete mode 100644 next-gen-gui/src/app/list-toggle/list-toggle.component.ts delete mode 100644 next-gen-gui/src/app/lists/device-list/device-list.component.html delete mode 100644 next-gen-gui/src/app/lists/device-list/device-list.component.spec.ts delete mode 100644 next-gen-gui/src/app/lists/device-list/device-list.component.ts delete mode 100644 next-gen-gui/src/app/lists/folder-list/folder-list.component.html delete mode 100644 next-gen-gui/src/app/lists/folder-list/folder-list.component.spec.ts delete mode 100644 next-gen-gui/src/app/lists/folder-list/folder-list.component.ts delete mode 100644 next-gen-gui/src/app/lists/status-list/status-list.component.html delete mode 100644 next-gen-gui/src/app/lists/status-list/status-list.component.scss delete mode 100644 next-gen-gui/src/app/lists/status-list/status-list.component.spec.ts delete mode 100644 next-gen-gui/src/app/lists/status-list/status-list.component.ts delete mode 100644 next-gen-gui/src/app/mocks/mock-db-completion.ts delete mode 100644 next-gen-gui/src/app/mocks/mock-db-status.ts delete mode 100644 next-gen-gui/src/app/mocks/mock-system-config.ts delete mode 100644 next-gen-gui/src/app/mocks/mock-system-connections.ts delete mode 100644 next-gen-gui/src/app/mocks/mock-system-status.ts delete mode 100644 next-gen-gui/src/app/services/cookie.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/cookie.service.ts delete mode 100644 next-gen-gui/src/app/services/db-completion.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/db-completion.service.ts delete mode 100644 next-gen-gui/src/app/services/db-status.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/db-status.service.ts delete mode 100644 next-gen-gui/src/app/services/device.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/device.service.ts delete mode 100644 next-gen-gui/src/app/services/filter.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/filter.service.ts delete mode 100644 next-gen-gui/src/app/services/folder.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/folder.service.ts delete mode 100644 next-gen-gui/src/app/services/in-memory-config-data.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/in-memory-config-data.service.ts delete mode 100644 next-gen-gui/src/app/services/message.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/message.service.ts delete mode 100644 next-gen-gui/src/app/services/progress.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/progress.service.ts delete mode 100644 next-gen-gui/src/app/services/request-cache.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/request-cache.service.ts delete mode 100644 next-gen-gui/src/app/services/system-config.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/system-config.service.ts delete mode 100644 next-gen-gui/src/app/services/system-connections.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/system-connections.service.ts delete mode 100644 next-gen-gui/src/app/services/system-status.service.spec.ts delete mode 100644 next-gen-gui/src/app/services/system-status.service.ts delete mode 100644 next-gen-gui/src/app/style.ts delete mode 100644 next-gen-gui/src/app/system-status.ts delete mode 100644 next-gen-gui/src/app/trim.pipe.spec.ts delete mode 100644 next-gen-gui/src/app/trim.pipe.ts delete mode 100644 next-gen-gui/src/app/type.ts delete mode 100644 next-gen-gui/src/assets/.gitkeep delete mode 100644 next-gen-gui/src/assets/logo-horizontal.svg delete mode 100644 next-gen-gui/src/environments/environment.prod.ts delete mode 100644 next-gen-gui/src/environments/environment.ts delete mode 100644 next-gen-gui/src/favicon.ico delete mode 100644 next-gen-gui/src/index.html delete mode 100644 next-gen-gui/src/main.ts delete mode 100644 next-gen-gui/src/polyfills.ts delete mode 100644 next-gen-gui/src/styles.scss delete mode 100644 next-gen-gui/src/test.ts delete mode 100644 next-gen-gui/tsconfig.app.json delete mode 100644 next-gen-gui/tsconfig.json delete mode 100644 next-gen-gui/tsconfig.spec.json delete mode 100644 next-gen-gui/tslint.json diff --git a/.gitignore b/.gitignore index 89eba4edd..27113a847 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,4 @@ deb *.bz2 /repos /proto/scripts/protoc-gen-gosyncthing -/gui/next-gen-gui /compat.json diff --git a/build.go b/build.go index 08d81414e..fd591a41e 100644 --- a/build.go +++ b/build.go @@ -38,27 +38,26 @@ import ( ) var ( - goarch string - goos string - noupgrade bool - version string - goCmd string - race bool - debug = os.Getenv("BUILDDEBUG") != "" - extraTags string - installSuffix string - pkgdir string - cc string - run string - benchRun string - buildOut string - debugBinary bool - coverage bool - long bool - timeout = "120s" - longTimeout = "600s" - numVersions = 5 - withNextGenGUI = os.Getenv("BUILD_NEXT_GEN_GUI") != "" + goarch string + goos string + noupgrade bool + version string + goCmd string + race bool + debug = os.Getenv("BUILDDEBUG") != "" + extraTags string + installSuffix string + pkgdir string + cc string + run string + benchRun string + buildOut string + debugBinary bool + coverage bool + long bool + timeout = "120s" + longTimeout = "600s" + numVersions = 5 ) type target struct { @@ -380,7 +379,6 @@ func parseFlags() { flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command") flag.StringVar(&run, "run", "", "Specify which tests to run") flag.StringVar(&benchRun, "bench", "", "Specify which benchmarks to run") - flag.BoolVar(&withNextGenGUI, "with-next-gen-gui", withNextGenGUI, "Also build 'newgui'") flag.StringVar(&buildOut, "build-out", "", "Set the '-o' value for 'go build'") flag.Parse() } @@ -453,10 +451,6 @@ func benchArgs() []string { } func install(target target, tags []string) { - if (target.name == "syncthing" || target.name == "") && !withNextGenGUI { - log.Println("Notice: Next generation GUI will not be built; see --with-next-gen-gui.") - } - lazyRebuildAssets() tags = append(target.tags, tags...) @@ -486,10 +480,6 @@ func install(target target, tags []string) { } func build(target target, tags []string) { - if (target.name == "syncthing" || target.name == "") && !withNextGenGUI { - log.Println("Notice: Next generation GUI will not be built; see --with-next-gen-gui.") - } - lazyRebuildAssets() tags = append(target.tags, tags...) @@ -823,43 +813,11 @@ func lazyRebuildAssets() { shouldRebuild := shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") || shouldRebuildAssets("cmd/infra/strelaypoolsrv/auto/gui.files.go", "cmd/infra/strelaypoolsrv/gui") - if withNextGenGUI { - shouldRebuild = buildNextGenGUI() || shouldRebuild - } - if shouldRebuild { rebuildAssets() } } -func buildNextGenGUI() bool { - // Check if we need to run the npm process, and if so also set the flag - // to rebuild Go assets afterwards. The index.html is regenerated every - // time by the build process. This assumes the new GUI ends up in - // next-gen-gui/dist/next-gen-gui. - - if !shouldRebuildAssets("gui/next-gen-gui/index.html", "next-gen-gui") { - // The GUI is up to date. - return false - } - - runPrintInDir("next-gen-gui", "npm", "install") - runPrintInDir("next-gen-gui", "npm", "run", "build", "--", "--prod", "--subresource-integrity") - - rmr("gui/tech-ui") - - for _, src := range listFiles("next-gen-gui/dist") { - rel, _ := filepath.Rel("next-gen-gui/dist", src) - dst := filepath.Join("gui", rel) - if err := copyFile(src, dst, 0o644); err != nil { - fmt.Println("copy:", err) - os.Exit(1) - } - } - - return true -} - func shouldRebuildAssets(target, srcdir string) bool { info, err := os.Stat(target) if err != nil { diff --git a/next-gen-gui/.editorconfig b/next-gen-gui/.editorconfig deleted file mode 100644 index e89330a61..000000000 --- a/next-gen-gui/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/next-gen-gui/.gitignore b/next-gen-gui/.gitignore deleted file mode 100644 index 86d943a9b..000000000 --- a/next-gen-gui/.gitignore +++ /dev/null @@ -1,46 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/tmp -/out-tsc -# Only exists if Bazel was run -/bazel-out - -# dependencies -/node_modules - -# profiling files -chrome-profiler-events*.json -speed-measure-plugin*.json - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history/* - -# misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log -npm-debug.log -yarn-error.log -testem.log -/typings - -# System Files -.DS_Store -Thumbs.db diff --git a/next-gen-gui/LICENSE b/next-gen-gui/LICENSE deleted file mode 100644 index d0a1fa148..000000000 --- a/next-gen-gui/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - 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 https://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/next-gen-gui/README.md b/next-gen-gui/README.md deleted file mode 100644 index 4e747ac45..000000000 --- a/next-gen-gui/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Syncthing Tech UI - -## Usage - -This is a very bare bones read-only GUI for viewing the status of large -setups. Download a [release -zip](https://github.com/kastelo/syncthing-tech-ui/releases) and unpack it -into the GUI override directory (assuming default Linux setup): - -``` -$ cd ~/.config/syncthing -$ mkdir -p gui/default -$ cd gui/default -$ unzip ~/tech-ui-v1.0.0.zip -``` - -Then load the GUI via http://localhost:8384/tech-ui/ or similar. You should see something like this: - -![Screenshot](screenshot.png) - -## Development server - -Run `npm run serve` for a dev server. Navigate to `http://localhost:4200/`. The -app will automatically reload if you change any of the source files. - -## Production server - -In production we serve the UI through Syncthing itself. The easiest way to -do that is to simply put the built assets in the `gui` subdirectory of -Syncthing's config directory. - -``` -$ npm run build -- --prod -$ rsync -va --delete dist/tech-ui/ ~/.config/syncthing/gui/default/tech-ui/ -``` - -Adjust for your actual Syncthing config dir if different. Navigate to -`http://localhost:8384/tech-ui/`. - -Another option is to start Syncthing with the STGUIASSETS environment -variable pointing to the distribution directory. - -``` -$ npm run build -- --prod -$ ln -sf . dist/default -$ export STGUIASSETS=$(pwd)/dist -$ syncthing -``` - -The magic is symlink is because Syncthing will look for the GUI in the -`default` subdirectory. Navigate to `http://localhost:8384/tech-ui/`. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You -can also use `ng generate -directive|pipe|service|class|guard|interface|enum|module`. - -## License - -MPLv2 - -## Copyright - -Copyright (c) 2020 The Syncthing Authors diff --git a/next-gen-gui/angular.json b/next-gen-gui/angular.json deleted file mode 100644 index e764afc38..000000000 --- a/next-gen-gui/angular.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "next-gen-gui": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/next-gen-gui", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "aot": true, - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "10kb" - } - ] - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "next-gen-gui:build" - }, - "configurations": { - "production": { - "browserTarget": "next-gen-gui:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "next-gen-gui:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "next-gen-gui:serve" - }, - "configurations": { - "production": { - "devServerTarget": "next-gen-gui:serve:production" - } - } - } - } - }}, - "defaultProject": "next-gen-gui" -} diff --git a/next-gen-gui/browserslist b/next-gen-gui/browserslist deleted file mode 100644 index 80848532e..000000000 --- a/next-gen-gui/browserslist +++ /dev/null @@ -1,12 +0,0 @@ -# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. -# For additional information regarding the format and rule options, please see: -# https://github.com/browserslist/browserslist#queries - -# You can see what browsers were selected by your queries by running: -# npx browserslist - -> 0.5% -last 2 versions -Firefox ESR -not dead -not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/next-gen-gui/e2e/protractor.conf.js b/next-gen-gui/e2e/protractor.conf.js deleted file mode 100644 index 7c798cfff..000000000 --- a/next-gen-gui/e2e/protractor.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// @ts-check -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -/** - * @type { import("protractor").Config } - */ -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './src/**/*.e2e-spec.ts' - ], - capabilities: { - browserName: 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: require('path').join(__dirname, './tsconfig.json') - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; \ No newline at end of file diff --git a/next-gen-gui/e2e/src/app.e2e-spec.ts b/next-gen-gui/e2e/src/app.e2e-spec.ts deleted file mode 100644 index 8e1d3c66f..000000000 --- a/next-gen-gui/e2e/src/app.e2e-spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppPage } from './app.po'; -import { browser, logging } from 'protractor'; - -describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display welcome message', () => { - page.navigateTo(); - expect(page.getTitleText()).toEqual('tech-ui app is running!'); - }); - - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - } as logging.Entry)); - }); -}); diff --git a/next-gen-gui/e2e/src/app.po.ts b/next-gen-gui/e2e/src/app.po.ts deleted file mode 100644 index b68475e0f..000000000 --- a/next-gen-gui/e2e/src/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo(): Promise { - return browser.get(browser.baseUrl) as Promise; - } - - getTitleText(): Promise { - return element(by.css('app-root .content span')).getText() as Promise; - } -} diff --git a/next-gen-gui/e2e/tsconfig.json b/next-gen-gui/e2e/tsconfig.json deleted file mode 100644 index 39b800f78..000000000 --- a/next-gen-gui/e2e/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "jasminewd2", - "node" - ] - } -} diff --git a/next-gen-gui/karma.conf.js b/next-gen-gui/karma.conf.js deleted file mode 100644 index 0340dbacc..000000000 --- a/next-gen-gui/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, './coverage/tech-ui'), - reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/next-gen-gui/package-lock.json b/next-gen-gui/package-lock.json deleted file mode 100644 index 02f3f0c8d..000000000 --- a/next-gen-gui/package-lock.json +++ /dev/null @@ -1,37977 +0,0 @@ -{ - "name": "next-gen-gui", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "version": "1.0.0", - "dependencies": { - "@angular/animations": "^9.1.0", - "@angular/cdk": "^9.2.0", - "@angular/common": "^9.1.0", - "@angular/compiler": "^9.1.0", - "@angular/core": "^9.1.0", - "@angular/flex-layout": "^9.0.0-beta.29", - "@angular/forms": "^9.1.0", - "@angular/material": "^9.2.0", - "@angular/platform-browser": "^9.1.0", - "@angular/platform-browser-dynamic": "^9.1.0", - "@angular/router": "^9.1.0", - "angular-in-memory-web-api": "^0.10.0", - "chart.js": "^2.9.3", - "component": "^1.1.0", - "rxjs": "^6.5.5", - "tslib": "^1.10.0", - "zone.js": "^0.10.3" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^0.901.0", - "@angular/cli": "^9.1.0", - "@angular/compiler-cli": "^9.1.0", - "@angular/language-service": "^9.1.0", - "@types/jasmine": "^3.5.10", - "@types/jasminewd2": "~2.0.3", - "@types/node": "^12.12.34", - "codelyzer": "^5.2.2", - "jasmine-core": "~3.5.0", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.3.0", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage-istanbul-reporter": "~2.1.0", - "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "^1.5.3", - "protractor": "~5.4.3", - "ts-node": "~8.3.0", - "tslint": "~5.18.0", - "typescript": "~3.7.5" - } - }, - "node_modules/@angular-devkit/architect": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.0.tgz", - "integrity": "sha512-SlqEBkPrT40zMCy5344AsUqC76pEPCaGPaAkCIvadaz2dC9vNMzQrvubCPJHViD/TumkSX1kYmLS3iYASVM9GQ==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "9.1.0", - "rxjs": "6.5.4" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/build-angular": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.901.0.tgz", - "integrity": "sha512-ftJVNlKvIomqRfr5jFVraPqlLSUJu8YyVbFv/aCsvhNpuZGkYpTOMoJDwyywdslSTH608BIoU63IAnIz9PwUdw==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/build-optimizer": "0.901.0", - "@angular-devkit/build-webpack": "0.901.0", - "@angular-devkit/core": "9.1.0", - "@babel/core": "7.9.0", - "@babel/generator": "7.9.3", - "@babel/preset-env": "7.9.0", - "@babel/template": "7.8.6", - "@jsdevtools/coverage-istanbul-loader": "3.0.3", - "@ngtools/webpack": "9.1.0", - "ajv": "6.12.0", - "autoprefixer": "9.7.4", - "babel-loader": "8.0.6", - "browserslist": "^4.9.1", - "cacache": "15.0.0", - "caniuse-lite": "^1.0.30001032", - "circular-dependency-plugin": "5.2.0", - "copy-webpack-plugin": "5.1.1", - "core-js": "3.6.4", - "cssnano": "4.1.10", - "file-loader": "6.0.0", - "find-cache-dir": "3.3.1", - "glob": "7.1.6", - "jest-worker": "25.1.0", - "karma-source-map-support": "1.4.0", - "less": "3.11.1", - "less-loader": "5.0.0", - "license-webpack-plugin": "2.1.4", - "loader-utils": "2.0.0", - "mini-css-extract-plugin": "0.9.0", - "minimatch": "3.0.4", - "open": "7.0.3", - "parse5": "4.0.0", - "postcss": "7.0.27", - "postcss-import": "12.0.1", - "postcss-loader": "3.0.0", - "raw-loader": "4.0.0", - "regenerator-runtime": "0.13.5", - "rimraf": "3.0.2", - "rollup": "2.1.0", - "rxjs": "6.5.4", - "sass": "1.26.3", - "sass-loader": "8.0.2", - "semver": "7.1.3", - "source-map": "0.7.3", - "source-map-loader": "0.2.4", - "source-map-support": "0.5.16", - "speed-measure-webpack-plugin": "1.3.1", - "style-loader": "1.1.3", - "stylus": "0.54.7", - "stylus-loader": "3.0.2", - "terser": "4.6.7", - "terser-webpack-plugin": "2.3.5", - "tree-kill": "1.2.2", - "webpack": "4.42.0", - "webpack-dev-middleware": "3.7.2", - "webpack-dev-server": "3.10.3", - "webpack-merge": "4.2.2", - "webpack-sources": "1.4.3", - "webpack-subresource-integrity": "1.4.0", - "worker-plugin": "4.0.2" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/postcss/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular-devkit/build-optimizer": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.901.0.tgz", - "integrity": "sha512-Y9sz8uf2zjilhPUVYb0K9Mio6c1d5c+csuDc15CCKzELXJwyyDxilIFgn6Eu+edM0HNQGzbIwkjy4DkR9mtuTQ==", - "dev": true, - "dependencies": { - "loader-utils": "2.0.0", - "source-map": "0.7.3", - "tslib": "1.11.1", - "typescript": "3.8.3", - "webpack-sources": "1.4.3" - }, - "bin": { - "build-optimizer": "src/build-optimizer/cli.js" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-optimizer/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@angular-devkit/build-optimizer/node_modules/typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.901.0.tgz", - "integrity": "sha512-Oze0VzIvHnoW12C80fiNH4HBu/GWmhJPXdNA7nRkU/tBQlIKnfngf8rQ0QbgecN2qdEXQpZJsP/XclTi3zugsg==", - "dev": true, - "dependencies": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/core": "9.1.0", - "rxjs": "6.5.4" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.0.tgz", - "integrity": "sha512-vHTsrB4JaVUQ95FRnKrgo79Y3F6FokImrZdrmwkQmwAThpjXeXmpUEKZS+ZSTFRgesjiIysVGOFijARP4BQ7Bg==", - "dev": true, - "dependencies": { - "ajv": "6.12.0", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.5.4", - "source-map": "0.7.3" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@angular-devkit/schematics": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.1.0.tgz", - "integrity": "sha512-cb9PSvskMwWlL54fPfCcpJoyNDWAX6Wo7CzL5qpIB2cJCPLAuyfRUYYrkO77YUST+n2HvypHz0cZ5SNGMfaaBQ==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "9.1.0", - "ora": "4.0.3", - "rxjs": "6.5.4" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular/animations": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.0.tgz", - "integrity": "sha512-o7X3HM+eocoryw3VrDUtG6Wci2KwtzyBFo3KBJXjQ16X6fwdkjTG+hLb7pp2CBFBEJW4tPYEy7cSBmEfMRTqag==" - }, - "node_modules/@angular/cdk": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.0.tgz", - "integrity": "sha512-jeeznvNDpR9POuxzz8Y0zFvMynG9HCJo3ZPTqOjlOq8Lj8876+rLsHDvKEMeLdwlkdi1EweYJW1CLQzI+TwqDA==", - "dependencies": { - "parse5": "^5.0.0" - }, - "optionalDependencies": { - "parse5": "^5.0.0" - } - }, - "node_modules/@angular/cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.0.tgz", - "integrity": "sha512-ofum4gPE/W3fKyzuJrpdHeOS0ZL8x0eYCgsrMyUoFodSpb5LWPqeW+56NgDTpIeny+Trx3pM9dr9QTUVTJ0vYg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0", - "@schematics/angular": "9.1.0", - "@schematics/update": "0.901.0", - "@yarnpkg/lockfile": "1.1.0", - "ansi-colors": "4.1.1", - "debug": "4.1.1", - "ini": "1.3.5", - "inquirer": "7.1.0", - "npm-package-arg": "8.0.1", - "npm-pick-manifest": "6.0.0", - "open": "7.0.3", - "pacote": "11.1.4", - "read-package-tree": "5.3.1", - "rimraf": "3.0.2", - "semver": "7.1.3", - "symbol-observable": "1.2.0", - "universal-analytics": "0.4.20", - "uuid": "7.0.2" - }, - "bin": { - "ng": "bin/ng" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular/cli/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@angular/cli/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@angular/cli/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/cli/node_modules/uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@angular/common": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-9.1.0.tgz", - "integrity": "sha512-6JPLNtMhI03bGTVQJeSwc+dTjV6DtP7M/BAyzIV0InZP1D6XsOh2QahLFIaaN2sSxYA2ClKuwfX1v+rx9AbXQA==" - }, - "node_modules/@angular/compiler": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.1.0.tgz", - "integrity": "sha512-QHw/JSeTXHiJQ2Ih0EtU7FGsYcOr+0hwZhqwSW3EEn8TtUgA3DS5lXeiDV66f+3DdvNZFPmgiZIvun3ypxn1HA==" - }, - "node_modules/@angular/compiler-cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.1.0.tgz", - "integrity": "sha512-xZ8mVPmPporSTtvNA+cbFJQymLzuWfMX6HDDgztZ2eZ5WcQJYloRN4CcYMEzDhCxfV1Zw9Tfc2l14jZD8osi6g==", - "dev": true, - "dependencies": { - "canonical-path": "1.0.0", - "chokidar": "^3.0.0", - "convert-source-map": "^1.5.1", - "dependency-graph": "^0.7.2", - "fs-extra": "4.0.2", - "magic-string": "^0.25.0", - "minimist": "^1.2.0", - "reflect-metadata": "^0.1.2", - "semver": "^6.3.0", - "source-map": "^0.6.1", - "sourcemap-codec": "^1.4.8", - "yargs": "15.3.0" - }, - "bin": { - "ivy-ngcc": "ngcc/main-ivy-ngcc.js", - "ng-xi18n": "src/extract_i18n.js", - "ngc": "src/main.js", - "ngcc": "ngcc/main-ngcc.js" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/@angular/compiler-cli/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular/compiler-cli/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/@angular/compiler-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@angular/compiler-cli/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/@angular/compiler-cli/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular/compiler-cli/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular/compiler-cli/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/@angular/compiler-cli/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@angular/compiler-cli/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@angular/compiler-cli/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "node_modules/@angular/compiler-cli/node_modules/yargs": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.0.tgz", - "integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/compiler-cli/node_modules/yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular/core": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.1.0.tgz", - "integrity": "sha512-RVlyegdIAij0P1wLY5ObIdsBAzvmHkHfElnmfiNKhaDftP6U/3zRtaKDu0bq0jvn1WCQ8zXxFQ8AWyKZwyFS+w==" - }, - "node_modules/@angular/flex-layout": { - "version": "9.0.0-beta.29", - "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-9.0.0-beta.29.tgz", - "integrity": "sha512-93sxR+kYfYMOdnlWL0Q77FZ428gg8XnBu0YZm6GsCdkw/vLggIT/G1ZAqHlCPIODt6pxmCJ5KXh4ShvniIYDsA==" - }, - "node_modules/@angular/forms": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.1.0.tgz", - "integrity": "sha512-5GC8HQlPChPV+168zLlm4yj4syA6N9ChSKV0tmzj1zIfMcub1UAOaB9IYaXRHQsjPFh9OuQXwmkzScyAfhEVjA==" - }, - "node_modules/@angular/language-service": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.1.0.tgz", - "integrity": "sha512-2f8ECoXrj40oS1rtIfi+F8T4WPzundcZDs8WMFNBuWYbk14v1S9sTgMEmZyePHGkPjt6IfYiLJKJCvVgrt1nxQ==", - "dev": true - }, - "node_modules/@angular/material": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.0.tgz", - "integrity": "sha512-KKzEIVh6/m56m+Ao8p4PK0SyEr0574l3VP2swj1qPag3u+FYgemmXCGTaChrKdDsez+zeTCPXImBGXzE6NQ80Q==" - }, - "node_modules/@angular/platform-browser": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.0.tgz", - "integrity": "sha512-OsS/blUjl8ranmDaRADjFAmvnlmwbT6WNU7dVov7FhV0rqesbwaOJ5bR0LSYHYpej7Jaa6oYk0v0XWkaH9LTFg==" - }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.0.tgz", - "integrity": "sha512-sMtz/poQ3TYaWZzWjrn9apKUZ/WKql2MYCWbpax7pql3GgC9OoTslc7ZEe7/d3ynfFE/CQqWBBOuWGD71Z0LMQ==" - }, - "node_modules/@angular/router": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.0.tgz", - "integrity": "sha512-cExO1nPnoPFiUJWZ28hTHozPLFoCmqr3xqcM57We0hhKE0esdrO+gRWKRH0EJERukLbU8coPKVhA8daGUpASiQ==" - }, - "node_modules/@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.8.3" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", - "dev": true, - "dependencies": { - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "node_modules/@babel/compat-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.3.tgz", - "integrity": "sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.9.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.7.0" - } - }, - "node_modules/@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", - "dev": true - }, - "node_modules/@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", - "dev": true, - "dependencies": { - "lodash": "^4.17.13" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", - "dev": true - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "node_modules/@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz", - "integrity": "sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz", - "integrity": "sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", - "globals": "^11.1.0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", - "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz", - "integrity": "sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", - "dev": true, - "dependencies": { - "regenerator-transform": "^0.14.2" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", - "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.0", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.7", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.0", - "browserslist": "^4.9.1", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "node_modules/@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, - "node_modules/@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "node_modules/@babel/traverse": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", - "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "node_modules/@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.3.tgz", - "integrity": "sha512-TAdNkeGB5Fe4Og+ZkAr1Kvn9by2sfL44IAHFtxlh1BA1XJ5cLpO9iSNki5opWESv3l3vSHsZ9BNKuqFKbEbFaA==", - "dev": true, - "dependencies": { - "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.1", - "loader-utils": "^1.4.0", - "merge-source-map": "^1.1.0", - "schema-utils": "^2.6.4" - } - }, - "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@ngtools/webpack": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.0.tgz", - "integrity": "sha512-kQ+1N/F+5tuUXiiaoqJwhcOIM0I93EEvF3xwpTLRm91wl2i8R1261LvsD/uQPrgLrZNGR6eFhFF1Izn2PnIjQA==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "9.1.0", - "enhanced-resolve": "4.1.1", - "rxjs": "6.5.4", - "webpack-sources": "1.4.3" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@ngtools/webpack/node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@npmcli/ci-detect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/ci-detect/-/ci-detect-1.2.0.tgz", - "integrity": "sha512-JtktVH7ASBVIWsQTFlFpeOzhBJskvoBCTfeeRhhZy7ybATcUvwiwotZ8j5rkqUUyB69lIy/AvboiiiGBjYBKBA==", - "dev": true - }, - "node_modules/@npmcli/git": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.0.1.tgz", - "integrity": "sha512-hVatexiBtx71F01Ars38Hr5AFUGmJgHAfQtRlO5fJlnAawRGSXwEFgjB5i3XdUUmElZU/RXy7fefN02dZKxgPw==", - "dev": true, - "dependencies": { - "@npmcli/promise-spawn": "^1.1.0", - "mkdirp": "^1.0.3", - "npm-pick-manifest": "^6.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "unique-filename": "^1.1.1", - "which": "^2.0.2" - } - }, - "node_modules/@npmcli/git/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/installed-package-contents": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.5.tgz", - "integrity": "sha512-aKIwguaaqb6ViwSOFytniGvLPb9SMCUm39TgM3SfUo7n0TxUMbwoXfpwyvQ4blm10lzbAwTsvjr7QZ85LvTi4A==", - "dev": true, - "dependencies": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1", - "read-package-json-fast": "^1.1.1", - "readdir-scoped-modules": "^1.1.0" - }, - "bin": { - "installed-package-contents": "index.js" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@npmcli/promise-spawn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.1.0.tgz", - "integrity": "sha512-FwbuYN9KXBkloLeIR3xRgI8dyOdfK/KzaJlChszNuwmUXD1lHXfLlSeo4n4KrKt2udIK9K9/TzlnyCA3ubM2fA==", - "dev": true, - "dependencies": { - "infer-owner": "^1.0.4" - } - }, - "node_modules/@schematics/angular": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.1.0.tgz", - "integrity": "sha512-qkehaITQ1S1udfnnBY5CXGWnk1iVFI8cZayjLUlRfD5w+6v9if3VIuqPssX96MqvkbjyRu1N214+ieaawzLmuA==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@schematics/update": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.901.0.tgz", - "integrity": "sha512-u2VESL1dgOSGZK/wcWEz0WcCU/yv764zhzCQerCwUtbV1CISSSDZ6x+prVYDXOdxWBGtDos2MbCF3GEJJI1T+w==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0", - "@yarnpkg/lockfile": "1.1.0", - "ini": "1.3.5", - "npm-package-arg": "^8.0.0", - "pacote": "11.1.4", - "rxjs": "6.5.4", - "semver": "7.1.3", - "semver-intersect": "1.4.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 6.11.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@schematics/update/node_modules/rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@schematics/update/node_modules/semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "node_modules/@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, - "node_modules/@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "dev": true, - "dependencies": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/jasmine": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.10.tgz", - "integrity": "sha512-3F8qpwBAiVc5+HPJeXJpbrl+XjawGmciN5LgiO7Gv1pl1RHtjoMNqZpqEksaPJW05ViKe8snYInRs6xB25Xdew==", - "dev": true - }, - "node_modules/@types/jasminewd2": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.8.tgz", - "integrity": "sha512-d9p31r7Nxk0ZH0U39PTH0hiDlJ+qNVGjlt1ucOoTUptxb2v+Y5VMnsxfwN+i3hK4yQnqBi3FMmoMFcd1JHDxdg==", - "dev": true, - "dependencies": { - "@types/jasmine": "*" - } - }, - "node_modules/@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "12.12.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", - "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==", - "dev": true - }, - "node_modules/@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", - "dev": true - }, - "node_modules/@types/selenium-webdriver": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", - "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", - "dev": true - }, - "node_modules/@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true - }, - "node_modules/@types/webpack-sources": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.7.tgz", - "integrity": "sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.6.1" - } - }, - "node_modules/@types/webpack-sources/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/wast-printer": "1.8.5" - } - }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "mamacro": "^0.0.3" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/helper-wasm-section": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-opt": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "@webassemblyjs/wast-printer": "1.8.5" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" - } - }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/floating-point-hex-parser": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-code-frame": "1.8.5", - "@webassemblyjs/helper-fsm": "1.8.5", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", - "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adm-zip": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", - "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==", - "engines": { - "node": ">=0.3.0" - } - }, - "node_modules/after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, - "node_modules/agent-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-1.0.2.tgz", - "integrity": "sha1-aJDT+yFwBLYrcPiSjg+uX4lSpwY=" - }, - "node_modules/agentkeepalive": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.0.tgz", - "integrity": "sha512-CW/n1wxF8RpEuuiq6Vbn9S8m0VSYDMnZESqaJ6F2cWN9fY8rei2qaxweIaRgq+ek8TqfoFIsUjaGNKGGEHElSg==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true - }, - "node_modules/align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dependencies": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "node_modules/alter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz", - "integrity": "sha1-x1iICGF1cgNKrmJICvJrHU0cs80=", - "dependencies": { - "stable": "~0.1.3" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/angular-in-memory-web-api": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.10.0.tgz", - "integrity": "sha512-GzY4lHPR8suREYfgiYiJSBrgq0bdCiFw/LuqW5IsOQfRx98AvS69A5Ehz12oMLy9ABzoGpqmNC5fcFtClDAPqQ==" - }, - "node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "dependencies": { - "type-fest": "^0.11.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/app-root-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", - "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", - "dev": true, - "dependencies": { - "default-require-extensions": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "node_modules/archy": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/archy/-/archy-0.0.2.tgz", - "integrity": "sha1-kQ9Dv2YUH8M1VkWXq8GJ30Sz014=" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", - "dev": true, - "dependencies": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "node_modules/assert/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-traverse": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz", - "integrity": "sha1-ac8rg4bxnc2hux4F1o/jWdiJfeY=" - }, - "node_modules/ast-types": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.3.tgz", - "integrity": "sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true - }, - "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/autoprefixer": { - "version": "9.7.4", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz", - "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==", - "dev": true, - "dependencies": { - "browserslist": "^4.8.3", - "caniuse-lite": "^1.0.30001020", - "chalk": "^2.4.2", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.26", - "postcss-value-parser": "^4.0.2" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/autoprefixer-core": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/autoprefixer-core/-/autoprefixer-core-3.1.2.tgz", - "integrity": "sha1-reXOni2dcbt//DHWlvpeh66+tjQ=", - "dependencies": { - "caniuse-db": "^1.0.30000006", - "postcss": "~2.2.5" - } - }, - "node_modules/autoprefixer/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/autoprefixer/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/autoprefixer/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true - }, - "node_modules/axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "dev": true, - "dependencies": { - "ast-types-flow": "0.0.7" - } - }, - "node_modules/babel-loader": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", - "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", - "dev": true, - "dependencies": { - "find-cache-dir": "^2.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">= 6.9" - } - }, - "node_modules/babel-loader/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/babel-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/babel-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/babel-loader/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base62": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", - "integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ=", - "engines": { - "node": "*" - } - }, - "node_modules/base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true - }, - "node_modules/base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "dependencies": { - "callsite": "1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bindings/node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, - "node_modules/block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "dependencies": { - "inherits": "~2.0.0" - }, - "engines": { - "node": "0.4 || >=0.5.8" - } - }, - "node_modules/blocking-proxy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", - "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "blocking-proxy": "built/lib/bin.js" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/breakable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz", - "integrity": "sha1-eEp5eRWjjq0nutRWtVcstLuqeME=" - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "dependencies": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" - } - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserslist": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz", - "integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001038", - "electron-to-chromium": "^1.3.390", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - }, - "bin": { - "browserslist": "cli.js" - } - }, - "node_modules/browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - } - }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "node_modules/buffer/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/builder-autoprefixer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/builder-autoprefixer/-/builder-autoprefixer-1.0.4.tgz", - "integrity": "sha1-nNjDdqbXoXIAvYyjMbUfIwDhrHA=", - "dependencies": { - "autoprefixer-core": "^3.1.1" - } - }, - "node_modules/builder-es6-module-to-cjs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/builder-es6-module-to-cjs/-/builder-es6-module-to-cjs-1.1.0.tgz", - "integrity": "sha1-efMpfRjEe7iLQ5R1OPoccnQnHuM=", - "dependencies": { - "es6-module-jstransform": "^0.1.2", - "is-module": "^1.0.0" - } - }, - "node_modules/builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "node_modules/builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true - }, - "node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" - }, - "node_modules/cacache": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.0.tgz", - "integrity": "sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g==", - "dev": true, - "dependencies": { - "chownr": "^1.1.2", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^8.0.0", - "tar": "^6.0.1", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cacache/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cacache/node_modules/tar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", - "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", - "dev": true, - "dependencies": { - "chownr": "^1.1.3", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cacache/node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/cacache/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-db": { - "version": "1.0.30001038", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001038.tgz", - "integrity": "sha512-yeQ2l99M9upOgMIRfZEdes6HuPbQiRZIMBumUwdXeEQz+faSXUZtZ8xeyEdU+TlJckH09M5NtM038sjKsRa2ow==" - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001038", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001038.tgz", - "integrity": "sha512-zii9quPo96XfOiRD4TrfYGs+QsGZpb2cGiMAzPjtf/hpFgB6zCPZgJb7I1+EATeMw/o+lG8FyRAnI+CWStHcaQ==", - "dev": true - }, - "node_modules/canonical-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", - "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", - "dev": true - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "node_modules/center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dependencies": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chanel": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/chanel/-/chanel-2.2.0.tgz", - "integrity": "sha1-JlKVsouSqoIAX4Zd0VpIlMQ42Tw=", - "dependencies": { - "co": "*", - "generator-supported": "*", - "to-descriptor": "*" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/chart.js": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", - "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", - "dependencies": { - "chartjs-color": "^2.1.0", - "moment": "^2.10.2" - } - }, - "node_modules/chartjs-color": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", - "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", - "dependencies": { - "chartjs-color-string": "^0.6.0", - "color-convert": "^1.9.3" - } - }, - "node_modules/chartjs-color-string": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", - "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", - "dependencies": { - "color-name": "^1.0.0" - } - }, - "node_modules/chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/circular-dependency-plugin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz", - "integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", - "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "node_modules/cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dependencies": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/co": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", - "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=" - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dev": true, - "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/codelyzer": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.2.tgz", - "integrity": "sha512-jB4FZ1Sx7kZhvZVdf+N2BaKTdrrNZOL0Bj10RRfrhHrb3zEvXjJvvq298JPMJAiyiCS/v4zs1QlGU0ip7xGqeA==", - "dev": true, - "dependencies": { - "app-root-path": "^2.2.1", - "aria-query": "^3.0.0", - "axobject-query": "2.0.2", - "css-selector-tokenizer": "^0.7.1", - "cssauron": "^1.4.0", - "damerau-levenshtein": "^1.0.4", - "semver-dsl": "^1.0.1", - "source-map": "^0.5.7", - "sprintf-js": "^1.1.2" - } - }, - "node_modules/codelyzer/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/codelyzer/node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true - }, - "node_modules/cogent": { - "resolved": "git+ssh://git@github.com/timaschew/cogent.git#2246bd071392f5053a3a110024fd608a40a593ba", - "dependencies": { - "debug": "*", - "generator-supported": "~0.0.1", - "netrc": "~0.1.3", - "proxy-agent": "^1.1.0", - "raw-body": "^1.1.2", - "statuses": "^1.0.2", - "write-to": "^1.0.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-convert/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "dev": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "node_modules/commoner": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz", - "integrity": "sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=", - "dependencies": { - "commander": "^2.5.0", - "detective": "^4.3.1", - "glob": "^5.0.15", - "graceful-fs": "^4.1.2", - "iconv-lite": "^0.4.5", - "mkdirp": "^0.5.0", - "private": "^0.1.6", - "q": "^1.1.2", - "recast": "^0.11.17" - }, - "bin": { - "commonize": "bin/commonize" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commoner/node_modules/ast-types": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", - "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commoner/node_modules/esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/commoner/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/commoner/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" - }, - "node_modules/commoner/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/commoner/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/commoner/node_modules/recast": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", - "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", - "dependencies": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commoner/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, - "node_modules/component": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/component/-/component-1.1.0.tgz", - "integrity": "sha1-NgSaold5i37lcUKWHhjgdnkYyao=", - "dependencies": { - "co": "^3.0.0", - "commander": "^2.2.0", - "component-build": "^1.2.2", - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-ls": "^2.1.0", - "component-outdated2": "^1.0.4", - "component-pin": "^1.0.4", - "component-remotes": "^1.2.0", - "component-resolver": "^1.3.0", - "component-search2": "^1.1.1", - "component-updater": "^1.0.4", - "component-watcher": "^1.0.1", - "debug": "*", - "mkdirp": "~0.3.5", - "rimraf": "^2.2.6", - "semver": "^2.2.1", - "superagent": "~0.17.0", - "tiny-lr-fork": "0.0.5", - "win-fork": "~1.1.1" - }, - "bin": { - "component": "bin/component", - "component-build": "bin/component-build", - "component-crawl": "bin/component-crawl", - "component-duplicates": "bin/component-duplicates", - "component-help": "bin/component-help", - "component-install": "bin/component-install", - "component-link": "bin/component-link", - "component-ls": "bin/component-ls", - "component-outdated": "bin/component-outdated", - "component-pin": "bin/component-pin", - "component-search": "bin/component-search", - "component-update": "bin/component-update", - "component-validate": "bin/component-validate" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, - "node_modules/component-build": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/component-build/-/component-build-1.2.2.tgz", - "integrity": "sha1-1bwl0lIE35On2aN6LeAE4RmkpXw=", - "dependencies": { - "builder-autoprefixer": "^1.0.3", - "builder-es6-module-to-cjs": "^1.1.0", - "component-builder": "^1.1.12", - "debug": "*" - } - }, - "node_modules/component-builder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-builder/-/component-builder-1.2.1.tgz", - "integrity": "sha1-o5fBg3na9RH4Woy5TKDZIcHdsaQ=", - "dependencies": { - "requires": "^1.0.0", - "chanel": "^2.0.0", - "co": "^3.1.0", - "component-flatten": "^1.0.1", - "component-manifest": "^1.0.0", - "component-require2": "^1.0.1", - "cp": "^0.1.1", - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.3", - "mkdirp": "^0.3.5", - "syntax-error": "^1.1.1" - } - }, - "node_modules/component-consoler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/component-consoler/-/component-consoler-2.0.0.tgz", - "integrity": "sha1-Peq/BGwjm2EGNi7JaJQGNruhTCM=", - "dependencies": { - "debug": "*" - } - }, - "node_modules/component-downloader": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/component-downloader/-/component-downloader-1.2.0.tgz", - "integrity": "sha1-b/Z/9xyEaoWKrjGog1X5vxrUO20=", - "dependencies": { - "chanel": "^2.0.1", - "co": "^3", - "component-consoler": "^2.0.0", - "component-remotes": "^1.1.1", - "debug": "*", - "decompress": "~0.2.0", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.2", - "mkdirp": "~0.3.5", - "rimraf": "^2.2.6", - "semver": "^2.2.0", - "unglob": "~0.1.2", - "write-to": "^1.0.0" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/component-flatten": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/component-flatten/-/component-flatten-1.0.1.tgz", - "integrity": "sha1-W0n0msRcuIxDAyuXozb1P4fNbIw=", - "dependencies": { - "semver": "^2.2.1" - } - }, - "node_modules/component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, - "node_modules/component-ls": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/component-ls/-/component-ls-2.1.0.tgz", - "integrity": "sha1-wR7ez4mDbd6JBkrQT+JK3YClERI=", - "dependencies": { - "archy": "~0.0.2" - } - }, - "node_modules/component-manifest": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-manifest/-/component-manifest-1.0.0.tgz", - "integrity": "sha1-SNy7HqPGiHYifmra/PNxJbC81XI=", - "dependencies": { - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.1", - "unglob": "~0.1.1" - } - }, - "node_modules/component-outdated2": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/component-outdated2/-/component-outdated2-1.0.5.tgz", - "integrity": "sha1-IGbA9Cx1w18tS/3iv5/s4M5GPRQ=", - "dependencies": { - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.1.5", - "generator-supported": "~0.0.1", - "semver": "^2.2.1" - } - }, - "node_modules/component-pin": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/component-pin/-/component-pin-1.0.5.tgz", - "integrity": "sha1-v6lOAepCB18ufU17X3i2Yt24NY0=", - "dependencies": { - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.1.5", - "generator-supported": "~0.0.1", - "semver": "^2.2.1" - } - }, - "node_modules/component-remotes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/component-remotes/-/component-remotes-1.2.0.tgz", - "integrity": "sha1-PQeYpR5ziZxs5VnZN2Olb8eh2IM=", - "dependencies": { - "co": "^3", - "cogent": "git://github.com/timaschew/cogent.git#fix-redirects", - "component-consoler": "^2.0.0", - "component-validator": "^1.0.0", - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.2", - "semver": "^2.2.1" - } - }, - "node_modules/component-require2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/component-require2/-/component-require2-1.1.1.tgz", - "integrity": "sha1-POrqGYc8HG74X1O48572koWvXMU=" - }, - "node_modules/component-resolver": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-resolver/-/component-resolver-1.3.0.tgz", - "integrity": "sha1-79grBa9krRJ4W5h8AfCsQfAKaNw=", - "dependencies": { - "chanel": "^2.0.2", - "co": "^3", - "component-consoler": "^2.0.0", - "component-downloader": "^1.2.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.2.0", - "component-validator": "^1.1.1", - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.1", - "semver": "^2.2.1", - "unglob": "~0.1.2" - } - }, - "node_modules/component-search2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/component-search2/-/component-search2-1.1.1.tgz", - "integrity": "sha1-kmeDygb2Hvdd7YW7ZVuq3i/J65E=", - "dependencies": { - "bytes": "~0.3.0", - "cogent": "~0.4.0", - "component-consoler": "^2.0.0", - "debug": "*", - "event-stream": "^3.1.1", - "generator-supported": "~0.0.1", - "JSONStream": "~0.8.0", - "stream-to-array": "^1.0.0" - } - }, - "node_modules/component-search2/node_modules/bytes": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-0.3.0.tgz", - "integrity": "sha1-eOLg4ox/nHuYjqiu4NtNX6mUGTU=" - }, - "node_modules/component-search2/node_modules/cogent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/cogent/-/cogent-0.4.3.tgz", - "integrity": "sha1-UAdE8R2D6cD8Q+9vlem5zHFaCFQ=", - "dependencies": { - "debug": "*", - "generator-supported": "~0.0.1", - "netrc": "~0.1.3", - "proxy-agent": "^1.1.0", - "raw-body": "^1.1.2", - "statuses": "^1.0.2", - "write-to": "^1.0.0" - } - }, - "node_modules/component-updater": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/component-updater/-/component-updater-1.0.5.tgz", - "integrity": "sha1-CHd/ZxzodeE+MHwdghfC6l8aSsQ=", - "dependencies": { - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.1.5", - "generator-supported": "~0.0.1", - "semver": "^2.2.1" - } - }, - "node_modules/component-validator": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/component-validator/-/component-validator-1.1.1.tgz", - "integrity": "sha1-+lD+t/WL7IndaXktE6Mn4lJgjl4=", - "dependencies": { - "component-consoler": "^2.0.0" - } - }, - "node_modules/component-watcher": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/component-watcher/-/component-watcher-1.0.3.tgz", - "integrity": "sha1-wl6AF0sLQFVJXX7xJnmGJm2QxTU=", - "dependencies": { - "debug": "*", - "sane": "0" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "node_modules/cookiejar": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-1.3.0.tgz", - "integrity": "sha1-3QCzVnkCHpnL1OhVua0EGRNHR2U=" - }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-concurrently/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", - "dev": true, - "dependencies": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", - "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "webpack-log": "^2.0.0" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/copy-webpack-plugin/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/copy-webpack-plugin/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/copy-webpack-plugin/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/copy-webpack-plugin/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/copy-webpack-plugin/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/copy-webpack-plugin/node_modules/p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/copy-webpack-plugin/node_modules/ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/copy-webpack-plugin/node_modules/y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "node_modules/copy-webpack-plugin/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", - "dev": true, - "hasInstallScript": true - }, - "node_modules/core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", - "dev": true, - "dependencies": { - "browserslist": "^4.8.3", - "semver": "7.0.0" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/cp/-/cp-0.1.1.tgz", - "integrity": "sha1-OUanbBpT/+DmhZPzQcEkszbB8G0=" - }, - "node_modules/create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" - } - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, - "node_modules/css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - }, - "engines": { - "node": ">4" - } - }, - "node_modules/css-declaration-sorter/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/css-declaration-sorter/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-declaration-sorter/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/css-parse": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", - "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", - "dev": true, - "dependencies": { - "css": "^2.0.0" - } - }, - "node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", - "dev": true - }, - "node_modules/css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" - } - }, - "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssauron": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", - "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", - "dev": true, - "dependencies": { - "through": "X.X.X" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "dev": true, - "dependencies": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-preset-default/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/cssnano-preset-default/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssnano-preset-default/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano-util-raw-cache/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/cssnano-util-raw-cache/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssnano-util-raw-cache/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/cssnano/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/cssnano/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssnano/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/csso": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", - "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", - "dev": true, - "dependencies": { - "css-tree": "1.0.0-alpha.39" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.0.0-alpha.39", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", - "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.6", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", - "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", - "dev": true - }, - "node_modules/csso/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "node_modules/cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", - "dev": true - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz", - "integrity": "sha1-RuE6udqOMJdFyNAc5UchPr2y/j8=" - }, - "node_modules/date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/decompress": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-0.2.5.tgz", - "integrity": "sha1-0hMjPv4GbM2A2RTXk/GzDNmEuEc=", - "dependencies": { - "adm-zip": "^0.4.3", - "ext-name": "^1.0.0", - "get-stdin": "^0.1.0", - "mkdirp": "^0.3.5", - "nopt": "^2.2.0", - "rimraf": "^2.2.2", - "stream-combiner": "^0.0.4", - "tar": "^0.1.18", - "tempfile": "^0.1.2" - }, - "bin": { - "decompress": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "node_modules/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dev": true, - "dependencies": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", - "dev": true, - "dependencies": { - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, - "node_modules/defs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz", - "integrity": "sha1-siYJ8sehG6ej2xFoBcE5scr/qdI=", - "dependencies": { - "alter": "~0.2.0", - "ast-traverse": "~0.1.1", - "breakable": "~1.0.0", - "esprima-fb": "~15001.1001.0-dev-harmony-fb", - "simple-fmt": "~0.1.0", - "simple-is": "~0.2.0", - "stringmap": "~0.2.2", - "stringset": "~0.2.1", - "tryor": "~0.1.2", - "yargs": "~3.27.0" - }, - "bin": { - "defs": "build/es5/defs" - } - }, - "node_modules/defs/node_modules/esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", - "dependencies": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" - } - }, - "node_modules/degenerator/node_modules/esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dev": true, - "dependencies": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/del/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/del/node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/del/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/dependency-graph": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", - "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "node_modules/detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", - "dev": true - }, - "node_modules/detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", - "dependencies": { - "acorn": "^5.2.1", - "defined": "^1.0.0" - } - }, - "node_modules/detective/node_modules/acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dethroy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dethroy/-/dethroy-1.0.2.tgz", - "integrity": "sha1-yCADMXaT3LzaP3UrAYLjvxbdRCE=" - }, - "node_modules/dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, - "dependencies": { - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "node_modules/dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "dev": true, - "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "dependencies": { - "buffer-indexof": "^1.0.0" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", - "dev": true - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.3.395", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.395.tgz", - "integrity": "sha512-kdn2cX6hZXDdz/O2Q8tZscITlsSv1a/7bOq/fQs7QAJ9iaRlnhZPccarNhxZv1tXgmgwCnKp/1lJNYLOG8Dxiw==", - "dev": true - }, - "node_modules/elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", - "dev": true, - "dependencies": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, - "node_modules/emitter-component": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.0.0.tgz", - "integrity": "sha1-8E3Rj8PcPpp0y8DzELCIZm5MAW8=" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "~0.4.13" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" - } - }, - "node_modules/engine.io-client": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", - "dev": true, - "dependencies": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - } - }, - "node_modules/engine.io-client/node_modules/component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/engine.io-client/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/engine.io-client/node_modules/ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, - "node_modules/engine.io-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", - "dev": true, - "dependencies": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" - } - }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/engine.io/node_modules/ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", - "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enhanced-resolve/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "node_modules/entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", - "dev": true - }, - "node_modules/err-code": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", - "dev": true - }, - "node_modules/errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "dev": true, - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es6-module-jstransform": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/es6-module-jstransform/-/es6-module-jstransform-0.1.4.tgz", - "integrity": "sha1-OtHj6qt0UY9a8jwQFPGCjF6WfAI=", - "dependencies": { - "esprima-fb": "~3001.1.0-dev-harmony-fb", - "jstransform": "~3.0.0" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/esprima-fb": { - "version": "3001.1.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", - "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "dependencies": { - "estraverse": "^4.1.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-stream": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz", - "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==", - "dependencies": { - "duplexer": "^0.1.1", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" - } - }, - "node_modules/event-stream/node_modules/stream-combiner": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", - "dependencies": { - "duplexer": "~0.1.1", - "through": "~2.3.4" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", - "dev": true - }, - "node_modules/events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", - "dev": true, - "dependencies": { - "original": "^1.0.0" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dev": true, - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/express/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ext-list": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-0.2.0.tgz", - "integrity": "sha1-NhTV8pn0pZKolinn3oJfF3TRmr0=", - "dependencies": { - "got": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-1.0.1.tgz", - "integrity": "sha1-GCgzVtxAo5NFXFRGDwWZzpfTDgw=", - "dependencies": { - "ext-list": "^0.2.0", - "underscore.string": "~2.3.3" - }, - "bin": { - "extname": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "node_modules/fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, - "node_modules/faye-websocket": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.4.4.tgz", - "integrity": "sha1-wUxbO/FNdBf/v9mQwKdJXNnzN7w=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/file-loader": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", - "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-0.0.2.tgz", - "integrity": "sha1-N83RtbkFQEs/BeGyNkW+aU/3D4I=" - }, - "node_modules/fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "dependencies": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, - "node_modules/fileset/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/fileset/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", - "dev": true, - "dependencies": { - "debug": "^3.0.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/follow-redirects/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/formidable": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz", - "integrity": "sha1-Kz9MQRy7X91pXESEPiojUUpDIxo=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs-extra": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", - "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "node_modules/fs-extra/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/fs-write-stream-atomic/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/fstream": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", - "integrity": "sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=", - "dependencies": { - "graceful-fs": "~3.0.2", - "inherits": "~2.0.0", - "mkdirp": "0.5", - "rimraf": "2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/fstream/node_modules/graceful-fs": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", - "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", - "dependencies": { - "natives": "^1.1.3" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/fstream/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", - "dependencies": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/ftp/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/generator-supported": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/generator-supported/-/generator-supported-0.0.1.tgz", - "integrity": "sha1-kivSIBpsONj6y4FdWT2KA8iYwpk=" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "node_modules/get-stdin": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-0.1.0.tgz", - "integrity": "sha1-WZivJKr8gC0VyCxoVlfuuLENSpE=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-uri": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-0.1.4.tgz", - "integrity": "sha1-NfinlUwSn7Ey/y3fXtgaV8uKnlQ=", - "dependencies": { - "data-uri-to-buffer": "0", - "debug": "2", - "extend": "3", - "file-uri-to-path": "0", - "ftp": "~0.3.5", - "readable-stream": "2" - } - }, - "node_modules/get-uri/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/get-uri/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "dependencies": { - "inherits": "2", - "minimatch": "0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "dependencies": { - "lru-cache": "2", - "sigmund": "~1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globby/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globby/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/got": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/got/-/got-0.2.0.tgz", - "integrity": "sha1-0Awkiyn9zK6pQN+coJlev/MbUaU=", - "dependencies": { - "object-assign": "^0.3.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "dependencies": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "dependencies": { - "isarray": "2.0.1" - } - }, - "node_modules/has-binary2/node_modules/isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "node_modules/has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", - "dev": true - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hosted-git-info": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", - "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", - "dev": true, - "dependencies": { - "lru-cache": "^5.1.1" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", - "dev": true - }, - "node_modules/hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", - "dev": true - }, - "node_modules/html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", - "dev": true - }, - "node_modules/html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", - "dev": true, - "engines": [ - "node >= 0.4.0" - ] - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-0.2.7.tgz", - "integrity": "sha1-4X/aZfCQLZUs55IeYsf/iGJlWl4=", - "dependencies": { - "agent-base": "~1.0.1", - "debug": "2", - "extend": "3" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, - "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "node_modules/https-proxy-agent": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-0.3.6.tgz", - "integrity": "sha1-cT+jjl01P1DrFKNC/r4pAz7RYZs=", - "dependencies": { - "agent-base": "~1.0.1", - "debug": "2", - "extend": "3" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "node_modules/ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true - }, - "node_modules/import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "dev": true, - "dependencies": { - "import-from": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "dev": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dev": true, - "dependencies": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "node_modules/indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "dependencies": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dev": true, - "dependencies": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "dependencies": { - "is-path-inside": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "dependencies": { - "path-is-inside": "^1.0.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "dev": true, - "dependencies": { - "html-comment-regex": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "dependencies": { - "buffer-alloc": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "node_modules/istanbul-api": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.6.tgz", - "integrity": "sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA==", - "dev": true, - "dependencies": { - "async": "^2.6.2", - "compare-versions": "^3.4.0", - "fileset": "^2.0.3", - "istanbul-lib-coverage": "^2.0.5", - "istanbul-lib-hook": "^2.0.7", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^2.2.4", - "js-yaml": "^3.13.1", - "make-dir": "^2.1.0", - "minimatch": "^3.0.4", - "once": "^1.4.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-api/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-api/node_modules/istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "dev": true, - "dependencies": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-api/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/istanbul-api/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-hook": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", - "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", - "dev": true, - "dependencies": { - "append-transform": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", - "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-report/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", - "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", - "dev": true, - "dependencies": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" - }, - "bin": { - "jasmine": "bin/jasmine.js" - } - }, - "node_modules/jasmine-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", - "dev": true - }, - "node_modules/jasmine-spec-reporter": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", - "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", - "dev": true, - "dependencies": { - "colors": "1.1.2" - } - }, - "node_modules/jasmine/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jasmine/node_modules/jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", - "dev": true - }, - "node_modules/jasmine/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true, - "engines": { - "node": ">= 6.9.x" - } - }, - "node_modules/jest-worker": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz", - "integrity": "sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg==", - "dev": true, - "dependencies": { - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 8.3" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-base64": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", - "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.2.0.tgz", - "integrity": "sha512-2tLgY7LRNZ9Hd6gmCuBG5/OjRHQpSgJQqJoYyLLOhUgn8LdOYrjaZLcxkWnDads+AD/haWWioPNziXQcgvQJ/g==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "node_modules/json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, - "node_modules/json5": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", - "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.6" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonfile/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true, - "optional": true - }, - "node_modules/jsonparse": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", - "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", - "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", - "dependencies": { - "jsonparse": "0.0.5", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "index.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "node_modules/jstransform": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz", - "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=", - "dependencies": { - "base62": "0.1.1", - "esprima-fb": "~3001.1.0-dev-harmony-fb", - "source-map": "0.1.31" - }, - "engines": { - "node": ">=0.8.8" - } - }, - "node_modules/jstransform/node_modules/source-map": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", - "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=", - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/jszip": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.3.0.tgz", - "integrity": "sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw==", - "dev": true, - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, - "node_modules/karma": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.3.0.tgz", - "integrity": "sha512-NSPViHOt+RW38oJklvYxQC4BSQsv737oQlr/r06pCM+slDOr4myuI1ivkRmp+3dVpJDfZt2DmaPJ2wkx+ZZuMQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", - "core-js": "^3.1.3", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "2.1.1", - "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", - "dev": true, - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-coverage-istanbul-reporter": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz", - "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==", - "dev": true, - "dependencies": { - "istanbul-api": "^2.1.6", - "minimatch": "^3.0.4" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/karma-jasmine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", - "dev": true, - "dependencies": { - "jasmine-core": "^3.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/karma-jasmine-html-reporter": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.3.tgz", - "integrity": "sha512-ci0VrjuCaFj+9d1tYlTE3KIPUCp0rz874zWWU3JgCMqGIyw5ke+BXWFPOAGAqUdCJcrMwneyvp1zFXA74MiPUA==", - "dev": true - }, - "node_modules/karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", - "dev": true, - "dependencies": { - "source-map-support": "^0.5.5" - } - }, - "node_modules/karma/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/karma/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/karma/node_modules/mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/karma/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, - "node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/less": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz", - "integrity": "sha512-tlWX341RECuTOvoDIvtFqXsKj072hm3+9ymRBe76/mD6O5ZZecnlAOVDlWAleF2+aohFrxNidXhv2773f6kY7g==", - "dev": true, - "dependencies": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0", - "tslib": "^1.10.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "source-map": "~0.6.0" - } - }, - "node_modules/less-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", - "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", - "dev": true, - "dependencies": { - "clone": "^2.1.1", - "loader-utils": "^1.1.0", - "pify": "^4.0.1" - }, - "engines": { - "node": ">= 4.8.0" - } - }, - "node_modules/less-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/less-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/less/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true, - "optional": true - }, - "node_modules/less/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/less/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "optional": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/less/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, - "dependencies": { - "leven": "^3.1.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/license-webpack-plugin": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.4.tgz", - "integrity": "sha512-1Xq72fmPbTg5KofXs+yI5L4QqPFjQ6mZxoeI6D7gfiEDOtaEIk6PGrdLaej90bpDqKNHNxlQ/MW4tMAL6xMPJQ==", - "dev": true, - "dependencies": { - "@types/webpack-sources": "^0.1.5", - "webpack-sources": "^1.2.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", - "dev": true, - "dependencies": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "node_modules/magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.4" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/make-fetch-happen": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.4.tgz", - "integrity": "sha512-hIFoqGq1db0QMiy/Atr/pI1Rs4rDV+ZdGSey2SQyF3KK3u1z4aj9mS5UdNnZkdQpA+H3pGn0J3KlEwsi2x4EqA==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.1.0", - "cacache": "^15.0.0", - "http-cache-semantics": "^4.0.4", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.1.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^5.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/make-fetch-happen/node_modules/agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-fetch-happen/node_modules/smart-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/socks": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "dev": true, - "dependencies": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - }, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", - "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4", - "socks": "^2.3.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dependencies": { - "tmpl": "1.0.x" - } - }, - "node_modules/mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", - "dev": true - }, - "node_modules/map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "dependencies": { - "p-defer": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=" - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "dependencies": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "node_modules/merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/merge-source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz", - "integrity": "sha1-J3yQ+L7zlwlkWoNxxRw7bGSOBow=" - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/mime": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.5.tgz", - "integrity": "sha1-nu0HMCKov14WyFZsaGe4gyv7+hM=" - }, - "node_modules/mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, - "dependencies": { - "mime-db": "1.43.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", - "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", - "dev": true, - "dependencies": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "dependencies": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "node_modules/minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dependencies": { - "lru-cache": "2", - "sigmund": "~1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "node_modules/minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.2.1.tgz", - "integrity": "sha512-ssHt0dkljEDaKmTgQ04DQgx2ag6G2gMPxA5hpcsoeTbfDgRf2fC2gNSRc6kISjD7ckCpHwwQvXxuTBK8402fXg==", - "dev": true, - "dependencies": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-pipeline": "^1.2.2", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/minipass-json-stream/node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/minipass-pipeline": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", - "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" - }, - "node_modules/moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "engines": { - "node": "*" - } - }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/move-concurrently/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "dependencies": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node_modules/nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natives": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", - "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==" - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "node_modules/netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/netrc": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz", - "integrity": "sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ=" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", - "dev": true, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "node_modules/node-libs-browser/node_modules/safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "node_modules/node-libs-browser/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/node-releases": { - "version": "1.1.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz", - "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==", - "dev": true - }, - "node_modules/nopt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", - "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/noptify": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/noptify/-/noptify-0.0.3.tgz", - "integrity": "sha1-WPZUpz2XU98MUdlobckhBKZ/S7s=", - "dependencies": { - "nopt": "~2.0.0" - } - }, - "node_modules/noptify/node_modules/nopt": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.0.0.tgz", - "integrity": "sha1-ynQW8gpeP5w7hhgPlilfo9C1Lg0=", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/npm-install-checks": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", - "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", - "dev": true, - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-install-checks/node_modules/semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true - }, - "node_modules/npm-package-arg": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.0.1.tgz", - "integrity": "sha512-/h5Fm6a/exByzFSTm7jAyHbgOqErl9qSNJDQF32Si/ZzgwT2TERVxRxn3Jurw1wflgyVVAxnFR4fRHPM7y1ClQ==", - "dev": true, - "dependencies": { - "hosted-git-info": "^3.0.2", - "semver": "^7.0.0", - "validate-npm-package-name": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-packlist": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.1.1.tgz", - "integrity": "sha512-95TSDvGwujIhqfSpIiRRLodEF+y6mJMopuZdahoGzqtRDFZXGav46S0p6ngeWaiAkb5R72w6eVARhzej0HvZeQ==", - "dev": true, - "dependencies": { - "glob": "^7.1.6", - "ignore-walk": "^3.0.3", - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - }, - "bin": { - "npm-packlist": "bin/index.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-packlist/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm-packlist/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm-pick-manifest": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.0.0.tgz", - "integrity": "sha512-PdJpXMvjqt4nftNEDpCgjBUF8yI3Q3MyuAmVB9nemnnCg32F4BPL/JFBfdj8DubgHCYUFQhtLWmBPvdsFtjWMg==", - "dev": true, - "dependencies": { - "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.0.0", - "semver": "^7.0.0" - } - }, - "node_modules/npm-pick-manifest/node_modules/semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-registry-fetch": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-8.0.0.tgz", - "integrity": "sha512-975WwLvZjX97y9UWWQ8nAyr7bw02s9xKPHqvEm5T900LQsB1HXb8Gb9ebYtCBLSX+K8gSOrO5KS/9yV/naLZmQ==", - "dev": true, - "dependencies": { - "@npmcli/ci-detect": "^1.0.0", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^8.0.2", - "minipass": "^3.0.0", - "minipass-fetch": "^1.1.2", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.0.0", - "npm-package-arg": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/npm-registry-fetch/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "dependencies": { - "boolbase": "~1.0.0" - } - }, - "node_modules/num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-0.3.1.tgz", - "integrity": "sha1-Bg4qKifXwNd+x3t48Rqkf9iACNI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "node_modules/object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/open": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz", - "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dev": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/opn/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "node_modules/optimist/node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.3.tgz", - "integrity": "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg==", - "dev": true, - "dependencies": { - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.2.0", - "is-interactive": "^1.0.0", - "log-symbols": "^3.0.0", - "mute-stream": "0.0.8", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "dependencies": { - "url-parse": "^1.4.3" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dev": true, - "dependencies": { - "retry": "^0.12.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pac-proxy-agent": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-0.2.0.tgz", - "integrity": "sha1-rZApCdkvT+fMLl9Z9b9QYbz6cbI=", - "dependencies": { - "agent-base": "~1.0.1", - "debug": "2", - "extend": "~1.2.1", - "get-uri": "~0.1.0", - "pac-resolver": "~1.2.1", - "proxy-agent": "1", - "stream-to-array": "~1.0.0" - } - }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/pac-proxy-agent/node_modules/extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", - "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=" - }, - "node_modules/pac-proxy-agent/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/pac-resolver": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-1.2.6.tgz", - "integrity": "sha1-7QOvDFtZM1Bb3T8H91F1Rm1efPs=", - "dependencies": { - "co": "~3.0.6", - "degenerator": "~1.0.0", - "netmask": "~1.0.4", - "regenerator": "~0.8.13", - "thunkify": "~2.1.1" - } - }, - "node_modules/pac-resolver/node_modules/co": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", - "integrity": "sha1-FEXyJsXrlWE45oyawwFn6n0ua9o=" - }, - "node_modules/pacote": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.1.4.tgz", - "integrity": "sha512-eUGJvSSpWFZKn3z8gig/HgnBmUl6gIWByIIaHzSyEr3tOWX0w8tFEADXtpu8HGv5E0ShCeTP6enRq8iHKCHSvw==", - "dev": true, - "dependencies": { - "@npmcli/git": "^2.0.1", - "@npmcli/installed-package-contents": "^1.0.5", - "@npmcli/promise-spawn": "^1.1.0", - "cacache": "^15.0.0", - "chownr": "^1.1.4", - "fs-minipass": "^2.1.0", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.1", - "minipass-fetch": "^1.2.1", - "mkdirp": "^1.0.3", - "npm-package-arg": "^8.0.1", - "npm-packlist": "^2.1.0", - "npm-pick-manifest": "^6.0.0", - "npm-registry-fetch": "^8.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "read-package-json-fast": "^1.1.3", - "rimraf": "^2.7.1", - "semver": "^7.1.3", - "ssri": "^8.0.0", - "tar": "^6.0.1", - "which": "^2.0.2" - }, - "bin": { - "pacote": "lib/bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pacote/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/pacote/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pacote/node_modules/semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pacote/node_modules/tar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", - "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", - "dev": true, - "dependencies": { - "chownr": "^1.1.3", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/pacote/node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/pacote/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/pacote/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", - "dev": true, - "dependencies": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "optional": true - }, - "node_modules/parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "dependencies": { - "better-assert": "~1.0.0" - } - }, - "node_modules/parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "dependencies": { - "better-assert": "~1.0.0" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dependencies": { - "through": "~2.3" - } - }, - "node_modules/pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", - "dev": true, - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/portfinder/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-2.2.6.tgz", - "integrity": "sha1-wENE4kSeRYa5Vfvkp093CA2EVx8=", - "dependencies": { - "js-base64": "~2.1.5", - "source-map": "~0.1.40" - } - }, - "node_modules/postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/postcss-calc/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-calc/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-calc/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-colormin/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-colormin/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-colormin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-colormin/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-convert-values/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-convert-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-convert-values/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-convert-values/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-comments/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-discard-comments/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-discard-comments/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-duplicates/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-discard-duplicates/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-discard-duplicates/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-empty/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-discard-empty/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-discard-empty/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-discard-overridden/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-discard-overridden/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-discard-overridden/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-import": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", - "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", - "dev": true, - "dependencies": { - "postcss": "^7.0.1", - "postcss-value-parser": "^3.2.3", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-import/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-import/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-import/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-import/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "dev": true, - "dependencies": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "dev": true, - "dependencies": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/postcss-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss-loader/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-loader/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/postcss-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-loader/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "dev": true, - "dependencies": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-longhand/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-merge-longhand/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-merge-longhand/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-merge-longhand/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-merge-rules/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-merge-rules/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-merge-rules/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-font-values/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-minify-font-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-font-values/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-minify-font-values/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-gradients/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-minify-gradients/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-gradients/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-minify-gradients/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-params/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-minify-params/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-minify-params/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-minify-params/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-minify-selectors/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-minify-selectors/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-minify-selectors/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-charset/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-charset/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-charset/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-display-values/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-display-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-display-values/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-display-values/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-positions/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-positions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-positions/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-positions/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-repeat-style/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-repeat-style/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-repeat-style/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-repeat-style/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "dev": true, - "dependencies": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-string/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-string/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-string/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-string/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-timing-functions/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-timing-functions/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-timing-functions/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-timing-functions/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-unicode/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-unicode/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-unicode/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-unicode/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "dev": true, - "dependencies": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-url/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-url/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-url/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-url/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "dev": true, - "dependencies": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-normalize-whitespace/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-normalize-whitespace/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-normalize-whitespace/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-normalize-whitespace/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "dev": true, - "dependencies": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-ordered-values/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-ordered-values/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-ordered-values/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-ordered-values/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-initial/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-reduce-initial/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-reduce-initial/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "dev": true, - "dependencies": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-reduce-transforms/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-reduce-transforms/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-reduce-transforms/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-reduce-transforms/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "dev": true, - "dependencies": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-svgo/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-svgo/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "node_modules/postcss-svgo/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-svgo/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "dev": true, - "dependencies": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/postcss-unique-selectors/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/postcss-unique-selectors/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-unique-selectors/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", - "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "optional": true, - "dependencies": { - "asap": "~2.0.3" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "node_modules/promise-retry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", - "dev": true, - "dependencies": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/promise-retry/node_modules/retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/protractor": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.3.tgz", - "integrity": "sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA==", - "dev": true, - "dependencies": { - "@types/q": "^0.0.32", - "@types/selenium-webdriver": "^3.0.0", - "blocking-proxy": "^1.0.0", - "browserstack": "^1.5.1", - "chalk": "^1.1.3", - "glob": "^7.0.3", - "jasmine": "2.8.0", - "jasminewd2": "^2.1.0", - "optimist": "~0.6.0", - "q": "1.4.1", - "saucelabs": "^1.5.0", - "selenium-webdriver": "3.6.0", - "source-map-support": "~0.4.0", - "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.0.6" - }, - "bin": { - "protractor": "bin/protractor", - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/protractor/node_modules/@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", - "dev": true - }, - "node_modules/protractor/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "dependencies": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/protractor/node_modules/globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "dependencies": { - "is-path-inside": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "dependencies": { - "path-is-inside": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/protractor/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/protractor/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/protractor/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/protractor/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/protractor/node_modules/webdriver-manager": { - "version": "12.1.7", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.7.tgz", - "integrity": "sha512-XINj6b8CYuUYC93SG3xPkxlyUc3IJbD6Vvo75CVGuG9uzsefDzWQrhz0Lq8vbPxtb4d63CZdYophF8k8Or/YiA==", - "dev": true, - "dependencies": { - "adm-zip": "^0.4.9", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "^1.2.0", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" - }, - "bin": { - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dev": true, - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-agent": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-1.1.1.tgz", - "integrity": "sha1-/LHu9eWJZcmV+TjwKdcp/IGFi5U=", - "dependencies": { - "http-proxy-agent": "0", - "https-proxy-agent": "0", - "lru-cache": "~2.5.0", - "pac-proxy-agent": "0", - "socks-proxy-agent": "1" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz", - "integrity": "sha1-H92tk4quEmPOE4aAvhs/WRwKtBw=" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.5.tgz", - "integrity": "sha1-KUsmjksNQlD23eGbO4s0k13/FO8=", - "engines": { - "node": "*" - } - }, - "node_modules/query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "dependencies": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/query-string/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.4.tgz", - "integrity": "sha1-zMfd/Ea3KGHN1btDPIQLcLbyf1Q=", - "dependencies": { - "bytes": "1.0.0", - "iconv-lite": "0.4.8" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.8.tgz", - "integrity": "sha1-xgGadZXyzvynAuq2lKAQvNkpjSA=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/raw-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.0.tgz", - "integrity": "sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==", - "dev": true, - "dependencies": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.5.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/raw-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/raw-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", - "dev": true, - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/read-cache/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-package-json": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", - "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", - "dev": true, - "dependencies": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/read-package-json-fast": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-1.1.3.tgz", - "integrity": "sha512-MmFqiyfCXV2Dmm4jH24DEGhxdkUDFivJQj4oPZQPOKywxR7HWBE6WnMWDAapfFHi3wm1b+mhR+XHlUH0CL8axg==", - "dev": true, - "dependencies": { - "json-parse-even-better-errors": "^2.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/read-package-json/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/read-package-json/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true, - "optional": true - }, - "node_modules/read-package-json/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/read-package-tree": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", - "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", - "dev": true, - "dependencies": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "dev": true, - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "node_modules/readdir-scoped-modules/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.0.7" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recast": { - "version": "0.10.33", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz", - "integrity": "sha1-lCgI96oBbx+nFCxGHX5XBKqo1pc=", - "dependencies": { - "ast-types": "0.8.12", - "esprima-fb": "~15001.1001.0-dev-harmony-fb", - "private": "~0.1.5", - "source-map": "~0.5.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/recast/node_modules/ast-types": { - "version": "0.8.12", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz", - "integrity": "sha1-oNkOQ1G7iHcWyD/WN+v4GK9K38w=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/recast/node_modules/esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/recast/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/reduce-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz", - "integrity": "sha1-4Mk1QsV0UhvqE98PlIjtgqt3xdo=" - }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true - }, - "node_modules/regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator": { - "version": "0.8.46", - "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.46.tgz", - "integrity": "sha1-FUwydoY2HtUsrWmyVF78U6PQdpY=", - "dependencies": { - "commoner": "~0.10.3", - "defs": "~1.1.0", - "esprima-fb": "~15001.1001.0-dev-harmony-fb", - "private": "~0.1.5", - "recast": "0.10.33", - "regenerator-runtime": "~0.9.5", - "through": "~2.3.8" - }, - "bin": { - "regenerator": "bin/regenerator" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz", - "integrity": "sha1-0z65XQ0gAaS+OWWXB8UbDLcc4Ck=" - }, - "node_modules/regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" - } - }, - "node_modules/regenerator/node_modules/esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "node_modules/requires": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/requires/-/requires-1.0.2.tgz", - "integrity": "sha1-djBOghNFYi/j+sCwcRoeTygo8Po=" - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "node_modules/resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - } - }, - "node_modules/resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rfdc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", - "dev": true - }, - "node_modules/rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", - "dev": true - }, - "node_modules/rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", - "dev": true - }, - "node_modules/right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dependencies": { - "align-text": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rollup": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.1.0.tgz", - "integrity": "sha512-gfE1455AEazVVTJoeQtcOq/U6GSxwoj4XPSWVsuWmgIxj7sBQNLDOSA82PbdMe+cP8ql8fR1jogPFe8Wg8g4SQ==", - "dev": true, - "dependencies": { - "fsevents": "~2.1.2" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "node_modules/run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "dependencies": { - "is-promise": "^2.1.0" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sane": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/sane/-/sane-0.8.1.tgz", - "integrity": "sha1-JDpLIECBvlWTwKIrC7sRVzUS/Mc=", - "dependencies": { - "minimatch": "~0.2.14", - "walker": "~1.0.5", - "watch": "~0.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/sass": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.3.tgz", - "integrity": "sha512-5NMHI1+YFYw4sN3yfKjpLuV9B5l7MqQ6FlkTcC4FT+oHbBRUZoSjHrrt/mE0nFXJyY2kQtU9ou9HxvFVjLFuuw==", - "dev": true, - "dependencies": { - "chokidar": ">=2.0.0 <4.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/sass-loader": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", - "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "loader-utils": "^1.2.3", - "neo-async": "^2.6.1", - "schema-utils": "^2.6.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">= 8.9.0" - } - }, - "node_modules/sass-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/sass-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/sass-loader/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/saucelabs": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", - "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/saucelabs/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/saucelabs/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/saucelabs/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "node_modules/schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "node_modules/selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", - "dev": true, - "dependencies": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/selenium-webdriver/node_modules/tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", - "dev": true, - "dependencies": { - "node-forge": "0.9.0" - } - }, - "node_modules/semver": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-2.3.2.tgz", - "integrity": "sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI=", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-dsl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", - "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", - "dev": true, - "dependencies": { - "semver": "^5.3.0" - } - }, - "node_modules/semver-dsl/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-intersect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", - "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==", - "dev": true, - "dependencies": { - "semver": "^5.0.0" - } - }, - "node_modules/semver-intersect/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shallow-clone/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/simple-fmt": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz", - "integrity": "sha1-GRv1ZqWeZTBILLJatTtKjchcOms=" - }, - "node_modules/simple-is": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz", - "integrity": "sha1-Krt1qt453rXMgVzhDmGRFkhQuvA=" - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - }, - "node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/smart-buffer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", - "engines": { - "node": ">= 0.10.15", - "npm": ">= 1.3.5" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/socket.io": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", - "dev": true, - "dependencies": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", - "dev": true - }, - "node_modules/socket.io-client": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", - "dev": true, - "dependencies": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", - "to-array": "0.1.4" - } - }, - "node_modules/socket.io-client/node_modules/component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/socket.io-client/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/socket.io-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", - "dev": true, - "dependencies": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - } - }, - "node_modules/socket.io-parser/node_modules/component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/socket.io/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.10.0", - "uuid": "^3.0.1" - } - }, - "node_modules/sockjs-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", - "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", - "dev": true, - "dependencies": { - "debug": "^3.2.5", - "eventsource": "^1.0.7", - "faye-websocket": "~0.11.1", - "inherits": "^2.0.3", - "json3": "^3.3.2", - "url-parse": "^1.4.3" - } - }, - "node_modules/sockjs-client/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/sockjs-client/node_modules/faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/sockjs/node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/sockjs/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/socks": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", - "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", - "dependencies": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" - }, - "engines": { - "node": ">= 0.10.0", - "npm": ">= 1.3.5" - } - }, - "node_modules/socks-proxy-agent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-1.0.2.tgz", - "integrity": "sha1-Z+BrRH/lY3QX/eVzPL/f7J/+EX8=", - "dependencies": { - "agent-base": "~1.0.1", - "extend": "~1.2.1", - "socks": "~1.1.5" - } - }, - "node_modules/socks-proxy-agent/node_modules/extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", - "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=" - }, - "node_modules/sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "dependencies": { - "is-plain-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/source-map-loader": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", - "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", - "dev": true, - "dependencies": { - "async": "^2.5.0", - "loader-utils": "^1.1.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/source-map-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/source-map-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "node_modules/spdy": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", - "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/spdy-transport/node_modules/safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "node_modules/spdy-transport/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/speed-measure-webpack-plugin": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz", - "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==", - "dev": true, - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dependencies": { - "duplexer": "~0.1.1" - } - }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/stream-to-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-1.0.0.tgz", - "integrity": "sha1-lBZrsp8+ok8ILS+M0+uyzA1uyiw=", - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", - "dev": true, - "dependencies": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/streamroller/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/streamroller/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", - "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", - "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/stringmap": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz", - "integrity": "sha1-VWwTeyWPlCuHdvWy71gqoGnX0bE=" - }, - "node_modules/stringset": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz", - "integrity": "sha1-7yWcTjSTRDd/zRyRPdLoSMnAQrU=" - }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/style-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", - "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", - "dev": true, - "dependencies": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.6.4" - }, - "engines": { - "node": ">= 8.9.0" - } - }, - "node_modules/style-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/style-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "dev": true, - "dependencies": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/stylehacks/node_modules/postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/stylehacks/node_modules/postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/stylehacks/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stylehacks/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stylus": { - "version": "0.54.7", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.7.tgz", - "integrity": "sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug==", - "dev": true, - "dependencies": { - "css-parse": "~2.0.0", - "debug": "~3.1.0", - "glob": "^7.1.3", - "mkdirp": "~0.5.x", - "safer-buffer": "^2.1.2", - "sax": "~1.2.4", - "semver": "^6.0.0", - "source-map": "^0.7.3" - }, - "bin": { - "stylus": "bin/stylus" - }, - "engines": { - "node": "*" - } - }, - "node_modules/stylus-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", - "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", - "dev": true, - "dependencies": { - "loader-utils": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "when": "~3.6.x" - } - }, - "node_modules/stylus-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/stylus-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/stylus/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/stylus/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/stylus/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/stylus/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/stylus/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/stylus/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/stylus/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/superagent": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-0.17.0.tgz", - "integrity": "sha1-qtzVD75ak+cZkRGNeb8HFNYlu6g=", - "dependencies": { - "cookiejar": "1.3.0", - "debug": "~0.7.2", - "emitter-component": "1.0.0", - "extend": "~1.2.1", - "formidable": "1.0.14", - "methods": "0.0.1", - "mime": "1.2.5", - "qs": "0.6.5", - "reduce-component": "1.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", - "engines": { - "node": "*" - } - }, - "node_modules/superagent/node_modules/extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", - "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=" - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "dev": true, - "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/svgo/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dependencies": { - "acorn-node": "^1.2.0" - } - }, - "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/tar/-/tar-0.1.20.tgz", - "integrity": "sha1-QpQLrltfIsdEg2mRJvnz8nRJyxM=", - "dependencies": { - "block-stream": "*", - "fstream": "~0.1.28", - "inherits": "2" - } - }, - "node_modules/tempfile": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-0.1.3.tgz", - "integrity": "sha1-fWtxAEcznTn4RzJ6BW2t8YMQMBA=", - "dependencies": { - "uuid": "~1.4.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", - "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz", - "integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==", - "dev": true, - "dependencies": { - "cacache": "^13.0.1", - "find-cache-dir": "^3.2.0", - "jest-worker": "^25.1.0", - "p-limit": "^2.2.2", - "schema-utils": "^2.6.4", - "serialize-javascript": "^2.1.2", - "source-map": "^0.6.1", - "terser": "^4.4.3", - "webpack-sources": "^1.4.3" - }, - "engines": { - "node": ">= 8.9.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", - "dev": true, - "dependencies": { - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^7.0.0", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/terser-webpack-plugin/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/terser-webpack-plugin/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/terser-webpack-plugin/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/terser-webpack-plugin/node_modules/p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", - "dev": true, - "dependencies": { - "figgy-pudding": "^3.5.1", - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=" - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "node_modules/timers-browserify": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", - "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", - "dev": true - }, - "node_modules/tiny-lr-fork": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/tiny-lr-fork/-/tiny-lr-fork-0.0.5.tgz", - "integrity": "sha1-Hpnh4qhGm3NquX2X7vqYxx927Qo=", - "dependencies": { - "debug": "~0.7.0", - "faye-websocket": "~0.4.3", - "noptify": "~0.0.3", - "qs": "~0.5.2" - }, - "bin": { - "tiny-lr-fork": "bin/tiny-lr" - } - }, - "node_modules/tiny-lr-fork/node_modules/debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", - "engines": { - "node": "*" - } - }, - "node_modules/tiny-lr-fork/node_modules/qs": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz", - "integrity": "sha1-MbGtBYVnZRxSaSFQa5qHk5EaA4Q=", - "engines": { - "node": "*" - } - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" - }, - "node_modules/to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "node_modules/to-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-descriptor/-/to-descriptor-1.0.1.tgz", - "integrity": "sha1-oOZ4w068fS2uRk2DcrwhR52cK80=" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/tryor": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz", - "integrity": "sha1-gUXkynyv9ArN48z5Rui4u3W0Fys=" - }, - "node_modules/ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" - }, - "bin": { - "ts-node": "dist/bin.js" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" - }, - "node_modules/tslint": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", - "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" - }, - "bin": { - "tslint": "bin/tslint" - }, - "engines": { - "node": ">=4.8.0" - } - }, - "node_modules/tslint/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslint/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tslint/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tslint/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/tslint/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - } - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "node_modules/typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, - "node_modules/underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "engines": { - "node": "*" - } - }, - "node_modules/unglob": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unglob/-/unglob-0.1.2.tgz", - "integrity": "sha1-8uwGKE5JGhx3YRp2CouOowAnLDg=", - "dependencies": { - "generator-supported": "~0.0.1", - "glob": "~3.2.8", - "minimatch": "~0.2.14" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "node_modules/uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/universal-analytics": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", - "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", - "dev": true, - "dependencies": { - "debug": "^3.0.0", - "request": "^2.88.0", - "uuid": "^3.0.0" - } - }, - "node_modules/universal-analytics/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/universal-analytics/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", - "dev": true - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "dependencies": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - } - }, - "node_modules/useragent/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/useragent/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/util-promisify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", - "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", - "dev": true, - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "node_modules/util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-1.4.2.tgz", - "integrity": "sha1-RTAZ9oaWam34PNxSROfJkOzDMvw=" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, - "dependencies": { - "builtins": "^1.0.3" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "dev": true - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dependencies": { - "makeerror": "1.0.x" - } - }, - "node_modules/watch": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz", - "integrity": "sha1-d3mLLaD5kQ1ZXxrOWwwiWFIfIdw=", - "engines": [ - "node >=0.1.95" - ] - }, - "node_modules/watchpack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", - "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", - "dev": true, - "dependencies": { - "chokidar": "^2.1.8", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - } - }, - "node_modules/watchpack/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/watchpack/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "bundleDependencies": [ - "abbrev", - "ansi-regex", - "aproba", - "are-we-there-yet", - "balanced-match", - "brace-expansion", - "chownr", - "code-point-at", - "concat-map", - "console-control-strings", - "core-util-is", - "debug", - "deep-extend", - "delegates", - "detect-libc", - "fs-minipass", - "fs.realpath", - "gauge", - "glob", - "has-unicode", - "iconv-lite", - "ignore-walk", - "inflight", - "inherits", - "ini", - "is-fullwidth-code-point", - "isarray", - "minimatch", - "minimist", - "minipass", - "minizlib", - "mkdirp", - "ms", - "needle", - "node-pre-gyp", - "nopt", - "npm-bundled", - "npm-normalize-package-bin", - "npm-packlist", - "npmlog", - "number-is-nan", - "object-assign", - "once", - "os-homedir", - "os-tmpdir", - "osenv", - "path-is-absolute", - "process-nextick-args", - "rc", - "readable-stream", - "rimraf", - "safe-buffer", - "safer-buffer", - "sax", - "semver", - "set-blocking", - "signal-exit", - "string-width", - "string_decoder", - "strip-ansi", - "strip-json-comments", - "tar", - "util-deprecate", - "wide-align", - "wrappy", - "yallist" - ], - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "extraneous": true, - "inBundle": true, - "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^2.6.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "extraneous": true, - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "extraneous": true, - "inBundle": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/fsevents/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/watchpack/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/watchpack/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/watchpack/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webdriver-js-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", - "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", - "dev": true, - "dependencies": { - "@types/selenium-webdriver": "^3.0.0", - "selenium-webdriver": "^3.0.1" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/webpack": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz", - "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/wasm-edit": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.1", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.0", - "webpack-sources": "^1.4.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", - "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", - "dev": true, - "dependencies": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/webpack-dev-server": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", - "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", - "dev": true, - "dependencies": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.2.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.6", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.25", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.7", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "0.3.19", - "sockjs-client": "1.4.0", - "spdy": "^4.0.1", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "12.0.5" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 6.11.5" - } - }, - "node_modules/webpack-dev-server/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "dependencies": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "bundleDependencies": [ - "abbrev", - "ansi-regex", - "aproba", - "are-we-there-yet", - "balanced-match", - "brace-expansion", - "chownr", - "code-point-at", - "concat-map", - "console-control-strings", - "core-util-is", - "debug", - "deep-extend", - "delegates", - "detect-libc", - "fs-minipass", - "fs.realpath", - "gauge", - "glob", - "has-unicode", - "iconv-lite", - "ignore-walk", - "inflight", - "inherits", - "ini", - "is-fullwidth-code-point", - "isarray", - "minimatch", - "minimist", - "minipass", - "minizlib", - "mkdirp", - "ms", - "needle", - "node-pre-gyp", - "nopt", - "npm-bundled", - "npm-normalize-package-bin", - "npm-packlist", - "npmlog", - "number-is-nan", - "object-assign", - "once", - "os-homedir", - "os-tmpdir", - "osenv", - "path-is-absolute", - "process-nextick-args", - "rc", - "readable-stream", - "rimraf", - "safe-buffer", - "safer-buffer", - "sax", - "semver", - "set-blocking", - "signal-exit", - "string-width", - "string_decoder", - "strip-ansi", - "strip-json-comments", - "tar", - "util-deprecate", - "wide-align", - "wrappy", - "yallist" - ], - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "extraneous": true, - "inBundle": true, - "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^2.6.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "extraneous": true, - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "extraneous": true, - "inBundle": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "extraneous": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "extraneous": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "extraneous": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/fsevents/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "extraneous": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/webpack-dev-server/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/webpack-dev-server/node_modules/invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-dev-server/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "dependencies": { - "invert-kv": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "dependencies": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack-dev-server/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/webpack-dev-server/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "dependencies": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "node_modules/webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "dependencies": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-log/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/webpack-sources/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-subresource-integrity": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.4.0.tgz", - "integrity": "sha512-GB1kB/LwAWC3CxwcedGhMkxGpNZxSheCe1q+KJP1bakuieAdX/rGHEcf5zsEzhKXpqsGqokgsDoD9dIkr61VDQ==", - "dev": true, - "dependencies": { - "webpack-sources": "^1.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack/node_modules/acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack/node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/webpack/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/webpack/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "node_modules/webpack/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/webpack/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/webpack/node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/webpack/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/webpack/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/webpack/node_modules/terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", - "dev": true, - "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/webpack/node_modules/y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "node_modules/webpack/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.4.0 <0.4.11", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/when": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", - "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", - "dev": true - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/win-fork": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/win-fork/-/win-fork-1.1.1.tgz", - "integrity": "sha1-j1jgZW/KAK3IyGoriePNLWotXl4=", - "bin": { - "win-fork": "bin/win-spawn", - "win-line-endings": "bin/win-line-endings", - "win-spawn": "bin/win-spawn" - } - }, - "node_modules/window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=", - "bin": { - "window-size": "cli.js" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "dependencies": { - "errno": "~0.1.7" - } - }, - "node_modules/worker-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-4.0.2.tgz", - "integrity": "sha512-V+1zSZMOOKk+uBzKyNIODLQLsx59zSIOaI75J1EMS0iR1qy+KQR3y/pQ3T0vIhvPfDFapGRMsoMvQNEL3okqSA==", - "dev": true, - "dependencies": { - "loader-utils": "^1.1.0" - } - }, - "node_modules/worker-plugin/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/worker-plugin/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-to": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/write-to/-/write-to-1.1.1.tgz", - "integrity": "sha1-Xu+/aC0SjCqwwF9l3xA4c54GxqM=", - "dependencies": { - "dethroy": "1", - "mkdirp": "0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz", - "integrity": "sha1-ISBUaTFuk5Ex1Z8toMbX+YIh6kA=", - "dependencies": { - "camelcase": "^1.2.1", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "os-locale": "^1.4.0", - "window-size": "^0.1.2", - "y18n": "^3.2.0" - } - }, - "node_modules/yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/zone.js": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz", - "integrity": "sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg==" - } - }, - "dependencies": { - "requires": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/requires/-/requires-1.0.2.tgz", - "integrity": "sha1-djBOghNFYi/j+sCwcRoeTygo8Po=" - }, - "@angular-devkit/architect": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.0.tgz", - "integrity": "sha512-SlqEBkPrT40zMCy5344AsUqC76pEPCaGPaAkCIvadaz2dC9vNMzQrvubCPJHViD/TumkSX1kYmLS3iYASVM9GQ==", - "dev": true, - "requires": { - "@angular-devkit/core": "9.1.0", - "rxjs": "6.5.4" - }, - "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - } - } - }, - "@angular-devkit/build-angular": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.901.0.tgz", - "integrity": "sha512-ftJVNlKvIomqRfr5jFVraPqlLSUJu8YyVbFv/aCsvhNpuZGkYpTOMoJDwyywdslSTH608BIoU63IAnIz9PwUdw==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/build-optimizer": "0.901.0", - "@angular-devkit/build-webpack": "0.901.0", - "@angular-devkit/core": "9.1.0", - "@babel/core": "7.9.0", - "@babel/generator": "7.9.3", - "@babel/preset-env": "7.9.0", - "@babel/template": "7.8.6", - "@jsdevtools/coverage-istanbul-loader": "3.0.3", - "@ngtools/webpack": "9.1.0", - "ajv": "6.12.0", - "autoprefixer": "9.7.4", - "babel-loader": "8.0.6", - "browserslist": "^4.9.1", - "cacache": "15.0.0", - "caniuse-lite": "^1.0.30001032", - "circular-dependency-plugin": "5.2.0", - "copy-webpack-plugin": "5.1.1", - "core-js": "3.6.4", - "cssnano": "4.1.10", - "file-loader": "6.0.0", - "find-cache-dir": "3.3.1", - "glob": "7.1.6", - "jest-worker": "25.1.0", - "karma-source-map-support": "1.4.0", - "less": "3.11.1", - "less-loader": "5.0.0", - "license-webpack-plugin": "2.1.4", - "loader-utils": "2.0.0", - "mini-css-extract-plugin": "0.9.0", - "minimatch": "3.0.4", - "open": "7.0.3", - "parse5": "4.0.0", - "postcss": "7.0.27", - "postcss-import": "12.0.1", - "postcss-loader": "3.0.0", - "raw-loader": "4.0.0", - "regenerator-runtime": "0.13.5", - "rimraf": "3.0.2", - "rollup": "2.1.0", - "rxjs": "6.5.4", - "sass": "1.26.3", - "sass-loader": "8.0.2", - "semver": "7.1.3", - "source-map": "0.7.3", - "source-map-loader": "0.2.4", - "source-map-support": "0.5.16", - "speed-measure-webpack-plugin": "1.3.1", - "style-loader": "1.1.3", - "stylus": "0.54.7", - "stylus-loader": "3.0.2", - "terser": "4.6.7", - "terser-webpack-plugin": "2.3.5", - "tree-kill": "1.2.2", - "webpack": "4.42.0", - "webpack-dev-middleware": "3.7.2", - "webpack-dev-server": "3.10.3", - "webpack-merge": "4.2.2", - "webpack-sources": "1.4.3", - "webpack-subresource-integrity": "1.4.0", - "worker-plugin": "4.0.2" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@angular-devkit/build-optimizer": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.901.0.tgz", - "integrity": "sha512-Y9sz8uf2zjilhPUVYb0K9Mio6c1d5c+csuDc15CCKzELXJwyyDxilIFgn6Eu+edM0HNQGzbIwkjy4DkR9mtuTQ==", - "dev": true, - "requires": { - "loader-utils": "2.0.0", - "source-map": "0.7.3", - "tslib": "1.11.1", - "typescript": "3.8.3", - "webpack-sources": "1.4.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - }, - "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", - "dev": true - } - } - }, - "@angular-devkit/build-webpack": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.901.0.tgz", - "integrity": "sha512-Oze0VzIvHnoW12C80fiNH4HBu/GWmhJPXdNA7nRkU/tBQlIKnfngf8rQ0QbgecN2qdEXQpZJsP/XclTi3zugsg==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/core": "9.1.0", - "rxjs": "6.5.4" - }, - "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - } - } - }, - "@angular-devkit/core": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.0.tgz", - "integrity": "sha512-vHTsrB4JaVUQ95FRnKrgo79Y3F6FokImrZdrmwkQmwAThpjXeXmpUEKZS+ZSTFRgesjiIysVGOFijARP4BQ7Bg==", - "dev": true, - "requires": { - "ajv": "6.12.0", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.5.4", - "source-map": "0.7.3" - }, - "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "@angular-devkit/schematics": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.1.0.tgz", - "integrity": "sha512-cb9PSvskMwWlL54fPfCcpJoyNDWAX6Wo7CzL5qpIB2cJCPLAuyfRUYYrkO77YUST+n2HvypHz0cZ5SNGMfaaBQ==", - "dev": true, - "requires": { - "@angular-devkit/core": "9.1.0", - "ora": "4.0.3", - "rxjs": "6.5.4" - }, - "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - } - } - }, - "@angular/animations": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.0.tgz", - "integrity": "sha512-o7X3HM+eocoryw3VrDUtG6Wci2KwtzyBFo3KBJXjQ16X6fwdkjTG+hLb7pp2CBFBEJW4tPYEy7cSBmEfMRTqag==" - }, - "@angular/cdk": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.0.tgz", - "integrity": "sha512-jeeznvNDpR9POuxzz8Y0zFvMynG9HCJo3ZPTqOjlOq8Lj8876+rLsHDvKEMeLdwlkdi1EweYJW1CLQzI+TwqDA==", - "requires": { - "parse5": "^5.0.0" - } - }, - "@angular/cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.0.tgz", - "integrity": "sha512-ofum4gPE/W3fKyzuJrpdHeOS0ZL8x0eYCgsrMyUoFodSpb5LWPqeW+56NgDTpIeny+Trx3pM9dr9QTUVTJ0vYg==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0", - "@schematics/angular": "9.1.0", - "@schematics/update": "0.901.0", - "@yarnpkg/lockfile": "1.1.0", - "ansi-colors": "4.1.1", - "debug": "4.1.1", - "ini": "1.3.5", - "inquirer": "7.1.0", - "npm-package-arg": "8.0.1", - "npm-pick-manifest": "6.0.0", - "open": "7.0.3", - "pacote": "11.1.4", - "read-package-tree": "5.3.1", - "rimraf": "3.0.2", - "semver": "7.1.3", - "symbol-observable": "1.2.0", - "universal-analytics": "0.4.20", - "uuid": "7.0.2" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - }, - "uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==", - "dev": true - } - } - }, - "@angular/common": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-9.1.0.tgz", - "integrity": "sha512-6JPLNtMhI03bGTVQJeSwc+dTjV6DtP7M/BAyzIV0InZP1D6XsOh2QahLFIaaN2sSxYA2ClKuwfX1v+rx9AbXQA==" - }, - "@angular/compiler": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.1.0.tgz", - "integrity": "sha512-QHw/JSeTXHiJQ2Ih0EtU7FGsYcOr+0hwZhqwSW3EEn8TtUgA3DS5lXeiDV66f+3DdvNZFPmgiZIvun3ypxn1HA==" - }, - "@angular/compiler-cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.1.0.tgz", - "integrity": "sha512-xZ8mVPmPporSTtvNA+cbFJQymLzuWfMX6HDDgztZ2eZ5WcQJYloRN4CcYMEzDhCxfV1Zw9Tfc2l14jZD8osi6g==", - "dev": true, - "requires": { - "canonical-path": "1.0.0", - "chokidar": "^3.0.0", - "convert-source-map": "^1.5.1", - "dependency-graph": "^0.7.2", - "fs-extra": "4.0.2", - "magic-string": "^0.25.0", - "minimist": "^1.2.0", - "reflect-metadata": "^0.1.2", - "semver": "^6.3.0", - "source-map": "^0.6.1", - "sourcemap-codec": "^1.4.8", - "yargs": "15.3.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yargs": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.0.tgz", - "integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.0" - } - }, - "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "@angular/core": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.1.0.tgz", - "integrity": "sha512-RVlyegdIAij0P1wLY5ObIdsBAzvmHkHfElnmfiNKhaDftP6U/3zRtaKDu0bq0jvn1WCQ8zXxFQ8AWyKZwyFS+w==" - }, - "@angular/flex-layout": { - "version": "9.0.0-beta.29", - "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-9.0.0-beta.29.tgz", - "integrity": "sha512-93sxR+kYfYMOdnlWL0Q77FZ428gg8XnBu0YZm6GsCdkw/vLggIT/G1ZAqHlCPIODt6pxmCJ5KXh4ShvniIYDsA==" - }, - "@angular/forms": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.1.0.tgz", - "integrity": "sha512-5GC8HQlPChPV+168zLlm4yj4syA6N9ChSKV0tmzj1zIfMcub1UAOaB9IYaXRHQsjPFh9OuQXwmkzScyAfhEVjA==" - }, - "@angular/language-service": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.1.0.tgz", - "integrity": "sha512-2f8ECoXrj40oS1rtIfi+F8T4WPzundcZDs8WMFNBuWYbk14v1S9sTgMEmZyePHGkPjt6IfYiLJKJCvVgrt1nxQ==", - "dev": true - }, - "@angular/material": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.0.tgz", - "integrity": "sha512-KKzEIVh6/m56m+Ao8p4PK0SyEr0574l3VP2swj1qPag3u+FYgemmXCGTaChrKdDsez+zeTCPXImBGXzE6NQ80Q==" - }, - "@angular/platform-browser": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.0.tgz", - "integrity": "sha512-OsS/blUjl8ranmDaRADjFAmvnlmwbT6WNU7dVov7FhV0rqesbwaOJ5bR0LSYHYpej7Jaa6oYk0v0XWkaH9LTFg==" - }, - "@angular/platform-browser-dynamic": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.0.tgz", - "integrity": "sha512-sMtz/poQ3TYaWZzWjrn9apKUZ/WKql2MYCWbpax7pql3GgC9OoTslc7ZEe7/d3ynfFE/CQqWBBOuWGD71Z0LMQ==" - }, - "@angular/router": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.0.tgz", - "integrity": "sha512-cExO1nPnoPFiUJWZ28hTHozPLFoCmqr3xqcM57We0hhKE0esdrO+gRWKRH0EJERukLbU8coPKVhA8daGUpASiQ==" - }, - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", - "dev": true, - "requires": { - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.3.tgz", - "integrity": "sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ==", - "dev": true, - "requires": { - "@babel/types": "^7.9.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.7.0" - } - }, - "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", - "dev": true, - "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", - "dev": true - }, - "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", - "dev": true, - "requires": { - "lodash": "^4.17.13" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "dev": true, - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", - "dev": true, - "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" - } - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz", - "integrity": "sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz", - "integrity": "sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", - "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz", - "integrity": "sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/preset-env": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", - "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.0", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.7", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.0", - "browserslist": "^4.9.1", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - } - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/traverse": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", - "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true - }, - "@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.3.tgz", - "integrity": "sha512-TAdNkeGB5Fe4Og+ZkAr1Kvn9by2sfL44IAHFtxlh1BA1XJ5cLpO9iSNki5opWESv3l3vSHsZ9BNKuqFKbEbFaA==", - "dev": true, - "requires": { - "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.1", - "loader-utils": "^1.4.0", - "merge-source-map": "^1.1.0", - "schema-utils": "^2.6.4" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "@ngtools/webpack": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.0.tgz", - "integrity": "sha512-kQ+1N/F+5tuUXiiaoqJwhcOIM0I93EEvF3xwpTLRm91wl2i8R1261LvsD/uQPrgLrZNGR6eFhFF1Izn2PnIjQA==", - "dev": true, - "requires": { - "@angular-devkit/core": "9.1.0", - "enhanced-resolve": "4.1.1", - "rxjs": "6.5.4", - "webpack-sources": "1.4.3" - }, - "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - } - } - }, - "@npmcli/ci-detect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/ci-detect/-/ci-detect-1.2.0.tgz", - "integrity": "sha512-JtktVH7ASBVIWsQTFlFpeOzhBJskvoBCTfeeRhhZy7ybATcUvwiwotZ8j5rkqUUyB69lIy/AvboiiiGBjYBKBA==", - "dev": true - }, - "@npmcli/git": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.0.1.tgz", - "integrity": "sha512-hVatexiBtx71F01Ars38Hr5AFUGmJgHAfQtRlO5fJlnAawRGSXwEFgjB5i3XdUUmElZU/RXy7fefN02dZKxgPw==", - "dev": true, - "requires": { - "@npmcli/promise-spawn": "^1.1.0", - "mkdirp": "^1.0.3", - "npm-pick-manifest": "^6.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "unique-filename": "^1.1.1", - "which": "^2.0.2" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "@npmcli/installed-package-contents": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.5.tgz", - "integrity": "sha512-aKIwguaaqb6ViwSOFytniGvLPb9SMCUm39TgM3SfUo7n0TxUMbwoXfpwyvQ4blm10lzbAwTsvjr7QZ85LvTi4A==", - "dev": true, - "requires": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1", - "read-package-json-fast": "^1.1.1", - "readdir-scoped-modules": "^1.1.0" - } - }, - "@npmcli/promise-spawn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.1.0.tgz", - "integrity": "sha512-FwbuYN9KXBkloLeIR3xRgI8dyOdfK/KzaJlChszNuwmUXD1lHXfLlSeo4n4KrKt2udIK9K9/TzlnyCA3ubM2fA==", - "dev": true, - "requires": { - "infer-owner": "^1.0.4" - } - }, - "@schematics/angular": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.1.0.tgz", - "integrity": "sha512-qkehaITQ1S1udfnnBY5CXGWnk1iVFI8cZayjLUlRfD5w+6v9if3VIuqPssX96MqvkbjyRu1N214+ieaawzLmuA==", - "dev": true, - "requires": { - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0" - } - }, - "@schematics/update": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.901.0.tgz", - "integrity": "sha512-u2VESL1dgOSGZK/wcWEz0WcCU/yv764zhzCQerCwUtbV1CISSSDZ6x+prVYDXOdxWBGtDos2MbCF3GEJJI1T+w==", - "dev": true, - "requires": { - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0", - "@yarnpkg/lockfile": "1.1.0", - "ini": "1.3.5", - "npm-package-arg": "^8.0.0", - "pacote": "11.1.4", - "rxjs": "6.5.4", - "semver": "7.1.3", - "semver-intersect": "1.4.0" - }, - "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - } - } - }, - "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", - "dev": true - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, - "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/jasmine": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.10.tgz", - "integrity": "sha512-3F8qpwBAiVc5+HPJeXJpbrl+XjawGmciN5LgiO7Gv1pl1RHtjoMNqZpqEksaPJW05ViKe8snYInRs6xB25Xdew==", - "dev": true - }, - "@types/jasminewd2": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.8.tgz", - "integrity": "sha512-d9p31r7Nxk0ZH0U39PTH0hiDlJ+qNVGjlt1ucOoTUptxb2v+Y5VMnsxfwN+i3hK4yQnqBi3FMmoMFcd1JHDxdg==", - "dev": true, - "requires": { - "@types/jasmine": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "@types/node": { - "version": "12.12.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", - "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==", - "dev": true - }, - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", - "dev": true - }, - "@types/selenium-webdriver": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", - "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", - "dev": true - }, - "@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true - }, - "@types/webpack-sources": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.7.tgz", - "integrity": "sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", - "dev": true, - "requires": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", - "dev": true - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", - "dev": true, - "requires": { - "@webassemblyjs/wast-printer": "1.8.5" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", - "dev": true - }, - "@webassemblyjs/helper-module-context": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "mamacro": "^0.0.3" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/helper-wasm-section": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-opt": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "@webassemblyjs/wast-printer": "1.8.5" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/floating-point-hex-parser": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-code-frame": "1.8.5", - "@webassemblyjs/helper-fsm": "1.8.5", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", - "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==" - }, - "adm-zip": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", - "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==" - }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, - "agent-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-1.0.2.tgz", - "integrity": "sha1-aJDT+yFwBLYrcPiSjg+uX4lSpwY=" - }, - "agentkeepalive": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.0.tgz", - "integrity": "sha512-CW/n1wxF8RpEuuiq6Vbn9S8m0VSYDMnZESqaJ6F2cWN9fY8rei2qaxweIaRgq+ek8TqfoFIsUjaGNKGGEHElSg==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - } - }, - "aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "alter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz", - "integrity": "sha1-x1iICGF1cgNKrmJICvJrHU0cs80=", - "requires": { - "stable": "~0.1.3" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "angular-in-memory-web-api": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.10.0.tgz", - "integrity": "sha512-GzY4lHPR8suREYfgiYiJSBrgq0bdCiFw/LuqW5IsOQfRx98AvS69A5Ehz12oMLy9ABzoGpqmNC5fcFtClDAPqQ==" - }, - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - } - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "app-root-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", - "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", - "dev": true - }, - "append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", - "dev": true, - "requires": { - "default-require-extensions": "^2.0.0" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "archy": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/archy/-/archy-0.0.2.tgz", - "integrity": "sha1-kQ9Dv2YUH8M1VkWXq8GJ30Sz014=" - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "ast-traverse": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz", - "integrity": "sha1-ac8rg4bxnc2hux4F1o/jWdiJfeY=" - }, - "ast-types": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.3.tgz", - "integrity": "sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA==" - }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "autoprefixer": { - "version": "9.7.4", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz", - "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==", - "dev": true, - "requires": { - "browserslist": "^4.8.3", - "caniuse-lite": "^1.0.30001020", - "chalk": "^2.4.2", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.26", - "postcss-value-parser": "^4.0.2" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "autoprefixer-core": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/autoprefixer-core/-/autoprefixer-core-3.1.2.tgz", - "integrity": "sha1-reXOni2dcbt//DHWlvpeh66+tjQ=", - "requires": { - "caniuse-db": "^1.0.30000006", - "postcss": "~2.2.5" - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true - }, - "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7" - } - }, - "babel-loader": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", - "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", - "dev": true, - "requires": { - "find-cache-dir": "^2.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "pify": "^4.0.1" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "base62": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", - "integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ=" - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "requires": { - "callsite": "1.0.0" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - }, - "dependencies": { - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - } - } - }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "~2.0.0" - } - }, - "blocking-proxy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", - "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "breakable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz", - "integrity": "sha1-eEp5eRWjjq0nutRWtVcstLuqeME=" - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "browserslist": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz", - "integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001038", - "electron-to-chromium": "^1.3.390", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - } - }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - }, - "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - } - } - }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builder-autoprefixer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/builder-autoprefixer/-/builder-autoprefixer-1.0.4.tgz", - "integrity": "sha1-nNjDdqbXoXIAvYyjMbUfIwDhrHA=", - "requires": { - "autoprefixer-core": "^3.1.1" - } - }, - "builder-es6-module-to-cjs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/builder-es6-module-to-cjs/-/builder-es6-module-to-cjs-1.1.0.tgz", - "integrity": "sha1-efMpfRjEe7iLQ5R1OPoccnQnHuM=", - "requires": { - "es6-module-jstransform": "^0.1.2", - "is-module": "^1.0.0" - } - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true - }, - "bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" - }, - "cacache": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.0.tgz", - "integrity": "sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g==", - "dev": true, - "requires": { - "chownr": "^1.1.2", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^8.0.0", - "tar": "^6.0.1", - "unique-filename": "^1.1.1" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "tar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", - "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", - "dev": true, - "requires": { - "chownr": "^1.1.3", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-db": { - "version": "1.0.30001038", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001038.tgz", - "integrity": "sha512-yeQ2l99M9upOgMIRfZEdes6HuPbQiRZIMBumUwdXeEQz+faSXUZtZ8xeyEdU+TlJckH09M5NtM038sjKsRa2ow==" - }, - "caniuse-lite": { - "version": "1.0.30001038", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001038.tgz", - "integrity": "sha512-zii9quPo96XfOiRD4TrfYGs+QsGZpb2cGiMAzPjtf/hpFgB6zCPZgJb7I1+EATeMw/o+lG8FyRAnI+CWStHcaQ==", - "dev": true - }, - "canonical-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", - "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chanel": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/chanel/-/chanel-2.2.0.tgz", - "integrity": "sha1-JlKVsouSqoIAX4Zd0VpIlMQ42Tw=", - "requires": { - "co": "*", - "generator-supported": "*", - "to-descriptor": "*" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "chart.js": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", - "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", - "requires": { - "chartjs-color": "^2.1.0", - "moment": "^2.10.2" - } - }, - "chartjs-color": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", - "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", - "requires": { - "chartjs-color-string": "^0.6.0", - "color-convert": "^1.9.3" - } - }, - "chartjs-color-string": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", - "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", - "requires": { - "color-name": "^1.0.0" - } - }, - "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "circular-dependency-plugin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz", - "integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", - "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", - "dev": true - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "co": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", - "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=" - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dev": true, - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "codelyzer": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.2.tgz", - "integrity": "sha512-jB4FZ1Sx7kZhvZVdf+N2BaKTdrrNZOL0Bj10RRfrhHrb3zEvXjJvvq298JPMJAiyiCS/v4zs1QlGU0ip7xGqeA==", - "dev": true, - "requires": { - "app-root-path": "^2.2.1", - "aria-query": "^3.0.0", - "axobject-query": "2.0.2", - "css-selector-tokenizer": "^0.7.1", - "cssauron": "^1.4.0", - "damerau-levenshtein": "^1.0.4", - "semver-dsl": "^1.0.1", - "source-map": "^0.5.7", - "sprintf-js": "^1.1.2" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true - } - } - }, - "cogent": { - "version": "git+ssh://git@github.com/timaschew/cogent.git#2246bd071392f5053a3a110024fd608a40a593ba", - "from": "cogent@git://github.com/timaschew/cogent.git#fix-redirects", - "requires": { - "debug": "*", - "generator-supported": "~0.0.1", - "netrc": "~0.1.3", - "proxy-agent": "^1.1.0", - "raw-body": "^1.1.2", - "statuses": "^1.0.2", - "write-to": "^1.0.0" - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - }, - "dependencies": { - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - } - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "dev": true, - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "commoner": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz", - "integrity": "sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=", - "requires": { - "commander": "^2.5.0", - "detective": "^4.3.1", - "glob": "^5.0.15", - "graceful-fs": "^4.1.2", - "iconv-lite": "^0.4.5", - "mkdirp": "^0.5.0", - "private": "^0.1.6", - "q": "^1.1.2", - "recast": "^0.11.17" - }, - "dependencies": { - "ast-types": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", - "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=" - }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "recast": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", - "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", - "requires": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, - "component": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/component/-/component-1.1.0.tgz", - "integrity": "sha1-NgSaold5i37lcUKWHhjgdnkYyao=", - "requires": { - "co": "^3.0.0", - "commander": "^2.2.0", - "component-build": "^1.2.2", - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-ls": "^2.1.0", - "component-outdated2": "^1.0.4", - "component-pin": "^1.0.4", - "component-remotes": "^1.2.0", - "component-resolver": "^1.3.0", - "component-search2": "^1.1.1", - "component-updater": "^1.0.4", - "component-watcher": "^1.0.1", - "debug": "*", - "mkdirp": "~0.3.5", - "rimraf": "^2.2.6", - "semver": "^2.2.1", - "superagent": "~0.17.0", - "tiny-lr-fork": "0.0.5", - "win-fork": "~1.1.1" - } - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, - "component-build": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/component-build/-/component-build-1.2.2.tgz", - "integrity": "sha1-1bwl0lIE35On2aN6LeAE4RmkpXw=", - "requires": { - "builder-autoprefixer": "^1.0.3", - "builder-es6-module-to-cjs": "^1.1.0", - "component-builder": "^1.1.12", - "debug": "*" - } - }, - "component-builder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-builder/-/component-builder-1.2.1.tgz", - "integrity": "sha1-o5fBg3na9RH4Woy5TKDZIcHdsaQ=", - "requires": { - "requires": "^1.0.0", - "chanel": "^2.0.0", - "co": "^3.1.0", - "component-flatten": "^1.0.1", - "component-manifest": "^1.0.0", - "component-require2": "^1.0.1", - "cp": "^0.1.1", - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.3", - "mkdirp": "^0.3.5", - "syntax-error": "^1.1.1" - } - }, - "component-consoler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/component-consoler/-/component-consoler-2.0.0.tgz", - "integrity": "sha1-Peq/BGwjm2EGNi7JaJQGNruhTCM=", - "requires": { - "debug": "*" - } - }, - "component-downloader": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/component-downloader/-/component-downloader-1.2.0.tgz", - "integrity": "sha1-b/Z/9xyEaoWKrjGog1X5vxrUO20=", - "requires": { - "chanel": "^2.0.1", - "co": "^3", - "component-consoler": "^2.0.0", - "component-remotes": "^1.1.1", - "debug": "*", - "decompress": "~0.2.0", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.2", - "mkdirp": "~0.3.5", - "rimraf": "^2.2.6", - "semver": "^2.2.0", - "unglob": "~0.1.2", - "write-to": "^1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "component-flatten": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/component-flatten/-/component-flatten-1.0.1.tgz", - "integrity": "sha1-W0n0msRcuIxDAyuXozb1P4fNbIw=", - "requires": { - "semver": "^2.2.1" - } - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, - "component-ls": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/component-ls/-/component-ls-2.1.0.tgz", - "integrity": "sha1-wR7ez4mDbd6JBkrQT+JK3YClERI=", - "requires": { - "archy": "~0.0.2" - } - }, - "component-manifest": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-manifest/-/component-manifest-1.0.0.tgz", - "integrity": "sha1-SNy7HqPGiHYifmra/PNxJbC81XI=", - "requires": { - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.1", - "unglob": "~0.1.1" - } - }, - "component-outdated2": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/component-outdated2/-/component-outdated2-1.0.5.tgz", - "integrity": "sha1-IGbA9Cx1w18tS/3iv5/s4M5GPRQ=", - "requires": { - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.1.5", - "generator-supported": "~0.0.1", - "semver": "^2.2.1" - } - }, - "component-pin": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/component-pin/-/component-pin-1.0.5.tgz", - "integrity": "sha1-v6lOAepCB18ufU17X3i2Yt24NY0=", - "requires": { - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.1.5", - "generator-supported": "~0.0.1", - "semver": "^2.2.1" - } - }, - "component-remotes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/component-remotes/-/component-remotes-1.2.0.tgz", - "integrity": "sha1-PQeYpR5ziZxs5VnZN2Olb8eh2IM=", - "requires": { - "co": "^3", - "cogent": "git://github.com/timaschew/cogent.git#fix-redirects", - "component-consoler": "^2.0.0", - "component-validator": "^1.0.0", - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.2", - "semver": "^2.2.1" - } - }, - "component-require2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/component-require2/-/component-require2-1.1.1.tgz", - "integrity": "sha1-POrqGYc8HG74X1O48572koWvXMU=" - }, - "component-resolver": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-resolver/-/component-resolver-1.3.0.tgz", - "integrity": "sha1-79grBa9krRJ4W5h8AfCsQfAKaNw=", - "requires": { - "chanel": "^2.0.2", - "co": "^3", - "component-consoler": "^2.0.0", - "component-downloader": "^1.2.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.2.0", - "component-validator": "^1.1.1", - "debug": "*", - "generator-supported": "~0.0.1", - "graceful-fs": "^2.0.1", - "semver": "^2.2.1", - "unglob": "~0.1.2" - } - }, - "component-search2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/component-search2/-/component-search2-1.1.1.tgz", - "integrity": "sha1-kmeDygb2Hvdd7YW7ZVuq3i/J65E=", - "requires": { - "bytes": "~0.3.0", - "cogent": "~0.4.0", - "component-consoler": "^2.0.0", - "debug": "*", - "event-stream": "^3.1.1", - "generator-supported": "~0.0.1", - "JSONStream": "~0.8.0", - "stream-to-array": "^1.0.0" - }, - "dependencies": { - "bytes": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-0.3.0.tgz", - "integrity": "sha1-eOLg4ox/nHuYjqiu4NtNX6mUGTU=" - }, - "cogent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/cogent/-/cogent-0.4.3.tgz", - "integrity": "sha1-UAdE8R2D6cD8Q+9vlem5zHFaCFQ=", - "requires": { - "debug": "*", - "generator-supported": "~0.0.1", - "netrc": "~0.1.3", - "proxy-agent": "^1.1.0", - "raw-body": "^1.1.2", - "statuses": "^1.0.2", - "write-to": "^1.0.0" - } - } - } - }, - "component-updater": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/component-updater/-/component-updater-1.0.5.tgz", - "integrity": "sha1-CHd/ZxzodeE+MHwdghfC6l8aSsQ=", - "requires": { - "component-consoler": "^2.0.0", - "component-flatten": "^1.0.1", - "component-remotes": "^1.1.5", - "generator-supported": "~0.0.1", - "semver": "^2.2.1" - } - }, - "component-validator": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/component-validator/-/component-validator-1.1.1.tgz", - "integrity": "sha1-+lD+t/WL7IndaXktE6Mn4lJgjl4=", - "requires": { - "component-consoler": "^2.0.0" - } - }, - "component-watcher": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/component-watcher/-/component-watcher-1.0.3.tgz", - "integrity": "sha1-wl6AF0sLQFVJXX7xJnmGJm2QxTU=", - "requires": { - "debug": "*", - "sane": "0" - } - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "cookiejar": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-1.3.0.tgz", - "integrity": "sha1-3QCzVnkCHpnL1OhVua0EGRNHR2U=" - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", - "dev": true, - "requires": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", - "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", - "dev": true - }, - "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", - "dev": true, - "requires": { - "browserslist": "^4.8.3", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "cp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/cp/-/cp-0.1.1.tgz", - "integrity": "sha1-OUanbBpT/+DmhZPzQcEkszbB8G0=" - }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, - "css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "dev": true, - "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "css-parse": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", - "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", - "dev": true, - "requires": { - "css": "^2.0.0" - } - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", - "dev": true - }, - "css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" - } - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dev": true, - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", - "dev": true - }, - "cssauron": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", - "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", - "dev": true, - "requires": { - "through": "X.X.X" - } - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "dev": true, - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", - "dev": true - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", - "dev": true - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", - "dev": true - }, - "csso": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", - "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", - "dev": true, - "requires": { - "css-tree": "1.0.0-alpha.39" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.39", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", - "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", - "dev": true, - "requires": { - "mdn-data": "2.0.6", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", - "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, - "damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-uri-to-buffer": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz", - "integrity": "sha1-RuE6udqOMJdFyNAc5UchPr2y/j8=" - }, - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "decompress": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-0.2.5.tgz", - "integrity": "sha1-0hMjPv4GbM2A2RTXk/GzDNmEuEc=", - "requires": { - "adm-zip": "^0.4.3", - "ext-name": "^1.0.0", - "get-stdin": "^0.1.0", - "mkdirp": "^0.3.5", - "nopt": "^2.2.0", - "rimraf": "^2.2.2", - "stream-combiner": "^0.0.4", - "tar": "^0.1.18", - "tempfile": "^0.1.2" - } - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - } - }, - "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", - "dev": true, - "requires": { - "strip-bom": "^3.0.0" - } - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - } - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, - "defs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz", - "integrity": "sha1-siYJ8sehG6ej2xFoBcE5scr/qdI=", - "requires": { - "alter": "~0.2.0", - "ast-traverse": "~0.1.1", - "breakable": "~1.0.0", - "esprima-fb": "~15001.1001.0-dev-harmony-fb", - "simple-fmt": "~0.1.0", - "simple-is": "~0.2.0", - "stringmap": "~0.2.2", - "stringset": "~0.2.1", - "tryor": "~0.1.2", - "yargs": "~3.27.0" - }, - "dependencies": { - "esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=" - } - } - }, - "degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", - "requires": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - } - } - }, - "del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "dependency-graph": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", - "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", - "dev": true - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", - "dev": true - }, - "detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", - "requires": { - "acorn": "^5.2.1", - "defined": "^1.0.0" - }, - "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" - } - } - }, - "dethroy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dethroy/-/dethroy-1.0.2.tgz", - "integrity": "sha1-yCADMXaT3LzaP3UrAYLjvxbdRCE=" - }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, - "requires": { - "path-type": "^3.0.0" - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "dev": true, - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "^1.0.0" - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", - "dev": true - } - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.395", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.395.tgz", - "integrity": "sha512-kdn2cX6hZXDdz/O2Q8tZscITlsSv1a/7bOq/fQs7QAJ9iaRlnhZPccarNhxZv1tXgmgwCnKp/1lJNYLOG8Dxiw==", - "dev": true - }, - "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", - "dev": true, - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, - "emitter-component": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.0.0.tgz", - "integrity": "sha1-8E3Rj8PcPpp0y8DzELCIZm5MAW8=" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "dev": true, - "optional": true, - "requires": { - "iconv-lite": "~0.4.13" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "engine.io": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" - }, - "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - } - } - }, - "engine.io-client": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - } - } - }, - "engine.io-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", - "dev": true, - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" - } - }, - "enhanced-resolve": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", - "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - } - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", - "dev": true - }, - "err-code": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-module-jstransform": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/es6-module-jstransform/-/es6-module-jstransform-0.1.4.tgz", - "integrity": "sha1-OtHj6qt0UY9a8jwQFPGCjF6WfAI=", - "requires": { - "esprima-fb": "~3001.1.0-dev-harmony-fb", - "jstransform": "~3.0.0" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - } - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "esprima-fb": { - "version": "3001.1.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", - "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=" - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "event-stream": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz", - "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==", - "requires": { - "duplexer": "^0.1.1", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" - }, - "dependencies": { - "stream-combiner": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", - "requires": { - "duplexer": "~0.1.1", - "through": "~2.3.4" - } - } - } - }, - "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", - "dev": true - }, - "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", - "dev": true - }, - "eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", - "dev": true, - "requires": { - "original": "^1.0.0" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dev": true, - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - } - } - }, - "ext-list": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-0.2.0.tgz", - "integrity": "sha1-NhTV8pn0pZKolinn3oJfF3TRmr0=", - "requires": { - "got": "^0.2.0" - } - }, - "ext-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-1.0.1.tgz", - "integrity": "sha1-GCgzVtxAo5NFXFRGDwWZzpfTDgw=", - "requires": { - "ext-list": "^0.2.0", - "underscore.string": "~2.3.3" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, - "faye-websocket": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.4.4.tgz", - "integrity": "sha1-wUxbO/FNdBf/v9mQwKdJXNnzN7w=" - }, - "figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-loader": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", - "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", - "dev": true, - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^2.6.5" - } - }, - "file-uri-to-path": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-0.0.2.tgz", - "integrity": "sha1-N83RtbkFQEs/BeGyNkW+aU/3D4I=" - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", - "dev": true, - "requires": { - "debug": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz", - "integrity": "sha1-Kz9MQRy7X91pXESEPiojUUpDIxo=" - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs-extra": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", - "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - } - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, - "optional": true - }, - "fstream": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", - "integrity": "sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=", - "requires": { - "graceful-fs": "~3.0.2", - "inherits": "~2.0.0", - "mkdirp": "0.5", - "rimraf": "2" - }, - "dependencies": { - "graceful-fs": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", - "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", - "requires": { - "natives": "^1.1.3" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", - "requires": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "generator-supported": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/generator-supported/-/generator-supported-0.0.1.tgz", - "integrity": "sha1-kivSIBpsONj6y4FdWT2KA8iYwpk=" - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-stdin": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-0.1.0.tgz", - "integrity": "sha1-WZivJKr8gC0VyCxoVlfuuLENSpE=" - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-uri": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-0.1.4.tgz", - "integrity": "sha1-NfinlUwSn7Ey/y3fXtgaV8uKnlQ=", - "requires": { - "data-uri-to-buffer": "0", - "debug": "2", - "extend": "3", - "file-uri-to-path": "0", - "ftp": "~0.3.5", - "readable-stream": "2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "requires": { - "inherits": "2", - "minimatch": "0.3" - }, - "dependencies": { - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "got": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/got/-/got-0.2.0.tgz", - "integrity": "sha1-0Awkiyn9zK6pQN+coJlev/MbUaU=", - "requires": { - "object-assign": "^0.3.0" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", - "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", - "dev": true, - "requires": { - "lru-cache": "^5.1.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", - "dev": true - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", - "dev": true - }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", - "dev": true - }, - "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", - "dev": true - }, - "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-agent": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-0.2.7.tgz", - "integrity": "sha1-4X/aZfCQLZUs55IeYsf/iGJlWl4=", - "requires": { - "agent-base": "~1.0.1", - "debug": "2", - "extend": "3" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, - "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "https-proxy-agent": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-0.3.6.tgz", - "integrity": "sha1-cT+jjl01P1DrFKNC/r4pAz7RYZs=", - "requires": { - "agent-base": "~1.0.1", - "debug": "2", - "extend": "3" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - }, - "dependencies": { - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true - }, - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "dev": true, - "requires": { - "import-from": "^2.1.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dev": true, - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dev": true, - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-docker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "requires": { - "is-path-inside": "^2.1.0" - } - }, - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "requires": { - "path-is-inside": "^1.0.2" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "dev": true, - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "requires": { - "buffer-alloc": "^1.2.0" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.6.tgz", - "integrity": "sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA==", - "dev": true, - "requires": { - "async": "^2.6.2", - "compare-versions": "^3.4.0", - "fileset": "^2.0.3", - "istanbul-lib-coverage": "^2.0.5", - "istanbul-lib-hook": "^2.0.7", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^2.2.4", - "js-yaml": "^3.13.1", - "make-dir": "^2.1.0", - "minimatch": "^3.0.4", - "once": "^1.4.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "dev": true, - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", - "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", - "dev": true, - "requires": { - "append-transform": "^1.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", - "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", - "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0" - } - }, - "jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", - "dev": true, - "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "jasmine-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", - "dev": true - }, - "jasmine-spec-reporter": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", - "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", - "dev": true, - "requires": { - "colors": "1.1.2" - } - }, - "jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true - }, - "jest-worker": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz", - "integrity": "sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-base64": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", - "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - } - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.2.0.tgz", - "integrity": "sha512-2tLgY7LRNZ9Hd6gmCuBG5/OjRHQpSgJQqJoYyLLOhUgn8LdOYrjaZLcxkWnDads+AD/haWWioPNziXQcgvQJ/g==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, - "json5": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", - "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true, - "optional": true - } - } - }, - "jsonparse": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", - "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=" - }, - "JSONStream": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", - "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", - "requires": { - "jsonparse": "0.0.5", - "through": ">=2.2.7 <3" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "jstransform": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz", - "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=", - "requires": { - "base62": "0.1.1", - "esprima-fb": "~3001.1.0-dev-harmony-fb", - "source-map": "0.1.31" - }, - "dependencies": { - "source-map": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", - "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=", - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "jszip": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.3.0.tgz", - "integrity": "sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw==", - "dev": true, - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, - "karma": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.3.0.tgz", - "integrity": "sha512-NSPViHOt+RW38oJklvYxQC4BSQsv737oQlr/r06pCM+slDOr4myuI1ivkRmp+3dVpJDfZt2DmaPJ2wkx+ZZuMQ==", - "dev": true, - "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", - "core-js": "^3.1.3", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "2.1.1", - "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "karma-chrome-launcher": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", - "dev": true, - "requires": { - "which": "^1.2.1" - } - }, - "karma-coverage-istanbul-reporter": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz", - "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==", - "dev": true, - "requires": { - "istanbul-api": "^2.1.6", - "minimatch": "^3.0.4" - }, - "dependencies": { - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "karma-jasmine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", - "dev": true, - "requires": { - "jasmine-core": "^3.3" - } - }, - "karma-jasmine-html-reporter": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.3.tgz", - "integrity": "sha512-ci0VrjuCaFj+9d1tYlTE3KIPUCp0rz874zWWU3JgCMqGIyw5ke+BXWFPOAGAqUdCJcrMwneyvp1zFXA74MiPUA==", - "dev": true - }, - "karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", - "dev": true, - "requires": { - "source-map-support": "^0.5.5" - } - }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "less": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz", - "integrity": "sha512-tlWX341RECuTOvoDIvtFqXsKj072hm3+9ymRBe76/mD6O5ZZecnlAOVDlWAleF2+aohFrxNidXhv2773f6kY7g==", - "dev": true, - "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0", - "tslib": "^1.10.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true, - "optional": true - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "less-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", - "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "loader-utils": "^1.1.0", - "pify": "^4.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, - "requires": { - "leven": "^3.1.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "license-webpack-plugin": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.4.tgz", - "integrity": "sha512-1Xq72fmPbTg5KofXs+yI5L4QqPFjQ6mZxoeI6D7gfiEDOtaEIk6PGrdLaej90bpDqKNHNxlQ/MW4tMAL6xMPJQ==", - "dev": true, - "requires": { - "@types/webpack-sources": "^0.1.5", - "webpack-sources": "^1.2.0" - } - }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "requires": { - "immediate": "~3.0.5" - } - }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", - "dev": true, - "requires": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" - } - }, - "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", - "dev": true - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "make-fetch-happen": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.4.tgz", - "integrity": "sha512-hIFoqGq1db0QMiy/Atr/pI1Rs4rDV+ZdGSey2SQyF3KK3u1z4aj9mS5UdNnZkdQpA+H3pGn0J3KlEwsi2x4EqA==", - "dev": true, - "requires": { - "agentkeepalive": "^4.1.0", - "cacache": "^15.0.0", - "http-cache-semantics": "^4.0.4", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.1.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^5.0.0", - "ssri": "^8.0.0" - }, - "dependencies": { - "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "smart-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", - "dev": true - }, - "socks": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "dev": true, - "requires": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", - "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4", - "socks": "^2.3.3" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "requires": { - "tmpl": "1.0.x" - } - }, - "mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", - "dev": true - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "methods": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz", - "integrity": "sha1-J3yQ+L7zlwlkWoNxxRw7bGSOBow=" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - } - }, - "mime": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.5.tgz", - "integrity": "sha1-nu0HMCKov14WyFZsaGe4gyv7+hM=" - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, - "requires": { - "mime-db": "1.43.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mini-css-extract-plugin": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", - "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-fetch": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.2.1.tgz", - "integrity": "sha512-ssHt0dkljEDaKmTgQ04DQgx2ag6G2gMPxA5hpcsoeTbfDgRf2fC2gNSRc6kISjD7ckCpHwwQvXxuTBK8402fXg==", - "dev": true, - "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-pipeline": "^1.2.2", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "requires": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - }, - "dependencies": { - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - } - } - }, - "minipass-pipeline": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", - "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" - }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "natives": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", - "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==" - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=" - }, - "netrc": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz", - "integrity": "sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ=" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", - "dev": true - }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - } - } - }, - "node-releases": { - "version": "1.1.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz", - "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==", - "dev": true - }, - "nopt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", - "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", - "requires": { - "abbrev": "1" - } - }, - "noptify": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/noptify/-/noptify-0.0.3.tgz", - "integrity": "sha1-WPZUpz2XU98MUdlobckhBKZ/S7s=", - "requires": { - "nopt": "~2.0.0" - }, - "dependencies": { - "nopt": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.0.0.tgz", - "integrity": "sha1-ynQW8gpeP5w7hhgPlilfo9C1Lg0=", - "requires": { - "abbrev": "1" - } - } - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "dev": true - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-install-checks": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", - "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", - "dev": true, - "requires": { - "semver": "^7.1.1" - }, - "dependencies": { - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - } - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true - }, - "npm-package-arg": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.0.1.tgz", - "integrity": "sha512-/h5Fm6a/exByzFSTm7jAyHbgOqErl9qSNJDQF32Si/ZzgwT2TERVxRxn3Jurw1wflgyVVAxnFR4fRHPM7y1ClQ==", - "dev": true, - "requires": { - "hosted-git-info": "^3.0.2", - "semver": "^7.0.0", - "validate-npm-package-name": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - } - } - }, - "npm-packlist": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.1.1.tgz", - "integrity": "sha512-95TSDvGwujIhqfSpIiRRLodEF+y6mJMopuZdahoGzqtRDFZXGav46S0p6ngeWaiAkb5R72w6eVARhzej0HvZeQ==", - "dev": true, - "requires": { - "glob": "^7.1.6", - "ignore-walk": "^3.0.3", - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "npm-pick-manifest": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.0.0.tgz", - "integrity": "sha512-PdJpXMvjqt4nftNEDpCgjBUF8yI3Q3MyuAmVB9nemnnCg32F4BPL/JFBfdj8DubgHCYUFQhtLWmBPvdsFtjWMg==", - "dev": true, - "requires": { - "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.0.0", - "semver": "^7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - } - } - }, - "npm-registry-fetch": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-8.0.0.tgz", - "integrity": "sha512-975WwLvZjX97y9UWWQ8nAyr7bw02s9xKPHqvEm5T900LQsB1HXb8Gb9ebYtCBLSX+K8gSOrO5KS/9yV/naLZmQ==", - "dev": true, - "requires": { - "@npmcli/ci-detect": "^1.0.0", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^8.0.2", - "minipass": "^3.0.0", - "minipass-fetch": "^1.1.2", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.0.0", - "npm-package-arg": "^8.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-0.3.1.tgz", - "integrity": "sha1-Bg4qKifXwNd+x3t48Rqkf9iACNI=" - }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz", - "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==", - "dev": true, - "requires": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - } - }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dev": true, - "requires": { - "is-wsl": "^1.1.0" - }, - "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - } - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "ora": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.3.tgz", - "integrity": "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.2.0", - "is-interactive": "^1.0.0", - "log-symbols": "^3.0.0", - "mute-stream": "0.0.8", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "requires": { - "url-parse": "^1.4.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dev": true, - "requires": { - "retry": "^0.12.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pac-proxy-agent": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-0.2.0.tgz", - "integrity": "sha1-rZApCdkvT+fMLl9Z9b9QYbz6cbI=", - "requires": { - "agent-base": "~1.0.1", - "debug": "2", - "extend": "~1.2.1", - "get-uri": "~0.1.0", - "pac-resolver": "~1.2.1", - "proxy-agent": "1", - "stream-to-array": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", - "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "pac-resolver": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-1.2.6.tgz", - "integrity": "sha1-7QOvDFtZM1Bb3T8H91F1Rm1efPs=", - "requires": { - "co": "~3.0.6", - "degenerator": "~1.0.0", - "netmask": "~1.0.4", - "regenerator": "~0.8.13", - "thunkify": "~2.1.1" - }, - "dependencies": { - "co": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", - "integrity": "sha1-FEXyJsXrlWE45oyawwFn6n0ua9o=" - } - } - }, - "pacote": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.1.4.tgz", - "integrity": "sha512-eUGJvSSpWFZKn3z8gig/HgnBmUl6gIWByIIaHzSyEr3tOWX0w8tFEADXtpu8HGv5E0ShCeTP6enRq8iHKCHSvw==", - "dev": true, - "requires": { - "@npmcli/git": "^2.0.1", - "@npmcli/installed-package-contents": "^1.0.5", - "@npmcli/promise-spawn": "^1.1.0", - "cacache": "^15.0.0", - "chownr": "^1.1.4", - "fs-minipass": "^2.1.0", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.1", - "minipass-fetch": "^1.2.1", - "mkdirp": "^1.0.3", - "npm-package-arg": "^8.0.1", - "npm-packlist": "^2.1.0", - "npm-pick-manifest": "^6.0.0", - "npm-registry-fetch": "^8.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "read-package-json-fast": "^1.1.3", - "rimraf": "^2.7.1", - "semver": "^7.1.3", - "ssri": "^8.0.0", - "tar": "^6.0.1", - "which": "^2.0.2" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - }, - "tar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", - "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", - "dev": true, - "requires": { - "chownr": "^1.1.3", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", - "dev": true, - "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "optional": true - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "requires": { - "through": "~2.3" - } - }, - "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - } - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", - "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-2.2.6.tgz", - "integrity": "sha1-wENE4kSeRYa5Vfvkp093CA2EVx8=", - "requires": { - "js-base64": "~2.1.5", - "source-map": "~0.1.40" - } - }, - "postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", - "dev": true, - "requires": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-import": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", - "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", - "dev": true, - "requires": { - "postcss": "^7.0.1", - "postcss-value-parser": "^3.2.3", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - } - }, - "postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "dev": true, - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "dev": true, - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "dev": true, - "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "dev": true, - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "dev": true, - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "dev": true, - "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "dev": true, - "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-value-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", - "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "optional": true, - "requires": { - "asap": "~2.0.3" - } - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "promise-retry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", - "dev": true, - "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "dependencies": { - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true - } - } - }, - "protractor": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.3.tgz", - "integrity": "sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA==", - "dev": true, - "requires": { - "@types/q": "^0.0.32", - "@types/selenium-webdriver": "^3.0.0", - "blocking-proxy": "^1.0.0", - "browserstack": "^1.5.1", - "chalk": "^1.1.3", - "glob": "^7.0.3", - "jasmine": "2.8.0", - "jasminewd2": "^2.1.0", - "optimist": "~0.6.0", - "q": "1.4.1", - "saucelabs": "^1.5.0", - "selenium-webdriver": "3.6.0", - "source-map-support": "~0.4.0", - "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.0.6" - }, - "dependencies": { - "@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "webdriver-manager": { - "version": "12.1.7", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.7.tgz", - "integrity": "sha512-XINj6b8CYuUYC93SG3xPkxlyUc3IJbD6Vvo75CVGuG9uzsefDzWQrhz0Lq8vbPxtb4d63CZdYophF8k8Or/YiA==", - "dev": true, - "requires": { - "adm-zip": "^0.4.9", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "^1.2.0", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" - } - } - } - }, - "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dev": true, - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - } - }, - "proxy-agent": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-1.1.1.tgz", - "integrity": "sha1-/LHu9eWJZcmV+TjwKdcp/IGFi5U=", - "requires": { - "http-proxy-agent": "0", - "https-proxy-agent": "0", - "lru-cache": "~2.5.0", - "pac-proxy-agent": "0", - "socks-proxy-agent": "1" - }, - "dependencies": { - "lru-cache": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz", - "integrity": "sha1-H92tk4quEmPOE4aAvhs/WRwKtBw=" - } - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true - }, - "qs": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.5.tgz", - "integrity": "sha1-KUsmjksNQlD23eGbO4s0k13/FO8=" - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - } - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.4.tgz", - "integrity": "sha1-zMfd/Ea3KGHN1btDPIQLcLbyf1Q=", - "requires": { - "bytes": "1.0.0", - "iconv-lite": "0.4.8" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.8.tgz", - "integrity": "sha1-xgGadZXyzvynAuq2lKAQvNkpjSA=" - } - } - }, - "raw-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.0.tgz", - "integrity": "sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==", - "dev": true, - "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.5.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", - "dev": true, - "requires": { - "pify": "^2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "read-package-json": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", - "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", - "dev": true, - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "read-package-json-fast": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-1.1.3.tgz", - "integrity": "sha512-MmFqiyfCXV2Dmm4jH24DEGhxdkUDFivJQj4oPZQPOKywxR7HWBE6WnMWDAapfFHi3wm1b+mhR+XHlUH0CL8axg==", - "dev": true, - "requires": { - "json-parse-even-better-errors": "^2.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "read-package-tree": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", - "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", - "dev": true, - "requires": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - } - } - }, - "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", - "dev": true, - "requires": { - "picomatch": "^2.0.7" - } - }, - "recast": { - "version": "0.10.33", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz", - "integrity": "sha1-lCgI96oBbx+nFCxGHX5XBKqo1pc=", - "requires": { - "ast-types": "0.8.12", - "esprima-fb": "~15001.1001.0-dev-harmony-fb", - "private": "~0.1.5", - "source-map": "~0.5.0" - }, - "dependencies": { - "ast-types": { - "version": "0.8.12", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz", - "integrity": "sha1-oNkOQ1G7iHcWyD/WN+v4GK9K38w=" - }, - "esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "reduce-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz", - "integrity": "sha1-4Mk1QsV0UhvqE98PlIjtgqt3xdo=" - }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dev": true, - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator": { - "version": "0.8.46", - "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.46.tgz", - "integrity": "sha1-FUwydoY2HtUsrWmyVF78U6PQdpY=", - "requires": { - "commoner": "~0.10.3", - "defs": "~1.1.0", - "esprima-fb": "~15001.1001.0-dev-harmony-fb", - "private": "~0.1.5", - "recast": "0.10.33", - "regenerator-runtime": "~0.9.5", - "through": "~2.3.8" - }, - "dependencies": { - "esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=" - } - } - }, - "regenerator-runtime": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz", - "integrity": "sha1-0z65XQ0gAaS+OWWXB8UbDLcc4Ck=" - }, - "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", - "dev": true - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, - "rfdc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", - "dev": true - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", - "dev": true - }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", - "dev": true - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "requires": { - "align-text": "^0.1.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "rollup": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.1.0.tgz", - "integrity": "sha512-gfE1455AEazVVTJoeQtcOq/U6GSxwoj4XPSWVsuWmgIxj7sBQNLDOSA82PbdMe+cP8ql8fR1jogPFe8Wg8g4SQ==", - "dev": true, - "requires": { - "fsevents": "~2.1.2" - } - }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "^1.1.1" - } - }, - "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sane": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/sane/-/sane-0.8.1.tgz", - "integrity": "sha1-JDpLIECBvlWTwKIrC7sRVzUS/Mc=", - "requires": { - "minimatch": "~0.2.14", - "walker": "~1.0.5", - "watch": "~0.10.0" - } - }, - "sass": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.3.tgz", - "integrity": "sha512-5NMHI1+YFYw4sN3yfKjpLuV9B5l7MqQ6FlkTcC4FT+oHbBRUZoSjHrrt/mE0nFXJyY2kQtU9ou9HxvFVjLFuuw==", - "dev": true, - "requires": { - "chokidar": ">=2.0.0 <4.0.0" - } - }, - "sass-loader": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", - "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "loader-utils": "^1.2.3", - "neo-async": "^2.6.1", - "schema-utils": "^2.6.1", - "semver": "^6.3.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "saucelabs": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", - "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - }, - "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", - "dev": true, - "requires": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", - "dev": true, - "requires": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "dependencies": { - "tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - } - } - }, - "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", - "dev": true, - "requires": { - "node-forge": "0.9.0" - } - }, - "semver": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-2.3.2.tgz", - "integrity": "sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI=" - }, - "semver-dsl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", - "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", - "dev": true, - "requires": { - "semver": "^5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "semver-intersect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", - "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==", - "dev": true, - "requires": { - "semver": "^5.0.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "simple-fmt": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz", - "integrity": "sha1-GRv1ZqWeZTBILLJatTtKjchcOms=" - }, - "simple-is": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz", - "integrity": "sha1-Krt1qt453rXMgVzhDmGRFkhQuvA=" - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - } - } - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "smart-buffer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "socket.io": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", - "dev": true, - "requires": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", - "dev": true - }, - "socket.io-client": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", - "to-array": "0.1.4" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "socket.io-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", - "dev": true, - "requires": { - "faye-websocket": "^0.10.0", - "uuid": "^3.0.1" - }, - "dependencies": { - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "sockjs-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", - "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", - "dev": true, - "requires": { - "debug": "^3.2.5", - "eventsource": "^1.0.7", - "faye-websocket": "~0.11.1", - "inherits": "^2.0.3", - "json3": "^3.3.2", - "url-parse": "^1.4.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - } - } - }, - "socks": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", - "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", - "requires": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" - } - }, - "socks-proxy-agent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-1.0.2.tgz", - "integrity": "sha1-Z+BrRH/lY3QX/eVzPL/f7J/+EX8=", - "requires": { - "agent-base": "~1.0.1", - "extend": "~1.2.1", - "socks": "~1.1.5" - }, - "dependencies": { - "extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", - "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=" - } - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "requires": { - "amdefine": ">=0.0.4" - } - }, - "source-map-loader": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", - "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", - "dev": true, - "requires": { - "async": "^2.5.0", - "loader-utils": "^1.1.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "spdy": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", - "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - } - } - }, - "speed-measure-webpack-plugin": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz", - "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "requires": { - "duplexer": "~0.1.1" - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "stream-to-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-1.0.0.tgz", - "integrity": "sha1-lBZrsp8+ok8ILS+M0+uyzA1uyiw=" - }, - "streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", - "dev": true, - "requires": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - } - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string.prototype.trimend": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", - "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", - "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "stringmap": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz", - "integrity": "sha1-VWwTeyWPlCuHdvWy71gqoGnX0bE=" - }, - "stringset": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz", - "integrity": "sha1-7yWcTjSTRDd/zRyRPdLoSMnAQrU=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "style-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", - "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", - "dev": true, - "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.6.4" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "stylus": { - "version": "0.54.7", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.7.tgz", - "integrity": "sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug==", - "dev": true, - "requires": { - "css-parse": "~2.0.0", - "debug": "~3.1.0", - "glob": "^7.1.3", - "mkdirp": "~0.5.x", - "safer-buffer": "^2.1.2", - "sax": "~1.2.4", - "semver": "^6.0.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "stylus-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", - "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", - "dev": true, - "requires": { - "loader-utils": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "when": "~3.6.x" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "superagent": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-0.17.0.tgz", - "integrity": "sha1-qtzVD75ak+cZkRGNeb8HFNYlu6g=", - "requires": { - "cookiejar": "1.3.0", - "debug": "~0.7.2", - "emitter-component": "1.0.0", - "extend": "~1.2.1", - "formidable": "1.0.14", - "methods": "0.0.1", - "mime": "1.2.5", - "qs": "0.6.5", - "reduce-component": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" - }, - "extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", - "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=" - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "requires": { - "acorn-node": "^1.2.0" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "tar": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/tar/-/tar-0.1.20.tgz", - "integrity": "sha1-QpQLrltfIsdEg2mRJvnz8nRJyxM=", - "requires": { - "block-stream": "*", - "fstream": "~0.1.28", - "inherits": "2" - } - }, - "tempfile": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-0.1.3.tgz", - "integrity": "sha1-fWtxAEcznTn4RzJ6BW2t8YMQMBA=", - "requires": { - "uuid": "~1.4.0" - } - }, - "terser": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", - "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz", - "integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==", - "dev": true, - "requires": { - "cacache": "^13.0.1", - "find-cache-dir": "^3.2.0", - "jest-worker": "^25.1.0", - "p-limit": "^2.2.2", - "schema-utils": "^2.6.4", - "serialize-javascript": "^2.1.2", - "source-map": "^0.6.1", - "terser": "^4.4.3", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", - "dev": true, - "requires": { - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^7.0.0", - "unique-filename": "^1.1.1" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "minipass": "^3.1.1" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=" - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "timers-browserify": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", - "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", - "dev": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", - "dev": true - }, - "tiny-lr-fork": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/tiny-lr-fork/-/tiny-lr-fork-0.0.5.tgz", - "integrity": "sha1-Hpnh4qhGm3NquX2X7vqYxx927Qo=", - "requires": { - "debug": "~0.7.0", - "faye-websocket": "~0.4.3", - "noptify": "~0.0.3", - "qs": "~0.5.2" - }, - "dependencies": { - "debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" - }, - "qs": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz", - "integrity": "sha1-MbGtBYVnZRxSaSFQa5qHk5EaA4Q=" - } - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-descriptor/-/to-descriptor-1.0.1.tgz", - "integrity": "sha1-oOZ4w068fS2uRk2DcrwhR52cK80=" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, - "tryor": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz", - "integrity": "sha1-gUXkynyv9ArN48z5Rui4u3W0Fys=" - }, - "ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" - } - }, - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" - }, - "tslint": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", - "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" - }, - "dependencies": { - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", - "dev": true - }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=" - }, - "unglob": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unglob/-/unglob-0.1.2.tgz", - "integrity": "sha1-8uwGKE5JGhx3YRp2CouOowAnLDg=", - "requires": { - "generator-supported": "~0.0.1", - "glob": "~3.2.8", - "minimatch": "~0.2.14" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "universal-analytics": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", - "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", - "dev": true, - "requires": { - "debug": "^3.0.0", - "request": "^2.88.0", - "uuid": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "util-promisify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", - "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-1.4.2.tgz", - "integrity": "sha1-RTAZ9oaWam34PNxSROfJkOzDMvw=" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, - "requires": { - "builtins": "^1.0.3" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "requires": { - "makeerror": "1.0.x" - } - }, - "watch": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz", - "integrity": "sha1-d3mLLaD5kQ1ZXxrOWwwiWFIfIdw=" - }, - "watchpack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", - "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", - "dev": true, - "requires": { - "chokidar": "^2.1.8", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "bundled": true, - "extraneous": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "bundled": true, - "extraneous": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "bundled": true, - "extraneous": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "bundled": true, - "extraneous": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "bundled": true, - "extraneous": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "bundled": true, - "extraneous": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "bundled": true, - "extraneous": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "bundled": true, - "extraneous": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "bundled": true, - "extraneous": true - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "bundled": true, - "extraneous": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "bundled": true, - "extraneous": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "bundled": true, - "extraneous": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "bundled": true, - "extraneous": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "bundled": true, - "extraneous": true - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "bundled": true, - "extraneous": true - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "bundled": true, - "extraneous": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "bundled": true, - "extraneous": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "bundled": true, - "extraneous": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "bundled": true, - "extraneous": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "bundled": true, - "extraneous": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "bundled": true, - "extraneous": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "bundled": true, - "extraneous": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "bundled": true, - "extraneous": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "bundled": true, - "extraneous": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "bundled": true, - "extraneous": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "bundled": true, - "extraneous": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "bundled": true, - "extraneous": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "bundled": true, - "extraneous": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "bundled": true, - "extraneous": true - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "bundled": true, - "extraneous": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "bundled": true, - "extraneous": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "bundled": true, - "extraneous": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "bundled": true, - "extraneous": true - }, - "needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", - "bundled": true, - "extraneous": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "bundled": true, - "extraneous": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "bundled": true, - "extraneous": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "bundled": true, - "extraneous": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "bundled": true, - "extraneous": true - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "bundled": true, - "extraneous": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "bundled": true, - "extraneous": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "bundled": true, - "extraneous": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "bundled": true, - "extraneous": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "bundled": true, - "extraneous": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "bundled": true, - "extraneous": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "bundled": true, - "extraneous": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "bundled": true, - "extraneous": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "bundled": true, - "extraneous": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "bundled": true, - "extraneous": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "bundled": true, - "extraneous": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "bundled": true, - "extraneous": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "bundled": true, - "extraneous": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "bundled": true, - "extraneous": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "bundled": true, - "extraneous": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "bundled": true, - "extraneous": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bundled": true, - "extraneous": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "bundled": true, - "extraneous": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "bundled": true, - "extraneous": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "bundled": true, - "extraneous": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "bundled": true, - "extraneous": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "bundled": true, - "extraneous": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "bundled": true, - "extraneous": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "bundled": true, - "extraneous": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "bundled": true, - "extraneous": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "bundled": true, - "extraneous": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "bundled": true, - "extraneous": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "bundled": true, - "extraneous": true - } - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "webdriver-js-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", - "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", - "dev": true, - "requires": { - "@types/selenium-webdriver": "^3.0.0", - "selenium-webdriver": "^3.0.1" - } - }, - "webpack": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz", - "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/wasm-edit": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.1", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.0", - "webpack-sources": "^1.4.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", - "dev": true, - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "webpack-dev-middleware": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", - "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", - "dev": true, - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "webpack-dev-server": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", - "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.2.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.6", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.25", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.7", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "0.3.19", - "sockjs-client": "1.4.0", - "spdy": "^4.0.1", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "12.0.5" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "bundled": true, - "extraneous": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "bundled": true, - "extraneous": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "bundled": true, - "extraneous": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "bundled": true, - "extraneous": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "bundled": true, - "extraneous": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "bundled": true, - "extraneous": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "bundled": true, - "extraneous": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "bundled": true, - "extraneous": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "bundled": true, - "extraneous": true - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "bundled": true, - "extraneous": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "bundled": true, - "extraneous": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "bundled": true, - "extraneous": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "bundled": true, - "extraneous": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "bundled": true, - "extraneous": true - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "bundled": true, - "extraneous": true - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "bundled": true, - "extraneous": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "bundled": true, - "extraneous": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "bundled": true, - "extraneous": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "bundled": true, - "extraneous": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "bundled": true, - "extraneous": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "bundled": true, - "extraneous": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "bundled": true, - "extraneous": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "bundled": true, - "extraneous": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "bundled": true, - "extraneous": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "bundled": true, - "extraneous": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "bundled": true, - "extraneous": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "bundled": true, - "extraneous": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "bundled": true, - "extraneous": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "bundled": true, - "extraneous": true - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "bundled": true, - "extraneous": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "bundled": true, - "extraneous": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "bundled": true, - "extraneous": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "bundled": true, - "extraneous": true - }, - "needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", - "bundled": true, - "extraneous": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "bundled": true, - "extraneous": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "bundled": true, - "extraneous": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "bundled": true, - "extraneous": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "bundled": true, - "extraneous": true - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "bundled": true, - "extraneous": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "bundled": true, - "extraneous": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "bundled": true, - "extraneous": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "bundled": true, - "extraneous": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "bundled": true, - "extraneous": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "bundled": true, - "extraneous": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "bundled": true, - "extraneous": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "bundled": true, - "extraneous": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "bundled": true, - "extraneous": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "bundled": true, - "extraneous": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "bundled": true, - "extraneous": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "bundled": true, - "extraneous": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "bundled": true, - "extraneous": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "bundled": true, - "extraneous": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "bundled": true, - "extraneous": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "bundled": true, - "extraneous": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bundled": true, - "extraneous": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "bundled": true, - "extraneous": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "bundled": true, - "extraneous": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "bundled": true, - "extraneous": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "bundled": true, - "extraneous": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "bundled": true, - "extraneous": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "bundled": true, - "extraneous": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "bundled": true, - "extraneous": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "bundled": true, - "extraneous": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "bundled": true, - "extraneous": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "bundled": true, - "extraneous": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "bundled": true, - "extraneous": true - } - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - } - } - }, - "webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "webpack-subresource-integrity": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.4.0.tgz", - "integrity": "sha512-GB1kB/LwAWC3CxwcedGhMkxGpNZxSheCe1q+KJP1bakuieAdX/rGHEcf5zsEzhKXpqsGqokgsDoD9dIkr61VDQ==", - "dev": true, - "requires": { - "webpack-sources": "^1.3.0" - } - }, - "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", - "dev": true, - "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", - "dev": true - }, - "when": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", - "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "win-fork": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/win-fork/-/win-fork-1.1.1.tgz", - "integrity": "sha1-j1jgZW/KAK3IyGoriePNLWotXl4=" - }, - "window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "requires": { - "errno": "~0.1.7" - } - }, - "worker-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-4.0.2.tgz", - "integrity": "sha512-V+1zSZMOOKk+uBzKyNIODLQLsx59zSIOaI75J1EMS0iR1qy+KQR3y/pQ3T0vIhvPfDFapGRMsoMvQNEL3okqSA==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-to": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/write-to/-/write-to-1.1.1.tgz", - "integrity": "sha1-Xu+/aC0SjCqwwF9l3xA4c54GxqM=", - "requires": { - "dethroy": "1", - "mkdirp": "0" - } - }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true - }, - "xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz", - "integrity": "sha1-ISBUaTFuk5Ex1Z8toMbX+YIh6kA=", - "requires": { - "camelcase": "^1.2.1", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "os-locale": "^1.4.0", - "window-size": "^0.1.2", - "y18n": "^3.2.0" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "zone.js": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz", - "integrity": "sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg==" - } - } -} diff --git a/next-gen-gui/package.json b/next-gen-gui/package.json deleted file mode 100644 index e9803c3ca..000000000 --- a/next-gen-gui/package.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "next-gen-gui", - "version": "1.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build", - "test": "ng test", - "lint": "ng lint", - "e2e": "ng e2e" - }, - "private": true, - "dependencies": { - "@angular/animations": "^9.1.0", - "@angular/cdk": "^9.2.0", - "@angular/common": "^9.1.0", - "@angular/compiler": "^9.1.0", - "@angular/core": "^9.1.0", - "@angular/flex-layout": "^9.0.0-beta.29", - "@angular/forms": "^9.1.0", - "@angular/material": "^9.2.0", - "@angular/platform-browser": "^9.1.0", - "@angular/platform-browser-dynamic": "^9.1.0", - "@angular/router": "^9.1.0", - "angular-in-memory-web-api": "^0.10.0", - "chart.js": "^2.9.3", - "component": "^1.1.0", - "rxjs": "^6.5.5", - "tslib": "^1.10.0", - "zone.js": "^0.10.3" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^0.901.0", - "@angular/cli": "^9.1.0", - "@angular/compiler-cli": "^9.1.0", - "@angular/language-service": "^9.1.0", - "@types/jasmine": "^3.5.10", - "@types/jasminewd2": "~2.0.3", - "@types/node": "^12.12.34", - "codelyzer": "^5.2.2", - "jasmine-core": "~3.5.0", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.3.0", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage-istanbul-reporter": "~2.1.0", - "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "^1.5.3", - "protractor": "~5.4.3", - "ts-node": "~8.3.0", - "tslint": "~5.18.0", - "typescript": "~3.7.5" - } -} diff --git a/next-gen-gui/screenshot.png b/next-gen-gui/screenshot.png deleted file mode 100644 index 955ccd4c73f33ecf910e86275c4fe062b071d8fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 752159 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 - - diff --git a/next-gen-gui/src/app/app.component.scss b/next-gen-gui/src/app/app.component.scss deleted file mode 100644 index f24418fbd..000000000 --- a/next-gen-gui/src/app/app.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -.header { - margin: 15px 3vw 12px 3vw; - font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; -} - -table { - width: 100%; -} - -.mat-form-field { - font-size: 14px; - width: 100%; -} \ No newline at end of file diff --git a/next-gen-gui/src/app/app.component.spec.ts b/next-gen-gui/src/app/app.component.spec.ts deleted file mode 100644 index 18d12318b..000000000 --- a/next-gen-gui/src/app/app.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - })); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/app.component.ts b/next-gen-gui/src/app/app.component.ts deleted file mode 100644 index db826164b..000000000 --- a/next-gen-gui/src/app/app.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Component } from '@angular/core'; -import { SystemConfigService } from './services/system-config.service'; -import { MessageService } from './services/message.service'; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] -}) -export class AppComponent { - constructor( - private systemConfigService: SystemConfigService, - private messageService: MessageService, - ) { } - - restoreDefaultTheme(): void { - this.systemConfigService - .setGUITheme('default') - .subscribe(() => { - this.messageService.add('The default GUI theme has been selected. Please hit "Reload" in your browser.') - }) - } -} diff --git a/next-gen-gui/src/app/app.module.ts b/next-gen-gui/src/app/app.module.ts deleted file mode 100644 index 148b3ac2f..000000000 --- a/next-gen-gui/src/app/app.module.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; -import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - -import { MatTableModule } from '@angular/material/table'; -import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatSortModule } from '@angular/material/sort'; -import { MatInputModule } from '@angular/material/input'; -import { MatButtonToggleModule } from '@angular/material/button-toggle'; -import { MatCardModule } from '@angular/material/card'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatListModule } from '@angular/material/list' -import { MatButtonModule } from '@angular/material/button'; -import { FlexLayoutModule } from '@angular/flex-layout'; - -import { httpInterceptorProviders } from './http-interceptors'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; - -import { StatusListComponent } from './lists/status-list/status-list.component'; -import { DeviceListComponent } from './lists/device-list/device-list.component'; -import { DonutChartComponent } from './charts/donut-chart/donut-chart.component'; -import { DashboardComponent } from './dashboard/dashboard.component'; -import { ListToggleComponent } from './list-toggle/list-toggle.component'; - -import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; -import { InMemoryConfigDataService } from './services/in-memory-config-data.service'; - -import { deviceID } from './api-utils'; -import { environment } from '../environments/environment'; -import { ChartItemComponent } from './charts/chart-item/chart-item.component'; -import { ChartComponent } from './charts/chart/chart.component'; -import { FolderListComponent } from './lists/folder-list/folder-list.component'; -import { DialogComponent } from './dialog/dialog.component'; -import { CardComponent, CardTitleComponent, CardContentComponent } from './card/card.component'; -import { TrimPipe } from './trim.pipe'; - -@NgModule({ - declarations: [ - AppComponent, - StatusListComponent, - DeviceListComponent, - ListToggleComponent, - DashboardComponent, - DonutChartComponent, - ChartComponent, - ChartItemComponent, - FolderListComponent, - DialogComponent, - CardComponent, - CardTitleComponent, - CardContentComponent, - TrimPipe, - ], - imports: [ - BrowserModule, - AppRoutingModule, - BrowserAnimationsModule, - MatInputModule, - MatTableModule, - MatPaginatorModule, - MatSortModule, - MatButtonToggleModule, - MatCardModule, - MatProgressBarModule, - MatDialogModule, - MatListModule, - MatButtonModule, - FlexLayoutModule, - HttpClientModule, - HttpClientXsrfModule.withOptions({ - headerName: 'X-CSRF-Token-' + deviceID(), - cookieName: 'CSRF-Token-' + deviceID(), - }), - environment.production ? - [] : HttpClientInMemoryWebApiModule.forRoot(InMemoryConfigDataService, - { dataEncapsulation: false, delay: 10 }), - ], - providers: [httpInterceptorProviders], - bootstrap: [AppComponent] -}) - -export class AppModule { } - - diff --git a/next-gen-gui/src/app/card/card.component.scss b/next-gen-gui/src/app/card/card.component.scss deleted file mode 100644 index 3e0fae75b..000000000 --- a/next-gen-gui/src/app/card/card.component.scss +++ /dev/null @@ -1,37 +0,0 @@ -// Import theming functions -@import '~@angular/material/theming'; - -@mixin tui-card-theme($theme) { - // Extract the palettes you need from the theme definition. - $primary: map-get($theme, primary); - $accent: map-get($theme, accent); - $background: map-get($theme, background); - $foreground: map-get($theme, foreground); - - .tui-card { - background-color: white; - border-radius: 4px; - height: 100%; - } - - .tui-card-title { - padding: 16px 16px 0 16px; - font-size: mat-font-size($tech-ui-typography, subheading-2); - font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; - color: mat-color($primary); - } - - .tui-card-content { - padding: 16px; - } - - .tui-button-toggle .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { - line-height: 34px; - } - - @media (prefers-color-scheme: dark) { - .tui-card { - background-color: map_get($mat-grey, 800); - } - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/card/card.component.spec.ts b/next-gen-gui/src/app/card/card.component.spec.ts deleted file mode 100644 index e141708ac..000000000 --- a/next-gen-gui/src/app/card/card.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CardComponent } from './card.component'; - -describe('CardComponent', () => { - let component: CardComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CardComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/card/card.component.ts b/next-gen-gui/src/app/card/card.component.ts deleted file mode 100644 index 10a01b109..000000000 --- a/next-gen-gui/src/app/card/card.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { cardElevation } from '../style'; - -@Component({ - selector: 'app-card', - template: '
', - styleUrls: ['./card.component.scss'] -}) -export class CardComponent implements OnInit { - elevation: string = cardElevation; - - constructor() { } - - ngOnInit(): void { - } - -} - -@Component({ - selector: 'app-card-title', - template: '
', - styleUrls: ['./card.component.scss'] -}) -export class CardTitleComponent { - constructor() { } -} - -@Component({ - selector: 'app-card-content', - template: '
', - styleUrls: ['./card.component.scss'] -}) -export class CardContentComponent { - constructor() { } -} diff --git a/next-gen-gui/src/app/charts/chart-item/chart-item.component.html b/next-gen-gui/src/app/charts/chart-item/chart-item.component.html deleted file mode 100644 index 47af68d1e..000000000 --- a/next-gen-gui/src/app/charts/chart-item/chart-item.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
- -
{{count}}
-
\ No newline at end of file diff --git a/next-gen-gui/src/app/charts/chart-item/chart-item.component.scss b/next-gen-gui/src/app/charts/chart-item/chart-item.component.scss deleted file mode 100644 index 4a7d9a572..000000000 --- a/next-gen-gui/src/app/charts/chart-item/chart-item.component.scss +++ /dev/null @@ -1,27 +0,0 @@ -@mixin chart-item-theme($theme) { - .item { - cursor: pointer; - padding: 3px 7px 3px 7px; - border-radius: 4px; - } - - .selected { - background-color: #DDDDDD; - color: #303030; - } - .selected a { - color: #303030; - text-decoration: none; - } - - @media (prefers-color-scheme: dark) { - .selected { - background-color: map_get($mat-grey, 900); - color: white; - } - - .selected a { - color: white; - } - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/charts/chart-item/chart-item.component.spec.ts b/next-gen-gui/src/app/charts/chart-item/chart-item.component.spec.ts deleted file mode 100644 index 75363d385..000000000 --- a/next-gen-gui/src/app/charts/chart-item/chart-item.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ChartItemComponent } from './chart-item.component'; - -describe('ChartItemComponent', () => { - let component: ChartItemComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ ChartItemComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ChartItemComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/charts/chart-item/chart-item.component.ts b/next-gen-gui/src/app/charts/chart-item/chart-item.component.ts deleted file mode 100644 index ec5ee46d5..000000000 --- a/next-gen-gui/src/app/charts/chart-item/chart-item.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, Input } from '@angular/core'; - -@Component({ - selector: 'app-chart-item', - templateUrl: './chart-item.component.html', - styleUrls: ['./chart-item.component.scss'] -}) -export class ChartItemComponent { - @Input() state: string; - @Input() count: number; - @Input('selected') - set selected(s: boolean) { - this._selected = s; - } - - _selected: boolean = true; - - constructor() { } -} diff --git a/next-gen-gui/src/app/charts/chart/chart.component.html b/next-gen-gui/src/app/charts/chart/chart.component.html deleted file mode 100644 index eefd0d62b..000000000 --- a/next-gen-gui/src/app/charts/chart/chart.component.html +++ /dev/null @@ -1,14 +0,0 @@ - - {{title | uppercase}} - -
- - -
- - -
-
-
-
\ No newline at end of file diff --git a/next-gen-gui/src/app/charts/chart/chart.component.scss b/next-gen-gui/src/app/charts/chart/chart.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/next-gen-gui/src/app/charts/chart/chart.component.spec.ts b/next-gen-gui/src/app/charts/chart/chart.component.spec.ts deleted file mode 100644 index 41d2df417..000000000 --- a/next-gen-gui/src/app/charts/chart/chart.component.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { async, TestBed } from '@angular/core/testing'; - -import { ChartComponent } from './chart.component'; -import { HttpClientModule } from '@angular/common/http'; - -class MockService { - getEach() { - // unimplemented - } -}; - -describe('ChartComponent', () => { - let component: ChartComponent; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [ChartComponent] - }).compileComponents(); - component = TestBed.inject(ChartComponent); - })); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/charts/chart/chart.component.ts b/next-gen-gui/src/app/charts/chart/chart.component.ts deleted file mode 100644 index d18189054..000000000 --- a/next-gen-gui/src/app/charts/chart/chart.component.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Component, OnInit, ViewChild, Input, Type } from '@angular/core'; -import Folder from '../../folder' -import { FolderService } from 'src/app/services/folder.service'; -import { DonutChartComponent } from '../donut-chart/donut-chart.component'; -import { DeviceService } from 'src/app/services/device.service'; -import Device from 'src/app/device'; -import { StType } from '../../type'; -import { FilterService } from 'src/app/services/filter.service'; -import { Observable } from 'rxjs'; - - -export interface ChartItemState { - label: string, - count: number, - color: string, - selected: boolean, -} -@Component({ - selector: 'app-chart', - templateUrl: './chart.component.html', - styleUrls: ['./chart.component.scss'] -}) - -export class ChartComponent implements OnInit { - @ViewChild(DonutChartComponent) donutChart: DonutChartComponent; - @Input() type: StType; - title: string; - chartID: string; - states: ChartItemState[] = []; - - private observer: Observable; - private activeChartState: ChartItemState; - - constructor( - private folderService: FolderService, - private deviceService: DeviceService, - private filterService: FilterService, - ) { } - - onItemSelect(s: ChartItemState) { - // Send chart item state to filter - this.filterService.changeFilter({ type: this.type, text: s.label }); - - // Deselect all other items - this.states.forEach(s => { - s.selected = false; - }); - - // Select item only - if (s !== this.activeChartState) { - s.selected = true; - this.activeChartState = s; - } else { - this.activeChartState = null; - this.filterService.changeFilter({ type: this.type, text: "" }) - } - } - - ngOnInit(): void { - switch (this.type) { - case StType.Folder: - this.title = "Folders"; - this.chartID = 'foldersChart'; - this.observer = this.folderService.folderAdded$; - break; - case StType.Device: - this.title = "Devices"; - this.chartID = 'devicesChart'; - this.observer = this.deviceService.deviceAdded$; - break; - } - } - - ngAfterViewInit() { - let totalCount: number = 0; - this.observer.subscribe( - t => { - // Count the number of folders and set chart - totalCount++; - this.donutChart.count = totalCount; - - // Get StateType and convert to string - const stateType = t.stateType; - const state = t.state; - let color; - switch (this.type) { - case StType.Folder: - color = Folder.stateTypeToColor(t.stateType); - break; - case StType.Device: - color = Device.stateTypeToColor(stateType); - break; - } - - // Check if state exists - let found: boolean = false; - this.states.forEach(s => { - if (s.label === state) { - s.count = s.count + 1; - found = true; - } - }); - - if (!found) { - this.states.push({ label: state, count: 1, color: color, selected: false }); - } - - this.donutChart.updateData(this.states); - }, - err => console.error('Observer got an error: ' + err), - () => { - } - ); - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.html b/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.html deleted file mode 100644 index 83bc1ce1b..000000000 --- a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
- -
-
{{_count}}
-
{{title}}
-
-
\ No newline at end of file diff --git a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.scss b/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.scss deleted file mode 100644 index 1793f03ff..000000000 --- a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.scss +++ /dev/null @@ -1,48 +0,0 @@ -.chart-container { - position: relative; - width: 100%; - height: 100%; - } - -.center { - position: absolute; - top: 0; left: 0; bottom: 0; right: 0; - width: 50%; - height: 50%; - overflow: auto; - margin: auto; -} - -.title { - font-size: calc(0.5rem + 0.625vw); - display:none; -} - -.count-total { - font-size: calc(1rem + 0.625vw); -} - -.large-count-total { - font-size: calc(0.5rem + 0.625vw); -} - -@media (max-width: 800px) { - .count-total { - font-size: calc(1.00rem + 0.625vw); - } -} -@media (min-width: 800px) and (max-width: 1000px) { - .title { - font-size: calc(0.35rem + 0.625vw); - } - - .count-total { - font-size: calc(1.35rem + 0.625vw); - } -} - -@media (min-width:1000px) { - .title { - display: inline; - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.spec.ts b/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.spec.ts deleted file mode 100644 index 340cfd687..000000000 --- a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DonutChartComponent } from './donut-chart.component'; -import { HttpClientModule } from '@angular/common/http'; - -describe('DonutChartComponent', () => { - let component: DonutChartComponent; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [DonutChartComponent], - providers: [DonutChartComponent] - }).compileComponents(); - - component = TestBed.inject(DonutChartComponent); - })); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.ts b/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.ts deleted file mode 100644 index 1600af6fe..000000000 --- a/next-gen-gui/src/app/charts/donut-chart/donut-chart.component.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { Chart } from 'chart.js' -import { tooltip } from '../tooltip' -import { FilterService } from 'src/app/services/filter.service'; -import { ChartItemState } from '../chart/chart.component'; - -@Component({ - selector: 'app-donut-chart', - templateUrl: './donut-chart.component.html', - styleUrls: ['./donut-chart.component.scss'] -}) -export class DonutChartComponent { - @Input() elementID: string; - @Input() title: number; - @Output() stateEvent = new EventEmitter();; - - _count: number; - _countClass = "count-total"; - set count(n: number) { - if (n >= 1000) { // use a smaller font - this._countClass = "large-count-total" - } - this._count = n; - } - - private canvas: any; - private ctx: any; - private chart: Chart; - private states: ChartItemState[]; - - constructor(private filterService: FilterService) { } - - updateData(states: ChartItemState[]): void { - this.states = states; - // Using object destructuring - for (let i = 0; i < states.length; i++) { - let s = states[i]; - this.chart.data.labels[i] = s.label; - this.chart.data.datasets[0].data[i] = s.count; - this.chart.data.datasets[0].backgroundColor[i] = s.color; - } - this.chart.update(); - } - - removeAllData(withAnimation: boolean): void { - this.chart.data.labels.pop(); - this.chart.data.datasets.forEach((dataset) => { - dataset.data = []; - }); - this.chart.update(withAnimation); - } - - ngAfterViewInit(): void { - this.canvas = document.getElementById(this.elementID); - this.ctx = this.canvas.getContext('2d'); - this.chart = new Chart(this.ctx, { - type: 'doughnut', - data: { - datasets: [{ - data: [], - backgroundColor: [], - borderWidth: 1 - }] - }, - options: { - cutoutPercentage: 77, - responsive: true, - onClick: (e) => { - var activePoints = this.chart.getElementsAtEvent(e); - if (activePoints.length > 0) { - const index = activePoints[0]["_index"]; - this.stateEvent.emit(this.states[index]); - } - }, - legend: { - display: false - }, - tooltips: { - // Disable the on-canvas tooltip - enabled: false, - custom: tooltip(), - }, - animation: false - } - }); - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/charts/tooltip.ts b/next-gen-gui/src/app/charts/tooltip.ts deleted file mode 100644 index d46e33848..000000000 --- a/next-gen-gui/src/app/charts/tooltip.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Adapted from https://www.chartjs.org/samples/latest/tooltips/custom-pie.html -export let tooltip: () => (tooltip: any) => void = - function (): (tooltip: any) => void { - return function (tooltip: any): void { - // Tooltip Element - const tooltipEl = document.getElementById('chartjs-tooltip'); - - // Hide if no tooltip - if (tooltip.opacity === 0) { - tooltipEl.style.opacity = '0'; - return; - } - - // Set caret Position - tooltipEl.classList.remove('above', 'below', 'no-transform'); - if (tooltip.yAlign) { - tooltipEl.classList.add(tooltip.yAlign); - } else { - tooltipEl.classList.add('no-transform'); - } - - function getBody(bodyItem) { - return bodyItem.lines; - } - - // Set Text - if (tooltip.body) { - let titleLines = tooltip.title || []; - const bodyLines = tooltip.body.map(getBody); - - let innerHtml = ''; - - titleLines.forEach(function (title) { - innerHtml += '' + title + ''; - }); - innerHtml += ''; - - bodyLines.forEach(function (body, i) { - let colors = tooltip.labelColors[i]; - let style = 'background:' + colors.backgroundColor; - style += '; border-color:' + colors.borderColor; - style += '; border-width: 2px'; - let span = ''; - innerHtml += '' + span + body + ''; - }); - innerHtml += ''; - - let tableRoot = tooltipEl.querySelector('table'); - tableRoot.innerHTML = innerHtml; - } - - var position = this._chart.canvas.getBoundingClientRect(); - - // Display, position, and set styles for font - tooltipEl.style.opacity = '1'; - tooltipEl.style.position = 'absolute'; - tooltipEl.style.left = position.left + window.pageXOffset + tooltip.caretX + 'px'; - tooltipEl.style.top = position.top + window.pageYOffset + tooltip.caretY + 'px'; - tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px'; - tooltipEl.style.pointerEvents = 'none'; - } - }; \ No newline at end of file diff --git a/next-gen-gui/src/app/completion.ts b/next-gen-gui/src/app/completion.ts deleted file mode 100644 index a52f19c5c..000000000 --- a/next-gen-gui/src/app/completion.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface Completion { - completion: number; - globalBytes: number; - needBytes: number; - needDeletes: number; - needItems: number; -} \ No newline at end of file diff --git a/next-gen-gui/src/app/connections.ts b/next-gen-gui/src/app/connections.ts deleted file mode 100644 index bf5da1a55..000000000 --- a/next-gen-gui/src/app/connections.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface SystemConnections { - connections: { deviceId?: Connection }; - total: Connection; -} - -export interface Connection { - address: string; - at: string; - clientVersion: string; - connected: boolean; - crypto: string; - inBytesTotal: number; - outBytesTotal: number; - paused: boolean; - type: string; -} \ No newline at end of file diff --git a/next-gen-gui/src/app/dashboard/dashboard.component.html b/next-gen-gui/src/app/dashboard/dashboard.component.html deleted file mode 100644 index 4e525403d..000000000 --- a/next-gen-gui/src/app/dashboard/dashboard.component.html +++ /dev/null @@ -1,16 +0,0 @@ - - - -
- - -
-
-
- - -
- -
-
\ No newline at end of file diff --git a/next-gen-gui/src/app/dashboard/dashboard.component.scss b/next-gen-gui/src/app/dashboard/dashboard.component.scss deleted file mode 100644 index 3fd8c2b3f..000000000 --- a/next-gen-gui/src/app/dashboard/dashboard.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.progress { - margin: 0 3vw 0 3vw; -} - -.grid-container { - margin: 10px calc(10px + 3.3vw); - min-width: 600px; -} \ No newline at end of file diff --git a/next-gen-gui/src/app/dashboard/dashboard.component.spec.ts b/next-gen-gui/src/app/dashboard/dashboard.component.spec.ts deleted file mode 100644 index 1ce2c37b2..000000000 --- a/next-gen-gui/src/app/dashboard/dashboard.component.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DashboardComponent } from './dashboard.component'; -import { HttpClientModule } from '@angular/common/http'; -import { NoopAnimationsModule, BrowserAnimationsModule } from '@angular/platform-browser/animations'; - -describe('DashboardComponent', () => { - let component: DashboardComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [DashboardComponent], - imports: [ - HttpClientModule, - NoopAnimationsModule, - BrowserAnimationsModule, - ], - providers: [DashboardComponent] - }).compileComponents(); - - component = TestBed.inject(DashboardComponent); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(DashboardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should compile', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/dashboard/dashboard.component.ts b/next-gen-gui/src/app/dashboard/dashboard.component.ts deleted file mode 100644 index 41d1fed61..000000000 --- a/next-gen-gui/src/app/dashboard/dashboard.component.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Component, OnInit, AfterViewInit, ViewChild, AfterViewChecked } from '@angular/core'; -import { - trigger, - state, - style, - animate, - transition, -} from '@angular/animations'; -import { SystemConfigService } from '../services/system-config.service'; -import { StType } from '../type'; -import { FilterService } from '../services/filter.service'; -import { ProgressService } from '../services/progress.service'; -import { MatProgressBar } from '@angular/material/progress-bar'; -import { MessageService } from '../services/message.service'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { DialogComponent } from '../dialog/dialog.component'; -import { FolderService } from '../services/folder.service'; -import { DeviceService } from '../services/device.service'; - -@Component({ - selector: 'app-dashboard', - templateUrl: './dashboard.component.html', - styleUrls: ['./dashboard.component.scss'], - providers: [FilterService], - animations: [ - trigger('loading', [ - state('start', style({ - marginTop: '20px', - })), - state('done', style({ - marginTop: '0px', - })), - transition('start => done', [ - animate('0.2s 0.2s') - ]), - transition('done => start', [ - animate('0.2s 0.2s') - ]), - ]), - trigger('progressBar', [ - state('start', style({ - opacity: 100, - visibility: 'visible' - })), - state('done', style({ - opacity: 0, - visibility: 'hidden' - })), - transition('start => done', [ - animate('0.35s') - ]), - transition('done => start', [ - animate('0.35s') - ]), - ]), - ] -}) -export class DashboardComponent implements OnInit, AfterViewInit { - @ViewChild(MatProgressBar) progressBar: MatProgressBar; - folderChart: StType = StType.Folder; - deviceChart: StType = StType.Device; - progressValue: number = 0; - isLoading = true; - private dialogRef: MatDialogRef; - - - constructor( - private systemConfigService: SystemConfigService, - private folderService: FolderService, - private deviceService: DeviceService, - private progressService: ProgressService, - private messageService: MessageService, - public dialog: MatDialog - ) { } - - ngOnInit() { - // Request data from Rest API - this.systemConfigService.getSystemConfig().subscribe( - _ => { - // Request devices and folders for charts and lists - this.folderService.requestFolders(); - this.deviceService.requestDevices(); - } - ); - - } - - ngAfterViewInit() { - this.isLoading = true; - - // Listen for progress service changes - let t = setInterval(() => { - if (this.progressService.isComplete()) { - clearInterval(t); - this.progressValue = 100; - this.isLoading = false; - } - this.progressValue = this.progressService.percentValue; - }, 100); - - // Listen for messages from other services/components - this.messageService.messageAdded$ - .subscribe( - _ => { - // Open dialog - if (!this.dialogRef) - this.dialogRef = this.dialog.open(DialogComponent); - - this.dialogRef.afterClosed().subscribe( - _ => { - this.dialogRef = null; - this.messageService.clear(); - } - ); - }); - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/device.ts b/next-gen-gui/src/app/device.ts deleted file mode 100644 index e631add1f..000000000 --- a/next-gen-gui/src/app/device.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { colors } from './style'; -import Folder from './folder'; -import { Completion } from './completion'; - -interface Device { - deviceID: string; - name: string; - stateType: Device.StateType; - state: string; - paused: boolean; - connected: boolean; - completion: Completion; - used: boolean; // indicates if a folder is using the device - folders: Folder[]; -} - -namespace Device { - export enum StateType { - Insync = 1, - UnusedInsync, - Unknown, - Syncing, - Paused, - UnusedPaused, - Disconnected, - UnusedDisconnected, - } - - export function stateTypeToString(s: StateType): string { - switch (s) { - case StateType.Insync: - return 'Up to Date'; - case StateType.UnusedInsync: - return 'Connected (Unused)'; - case StateType.Unknown: - return 'Unknown'; - case StateType.Syncing: - return 'Syncing'; - case StateType.Paused: - return 'Paused'; - case StateType.UnusedPaused: - return 'Paused (Unused)'; - case StateType.Disconnected: - return 'Disconnected'; - case StateType.UnusedDisconnected: - return 'Disconnected (Unused)'; - } - } - - /** - * stateTypeToColor looks up a hex color string based on StateType - * @param s StateType - */ - export function stateTypeToColor(s: StateType): string { - switch (s) { - case StateType.Insync: - return colors.get("blue"); - case StateType.UnusedInsync: - return colors.get("grey"); - case StateType.Unknown: - return colors.get("grey"); - case StateType.Syncing: - return colors.get("green"); - case StateType.Paused: - return colors.get("grey"); - case StateType.UnusedPaused: - return colors.get("grey"); - case StateType.Disconnected: - return colors.get("yellow"); - case StateType.UnusedDisconnected: - return colors.get("grey"); - } - } - - export function getStateType(d: Device): StateType { - // StateType Unknown is set in DeviceService - if (d.stateType === StateType.Unknown) { - return StateType.Unknown; - } - - if (d.paused) { - return d.used ? StateType.Paused : StateType.UnusedPaused; - } - - if (d.connected) { - if (d.completion.completion === 100) { - return d.used ? StateType.Insync : StateType.UnusedInsync; - } else { - return StateType.Syncing; - } - } - - return d.used ? StateType.Disconnected : StateType.UnusedDisconnected; - } - - export function recalcCompletion(d: Device) { - if (!d || !d.completion || !d.folders) { - return; - } - var total = 0, needed = 0, deletes = 0, items = 0; - d.folders.forEach(folder => { - if (!folder || !folder.completion) - return - needed += folder.completion.needBytes; - items += folder.completion.needItems; - deletes += folder.completion.needDeletes; - }); - if (total == 0) { - d.completion.completion = 100; - d.completion.needBytes = 0; - d.completion.needItems = 0; - } else { - d.completion.completion = Math.floor(100 * (1 - needed / total)); - d.completion.needBytes = needed; - d.completion.needItems = items + deletes; - } - - if (needed == 0 && deletes > 0) { - // We don't need any data, but we have deletes that we need - // to do. Drop down the completion percentage to indicate - // that we have stuff to do. - d.completion.completion = 95; - } - } -} -export default Device; diff --git a/next-gen-gui/src/app/dialog/dialog.component.html b/next-gen-gui/src/app/dialog/dialog.component.html deleted file mode 100644 index f926daa41..000000000 --- a/next-gen-gui/src/app/dialog/dialog.component.html +++ /dev/null @@ -1,6 +0,0 @@ -
- - {{message}} - - -
\ No newline at end of file diff --git a/next-gen-gui/src/app/dialog/dialog.component.scss b/next-gen-gui/src/app/dialog/dialog.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/next-gen-gui/src/app/dialog/dialog.component.spec.ts b/next-gen-gui/src/app/dialog/dialog.component.spec.ts deleted file mode 100644 index a6bce8db3..000000000 --- a/next-gen-gui/src/app/dialog/dialog.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DialogComponent } from './dialog.component'; - -describe('DialogComponent', () => { - let component: DialogComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ DialogComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(DialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/dialog/dialog.component.ts b/next-gen-gui/src/app/dialog/dialog.component.ts deleted file mode 100644 index 40a554339..000000000 --- a/next-gen-gui/src/app/dialog/dialog.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, OnInit, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { MessageService } from '../services/message.service'; - -export interface DialogData { - message: 'example message'; -} - -@Component({ - selector: 'app-dialog', - templateUrl: './dialog.component.html', - styleUrls: ['./dialog.component.scss'] -}) -export class DialogComponent implements OnInit { - - constructor( - public dialogRef: MatDialogRef, - public messageService: MessageService - ) { } - - ngOnInit(): void { } - - onNoClick(): void { - this.dialogRef.close(); - } -} diff --git a/next-gen-gui/src/app/folder.ts b/next-gen-gui/src/app/folder.ts deleted file mode 100644 index ca10c3486..000000000 --- a/next-gen-gui/src/app/folder.ts +++ /dev/null @@ -1,200 +0,0 @@ -import Device from './device'; -import { colors } from './style'; -import { Completion } from './completion'; - -interface Folder { - id: string; - label: string; - devices: Device[]; - status: Folder.Status; - stateType: Folder.StateType; - state: string; - paused: boolean; - completion: Completion; - path: string; -} - -namespace Folder { - export enum StateType { - Paused = 1, - Unknown, - Unshared, - WaitingToScan, - Stopped, - Scanning, - Idle, - LocalAdditions, - WaitingToSync, - PreparingToSync, - Syncing, - OutOfSync, - FailedItems, - } - - /** - * stateTypeToString returns a string representation of - * the StateType enum - * @param s StateType - */ - export function stateTypeToString(s: StateType): string { - switch (s) { - case StateType.Paused: - return 'Paused'; - case StateType.Unknown: - return 'Unknown'; - case StateType.Unshared: - return 'Unshared'; - case StateType.WaitingToSync: - return 'Waiting to Sync'; - case StateType.Stopped: - return 'Stopped'; - case StateType.Scanning: - return 'Scanning'; - case StateType.Idle: - return 'Up to Date'; - case StateType.LocalAdditions: - return 'Local Additions'; - case StateType.WaitingToScan: - return 'Waiting to Scan'; - case StateType.PreparingToSync: - return 'Preparing to Sync'; - case StateType.Syncing: - return 'Syncing'; - case StateType.OutOfSync: - return 'Out of Sync'; - case StateType.FailedItems: - return 'Failed Items'; - } - } - - /** - * stateTypeToColor looks up a hex color string based on StateType - * @param s StateType - */ - export function stateTypeToColor(s: StateType): string { - switch (s) { - case StateType.Paused: - return colors.get("grey"); - case StateType.Unknown: - return colors.get("grey"); - case StateType.Unshared: - return colors.get("grey"); - case StateType.WaitingToSync: - return colors.get("yellow"); - case StateType.Stopped: - return colors.get("grey"); - case StateType.Scanning: - return colors.get("grey"); - case StateType.Idle: - return colors.get("blue"); - case StateType.LocalAdditions: - return colors.get("grey"); - case StateType.WaitingToScan: - return colors.get("grey"); - case StateType.PreparingToSync: - return colors.get("grey"); - case StateType.Syncing: - return colors.get("green"); - case StateType.OutOfSync: - return colors.get("grey"); - case StateType.FailedItems: - return colors.get("red"); - } - } - - /** - * getStateType looks at a folder and determines the correct - * StateType to return - * - * Possible state values from API - * "idle", "scanning", "scan-waiting", "sync-waiting", "sync-preparing" - * "syncing", "error", "unknown" - * - * @param f Folder - */ - export function getStateType(f: Folder): StateType { - if (f.paused) { - return StateType.Paused; - } - - if (!f.status || (Object.keys(f.status).length === 0)) { - return StateType.Unknown; - } - - const fs: Folder.Status = f.status; - const state: string = fs.state; - - // Match API string to StateType - switch (state) { - case "idle": - return StateType.Idle; - case "scanning": - return StateType.Scanning; - case "scan-waiting": - return StateType.WaitingToScan; - case "sync-waiting": - return StateType.WaitingToSync; - case "sync-preparing": - return StateType.PreparingToSync; - case "syncing": - return StateType.Syncing; - case "error": - // legacy, the state is called "stopped" in the gui - return StateType.Stopped; - case "unknown": - return StateType.Unknown; - } - - if (fs.needTotalItems > 0) { - return StateType.OutOfSync; - } - if (fs.pullErrors > 0) { - return StateType.FailedItems; - } - if (fs.receiveOnlyTotalItems > 0) { - return StateType.LocalAdditions; - } - if (f.devices.length <= 1) { - return StateType.Unshared; - } - - return StateType.Unknown; - } - - - export interface Status { - globalBytes: number; - globalDeleted: number; - globalDirectories: number; - globalFiles: number; - globalSymlinks: number; - globalTotalItems: number; - ignorePatterns: boolean; - inSyncBytes: number; - inSyncFiles: number; - invalid: string; - localBytes: number; - localDeleted: number; - localDirectories: number; - localFiles: number; - localSymlinks: number; - needBytes: number; - needDeletes: number; - needDirectories: number; - needFiles: number; - needSymlinks: number; - needTotalItems: number; - pullErrors: number; - receiveOnlyChangedBytes: number; - receiveOnlyChangedDeletes: number; - receiveOnlyChangedDirectories: number; - receiveOnlyChangedFiles: number; - receiveOnlyChangedSymlinks: number; - receiveOnlyTotalItems: number; - sequence: number; - state: string; - stateChanged: string; - version: number; - } -} -export default Folder; \ No newline at end of file diff --git a/next-gen-gui/src/app/http-interceptors/caching.interceptor.spec.ts b/next-gen-gui/src/app/http-interceptors/caching.interceptor.spec.ts deleted file mode 100644 index ee914ec93..000000000 --- a/next-gen-gui/src/app/http-interceptors/caching.interceptor.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { CachingInterceptor } from './caching.interceptor'; - -describe('CachingInterceptor', () => { - beforeEach(() => TestBed.configureTestingModule({ - providers: [ - CachingInterceptor - ] - })); - - it('should be created', () => { - const interceptor: CachingInterceptor = TestBed.inject(CachingInterceptor); - expect(interceptor).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/http-interceptors/caching.interceptor.ts b/next-gen-gui/src/app/http-interceptors/caching.interceptor.ts deleted file mode 100644 index 7139c945d..000000000 --- a/next-gen-gui/src/app/http-interceptors/caching.interceptor.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Injectable } from '@angular/core'; -import { - HttpRequest, - HttpHandler, - HttpEvent, - HttpInterceptor, - HttpHeaders, - HttpResponse -} from '@angular/common/http'; -import { Observable, of } from 'rxjs'; -import { tap } from 'rxjs/operators'; -import { RequestCacheService } from '../services/request-cache.service' - -@Injectable() -export class CachingInterceptor implements HttpInterceptor { - constructor(private cache: RequestCacheService) { } - - intercept(req: HttpRequest, next: HttpHandler) { - // continue if not cacheable. - if (!isCacheable(req)) { return next.handle(req); } - - const cachedResponse = this.cache.get(req); - return cachedResponse ? - of(cachedResponse) : sendRequest(req, next, this.cache); - } -} - -/** Is this request cacheable? */ -function isCacheable(req: HttpRequest) { - // Only GET requests are cacheable - return req.method === 'GET'; - /* - return req.method === 'GET' && - -1 < req.url.indexOf("url"); - */ -} - -/** - * Get server response observable by sending request to `next()`. - * Will add the response to the cache on the way out. - */ -function sendRequest( - req: HttpRequest, - next: HttpHandler, - cache: RequestCacheService): Observable> { - - // No headers allowed in npm search request - const noHeaderReq = req.clone({ headers: new HttpHeaders() }); - - return next.handle(noHeaderReq).pipe( - tap(event => { - // There may be other events besides the response. - if (event instanceof HttpResponse) { - // cache.put(req, event); // Update the cache. - } - }) - ); -} diff --git a/next-gen-gui/src/app/http-interceptors/csrf.interceptor.spec.ts b/next-gen-gui/src/app/http-interceptors/csrf.interceptor.spec.ts deleted file mode 100644 index a8f998869..000000000 --- a/next-gen-gui/src/app/http-interceptors/csrf.interceptor.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { CSRFInterceptor } from './csrf.interceptor'; - -describe('CsrfInterceptor', () => { - beforeEach(() => TestBed.configureTestingModule({ - providers: [ - CSRFInterceptor - ] - })); - - it('should be created', () => { - const interceptor: CSRFInterceptor = TestBed.inject(CSRFInterceptor); - expect(interceptor).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/http-interceptors/csrf.interceptor.ts b/next-gen-gui/src/app/http-interceptors/csrf.interceptor.ts deleted file mode 100644 index d0a4df225..000000000 --- a/next-gen-gui/src/app/http-interceptors/csrf.interceptor.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import { deviceID } from '../api-utils'; -import { - HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders -} from '@angular/common/http'; - -import { CookieService } from '../services/cookie.service'; - - -@Injectable() -export class CSRFInterceptor implements HttpInterceptor { - - constructor(private cookieService: CookieService) { } - - intercept(req: HttpRequest, next: HttpHandler) { - const dID: String = deviceID(); - const csrfCookie = 'CSRF-Token-' + dID - - // Clone the request and replace the original headers with - // cloned headers, updated with the CSRF information. - const csrfReq = req.clone({ - headers: req.headers.set('X-CSRF-Token-' + dID, - this.cookieService.getCookie(csrfCookie)) - }); - - // send cloned request with header to the next handler. - return next.handle(csrfReq); - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/http-interceptors/error.interceptor.spec.ts b/next-gen-gui/src/app/http-interceptors/error.interceptor.spec.ts deleted file mode 100644 index 4d083ad20..000000000 --- a/next-gen-gui/src/app/http-interceptors/error.interceptor.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { ErrorInterceptor } from './error.interceptor'; - -describe('ErrorInterceptor', () => { - beforeEach(() => TestBed.configureTestingModule({ - providers: [ - ErrorInterceptor - ] - })); - - it('should be created', () => { - const interceptor: ErrorInterceptor = TestBed.inject(ErrorInterceptor); - expect(interceptor).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/http-interceptors/error.interceptor.ts b/next-gen-gui/src/app/http-interceptors/error.interceptor.ts deleted file mode 100644 index d5dfb21ad..000000000 --- a/next-gen-gui/src/app/http-interceptors/error.interceptor.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@angular/core'; -import { - HttpRequest, - HttpHandler, - HttpEvent, - HttpInterceptor, - HttpErrorResponse -} from '@angular/common/http'; -import { Observable, throwError } from 'rxjs'; -import { apiRetry } from '../api-utils'; -import { retry, catchError } from 'rxjs/operators'; -import { MessageService } from '../services/message.service'; - -@Injectable() -export class ErrorInterceptor implements HttpInterceptor { - - constructor(private messageService: MessageService) { } - - intercept(request: HttpRequest, next: HttpHandler): Observable> { - return next.handle(request) - .pipe( - retry(apiRetry), - catchError((error: HttpErrorResponse) => { - let errorMsg: string; - if (error.error instanceof ErrorEvent) { - // Client side - errorMsg = `Error: ${error.error.message}`; - } else { - // Server side - errorMsg = `Error Status: ${error.status}\nMessage: ${error.message}`; - } - console.log(errorMsg); - - this.messageService.add(errorMsg); - return throwError(errorMsg); - }) - ) - } -} diff --git a/next-gen-gui/src/app/http-interceptors/index.ts b/next-gen-gui/src/app/http-interceptors/index.ts deleted file mode 100644 index f478c61e6..000000000 --- a/next-gen-gui/src/app/http-interceptors/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* "Barrel" of Http Interceptors */ -import { HTTP_INTERCEPTORS } from '@angular/common/http'; - -import { CSRFInterceptor } from './csrf.interceptor'; -import { CachingInterceptor } from './caching.interceptor'; -import { ErrorInterceptor } from './error.interceptor'; - -/** Http interceptor providers in outside-in order */ -export const httpInterceptorProviders = [ - { provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }, - { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, - // CSRFInterceptor needs to be last - { provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true }, -]; \ No newline at end of file diff --git a/next-gen-gui/src/app/list-toggle/list-toggle.component.html b/next-gen-gui/src/app/list-toggle/list-toggle.component.html deleted file mode 100644 index 51c58f0fb..000000000 --- a/next-gen-gui/src/app/list-toggle/list-toggle.component.html +++ /dev/null @@ -1,4 +0,0 @@ - - Folders - Devices - \ No newline at end of file diff --git a/next-gen-gui/src/app/list-toggle/list-toggle.component.scss b/next-gen-gui/src/app/list-toggle/list-toggle.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/next-gen-gui/src/app/list-toggle/list-toggle.component.spec.ts b/next-gen-gui/src/app/list-toggle/list-toggle.component.spec.ts deleted file mode 100644 index 77e0653dc..000000000 --- a/next-gen-gui/src/app/list-toggle/list-toggle.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ListToggleComponent } from './list-toggle.component'; - -describe('ListToggleComponent', () => { - let component: ListToggleComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ListToggleComponent] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ListToggleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/list-toggle/list-toggle.component.ts b/next-gen-gui/src/app/list-toggle/list-toggle.component.ts deleted file mode 100644 index 733973476..000000000 --- a/next-gen-gui/src/app/list-toggle/list-toggle.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; -import { StType } from '../type'; -import { MatButtonToggleGroup } from '@angular/material/button-toggle'; - - - -@Component({ - selector: 'app-list-toggle', - templateUrl: './list-toggle.component.html', - styleUrls: ['./list-toggle.component.scss'] -}) - -export class ListToggleComponent implements OnInit { - @ViewChild(MatButtonToggleGroup) group: MatButtonToggleGroup; - public listType = StType; - // public toggleValue: string = "folders"; - @Output() listTypeEvent = new EventEmitter(); - - constructor() { } - ngOnInit(): void { - } - - onSelect(t: StType): void { - this.listTypeEvent.emit(t); - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/lists/device-list/device-list.component.html b/next-gen-gui/src/app/lists/device-list/device-list.component.html deleted file mode 100644 index a27202588..000000000 --- a/next-gen-gui/src/app/lists/device-list/device-list.component.html +++ /dev/null @@ -1,31 +0,0 @@ - - Filter - - - - - - - - - - - - - - - -
{{column}} {{device[column]}} -
-
- Folders: - {{folder.label | trim}} -
-
-
- - - \ No newline at end of file diff --git a/next-gen-gui/src/app/lists/device-list/device-list.component.spec.ts b/next-gen-gui/src/app/lists/device-list/device-list.component.spec.ts deleted file mode 100644 index 7b45e3278..000000000 --- a/next-gen-gui/src/app/lists/device-list/device-list.component.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatSortModule } from '@angular/material/sort'; -import { MatTableModule } from '@angular/material/table'; - -import { DeviceListComponent } from './device-list.component'; -import { HttpClientModule } from '@angular/common/http'; -import { ChangeDetectorRef } from '@angular/core'; - -describe('DeviceListComponent', () => { - let component: DeviceListComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [DeviceListComponent], - imports: [HttpClientModule], - providers: [DeviceListComponent, ChangeDetectorRef] - }).compileComponents(); - - component = TestBed.inject(DeviceListComponent); - })); - - it('should compile', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/lists/device-list/device-list.component.ts b/next-gen-gui/src/app/lists/device-list/device-list.component.ts deleted file mode 100644 index 3333b01a9..000000000 --- a/next-gen-gui/src/app/lists/device-list/device-list.component.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { AfterViewInit, Component, OnInit, ViewChild, ChangeDetectorRef, OnDestroy } from '@angular/core'; -import { MatPaginator } from '@angular/material/paginator'; -import { MatSort } from '@angular/material/sort'; -import { MatTable, MatTableDataSource } from '@angular/material/table'; - -import Device from '../../device'; -import { SystemConfigService } from '../../services/system-config.service'; -import { FilterService } from 'src/app/services/filter.service'; -import { StType } from 'src/app/type'; -import { MatInput } from '@angular/material/input'; -import { DeviceService } from 'src/app/services/device.service'; -import { trigger, state, style, transition, animate } from '@angular/animations'; - -@Component({ - selector: 'app-device-list', - templateUrl: './device-list.component.html', - styleUrls: ['../status-list/status-list.component.scss'], - animations: [ - trigger('detailExpand', [ - state('collapsed', style({ height: '0px', minHeight: '0' })), - state('expanded', style({ height: '*' })), - transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), - ]), - ], -}) -export class DeviceListComponent implements AfterViewInit, OnInit, OnDestroy { - @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild(MatSort) sort: MatSort; - @ViewChild(MatTable) table: MatTable; - @ViewChild(MatInput) input: MatInput; - dataSource: MatTableDataSource; - - /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ - displayedColumns = ['deviceID', 'name', 'state']; - expandedDevice: Device | null; - - constructor( - private deviceService: DeviceService, - private filterService: FilterService, - private cdr: ChangeDetectorRef, - ) { }; - - applyFilter(event: Event) { - // Set previous filter value - const filterValue = (event.target as HTMLInputElement).value; - this.filterService.previousInputs.set(StType.Device, filterValue); - this.dataSource.filter = filterValue.trim().toLowerCase(); - } - - ngOnInit() { - this.dataSource = new MatTableDataSource(); - this.dataSource.data = []; - - // Replace all data when requests are finished - this.deviceService.devicesUpdated$.subscribe( - devices => { - this.dataSource.data = devices; - } - ); - - // Add device as they come in - let devices: Device[] = []; - this.deviceService.deviceAdded$.subscribe( - device => { - devices.push(device); - this.dataSource.data = devices; - } - ); - } - - ngAfterViewInit() { - this.dataSource.sort = this.sort; - this.dataSource.paginator = this.paginator; - this.table.dataSource = this.dataSource; - - const changeText = (text: string) => { - this.dataSource.filter = text.trim().toLowerCase(); - this.input.value = text; - this.cdr.detectChanges(); - } - - // Set previous value - changeText(this.filterService.previousInputs.get(StType.Device)); - - // Listen for filter changes from other components - this.filterService.filterChanged$ - .subscribe( - input => { - if (input.type === StType.Device) { - changeText(input.text); - } - }); - } - - ngOnDestroy() { } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/lists/folder-list/folder-list.component.html b/next-gen-gui/src/app/lists/folder-list/folder-list.component.html deleted file mode 100644 index 90b16c28f..000000000 --- a/next-gen-gui/src/app/lists/folder-list/folder-list.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - Filter - - - - - - - - - - - - - - - - -
{{column}} {{folder[column]}} -
-
- Shared with: - {{device.name}} -
-
-
- - - \ No newline at end of file diff --git a/next-gen-gui/src/app/lists/folder-list/folder-list.component.spec.ts b/next-gen-gui/src/app/lists/folder-list/folder-list.component.spec.ts deleted file mode 100644 index 9fc88dcd3..000000000 --- a/next-gen-gui/src/app/lists/folder-list/folder-list.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FolderListComponent } from './folder-list.component'; -import { HttpClientModule } from '@angular/common/http'; -import { ChangeDetectorRef } from '@angular/core'; - -describe('FolderListComponent', () => { - let component: FolderListComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [FolderListComponent], - imports: [HttpClientModule], - providers: [FolderListComponent, ChangeDetectorRef] - }) - .compileComponents(); - - component = TestBed.inject(FolderListComponent); - })); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/lists/folder-list/folder-list.component.ts b/next-gen-gui/src/app/lists/folder-list/folder-list.component.ts deleted file mode 100644 index 357108281..000000000 --- a/next-gen-gui/src/app/lists/folder-list/folder-list.component.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { AfterViewInit, Component, OnInit, ViewChild, ChangeDetectorRef, OnDestroy } from '@angular/core'; -import { MatPaginator } from '@angular/material/paginator'; -import { MatSort } from '@angular/material/sort'; -import { MatTable, MatTableDataSource } from '@angular/material/table'; - -import Folder from '../../folder'; -import { SystemConfigService } from '../../services/system-config.service'; -import { FilterService } from 'src/app/services/filter.service'; -import { StType } from 'src/app/type'; -import { MatInput } from '@angular/material/input'; -import { FolderService } from 'src/app/services/folder.service'; -import { trigger, state, style, transition, animate } from '@angular/animations'; - -@Component({ - selector: 'app-folder-list', - templateUrl: './folder-list.component.html', - styleUrls: ['../status-list/status-list.component.scss'], - animations: [ - trigger('detailExpand', [ - state('collapsed', style({ height: '0px', minHeight: '0' })), - state('expanded', style({ height: '*' })), - transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), - ]), - ], -}) -export class FolderListComponent implements AfterViewInit, OnInit, OnDestroy { - @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild(MatSort) sort: MatSort; - @ViewChild(MatTable) table: MatTable; - @ViewChild(MatInput) input: MatInput; - dataSource: MatTableDataSource; - - /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ - displayedColumns = [ - "id", - "label", - "path", - "state" - ]; - - expandedFolder: Folder | null; - - constructor( - private folderService: FolderService, - private filterService: FilterService, - private cdr: ChangeDetectorRef, - ) { - }; - - applyFilter(event: Event) { - const filterValue = (event.target as HTMLInputElement).value; - this.filterService.previousInputs.set(StType.Folder, filterValue); - this.dataSource.filter = filterValue.trim().toLowerCase(); - } - - ngOnInit() { - this.dataSource = new MatTableDataSource(); - this.dataSource.data = []; - - // Replace all data when requests are finished - this.folderService.foldersUpdated$.subscribe( - folders => { - this.dataSource.data = folders; - } - ); - - // Add device as they come in - let folders: Folder[] = []; - this.folderService.folderAdded$.subscribe( - folder => { - folders.push(folder); - this.dataSource.data = folders; - } - );; - } - - ngAfterViewInit() { - this.dataSource.sort = this.sort; - this.dataSource.paginator = this.paginator; - this.table.dataSource = this.dataSource; - - const changeText = (text: string) => { - this.dataSource.filter = text.trim().toLowerCase(); - this.input.value = text; - this.cdr.detectChanges(); - } - - // Set previous value - changeText(this.filterService.previousInputs.get(StType.Folder)); - - // Listen for filter changes from other components - this.filterService.filterChanged$ - .subscribe( - input => { - if (input.type === StType.Folder) { - changeText(input.text); - } - }); - } - - ngOnDestroy() { } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/lists/status-list/status-list.component.html b/next-gen-gui/src/app/lists/status-list/status-list.component.html deleted file mode 100644 index bf2b5999d..000000000 --- a/next-gen-gui/src/app/lists/status-list/status-list.component.html +++ /dev/null @@ -1,10 +0,0 @@ - -
- {{title | uppercase}} - -
- - - - -
\ No newline at end of file diff --git a/next-gen-gui/src/app/lists/status-list/status-list.component.scss b/next-gen-gui/src/app/lists/status-list/status-list.component.scss deleted file mode 100644 index 61d04678d..000000000 --- a/next-gen-gui/src/app/lists/status-list/status-list.component.scss +++ /dev/null @@ -1,70 +0,0 @@ -.status-list .tui-card-toggle { - padding: 16px 16px 0 16px; -} - -.full-width-table { - width: 100%; -} - -.mat-form-field { - font-size: 14px; - width: 100%; -} - -tr.detail-row { - height: 0; -} - -tr.table-row:not(.expanded-row):hover { - background: whitesmoke; - color: #303030; -} - -tr.table-row:not(.expanded-row):active { - background: #DDDDDD; - color: #303030; -} - -.expanded-row { - background: #DDDDDD; - color: #303030; -} - -.table-row td { - border-bottom-width: 0; -} - -.table-detail { - overflow: hidden; - display: flex; -} - -.detail-items { - padding: 16px; -} - -// Hide empty name -.item-name:empty { - display: none; -} - -.item-name:not(:last-child):after { - content: ", "; -} - -@media (prefers-color-scheme: dark) { - tr.table-row:not(.expanded-row):hover { - background: #212121; - color: white; - } - - tr.table-row:not(.expanded-row):active { - background: #212121; - color: white; - } - - .expanded-row { - background: #212121; - color: white; - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/lists/status-list/status-list.component.spec.ts b/next-gen-gui/src/app/lists/status-list/status-list.component.spec.ts deleted file mode 100644 index c4daf34c4..000000000 --- a/next-gen-gui/src/app/lists/status-list/status-list.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StatusListComponent } from './status-list.component'; - -describe('StatusListComponent', () => { - let component: StatusListComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [StatusListComponent] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(StatusListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/lists/status-list/status-list.component.ts b/next-gen-gui/src/app/lists/status-list/status-list.component.ts deleted file mode 100644 index 2ab3c181d..000000000 --- a/next-gen-gui/src/app/lists/status-list/status-list.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Component, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; -import { StType } from '../../type'; -import { cardElevation } from '../../style'; -import { FilterService } from 'src/app/services/filter.service'; -import { ListToggleComponent } from 'src/app/list-toggle/list-toggle.component'; - - -@Component({ - selector: 'app-status-list', - templateUrl: './status-list.component.html', - styleUrls: ['./status-list.component.scss'] -}) -export class StatusListComponent { - @ViewChild(ListToggleComponent) toggle: ListToggleComponent; - currentListType: StType = StType.Folder; - listType = StType; // used in html - elevation: string = cardElevation; - title: string = 'Status'; - - constructor( - private filterService: FilterService, - private cdr: ChangeDetectorRef, - ) { } - - ngAfterViewInit() { - // Listen for filter changes from other components - this.filterService.filterChanged$.subscribe( - input => { - this.currentListType = input.type; - - switch (input.type) { - case StType.Folder: - this.toggle.group.value = "folders"; - break; - case StType.Device: - this.toggle.group.value = "devices"; - break; - } - }); - - this.cdr.detectChanges(); // manually detect changes - } - - onToggle(t: StType) { - this.currentListType = t; - } -} diff --git a/next-gen-gui/src/app/mocks/mock-db-completion.ts b/next-gen-gui/src/app/mocks/mock-db-completion.ts deleted file mode 100644 index a0fd65480..000000000 --- a/next-gen-gui/src/app/mocks/mock-db-completion.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const dbCompletion = - [ - { - "device": "YZJBJFX-RDBL7WY-6ZGKJ2D-4MJB4E7-ZATSDUY-LD6Y3L3-MLFUYWE-AEMXJAC", - "completion": 100, - "globalBytes": 156793013575, - "needBytes": 0, - "needDeletes": 0, - "needItems": 0 - }, - { - "completion": 80, - "globalBytes": 3013575, - "needBytes": 100, - "needDeletes": 0, - "needItems": 0 - } - ] -/* -{ - "completion": 100, - "globalBytes": 156793013575, - "needBytes": 0, - "needDeletes": 0 -} -*/ \ No newline at end of file diff --git a/next-gen-gui/src/app/mocks/mock-db-status.ts b/next-gen-gui/src/app/mocks/mock-db-status.ts deleted file mode 100644 index 03c5a51c6..000000000 --- a/next-gen-gui/src/app/mocks/mock-db-status.ts +++ /dev/null @@ -1,46 +0,0 @@ -export const dbStatus = - [ - { "folder": "GXWxf-3zgnU", "state": "active" }, - { "folder": "Tyeho-ncvqp", "state": "idle" }, - { "folder": "Ihpqp-3zgnq", "state": "idle" }, - { "folder": "Abqqp-3zgnU", "state": "idle" }, - { "folder": "Bawer-3zgnU", "state": "idle" }, - { "folder": "Zpohq-3zgnU", "state": "idle" }, - { "folder": "Lkmbn-3zgnU", "state": "idle" }, - { "folder": "Poqff-3zgnU", "state": "idle" } - ] -/*[{ - "folder": "GXWxf-3zgnU", - "globalBytes": 0, - "globalDeleted": 0, - "globalDirectories": 0, - "globalFiles": 0, - "globalSymlinks": 0, - "globalTotalItems": 0, - "ignorePatterns": false, - "inSyncBytes": 0, - "inSyncFiles": 0, - "invalid": "", - "localBytes": 0, - "localDeleted": 0, - "localDirectories": 0, - "localFiles": 0, - "localSymlinks": 0, - "localTotalItems": 0, - "needBytes": 0, - "needDeletes": 0, - "needDirectories": 0, - "needFiles": 0, - "needSymlinks": 0, - "needTotalItems": 0, - "pullErrors": 0, - "receiveOnlyChangedBytes": 0, - "receiveOnlyChangedDeletes": 0, - "receiveOnlyChangedDirectories": 0, - "receiveOnlyChangedFiles": 0, - "receiveOnlyChangedSymlinks": 0, - "sequence": 0, - "state": "idle", - "stateChanged": "2018-08-08T07:04:57.301064781+02:00", - "version": 0 -}]*/; \ No newline at end of file diff --git a/next-gen-gui/src/app/mocks/mock-system-config.ts b/next-gen-gui/src/app/mocks/mock-system-config.ts deleted file mode 100644 index b5cbe80a0..000000000 --- a/next-gen-gui/src/app/mocks/mock-system-config.ts +++ /dev/null @@ -1,67 +0,0 @@ -export const config = { - "version": 15, - "folders": [ - { "id": "GXWxf-3zgnU", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "YZJBJFX-RDBL7WY-6ZGKJ2D-4MJB4E7-ZATSDUY-LD6Y3L3-MLFUYWE-AEMXJAC" }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - { "id": "Tyeho-ncvqp", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "..." }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - { "id": "Ihpqp-3zgnq", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "..." }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - { "id": "Abqqp-3zgnU", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "..." }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - { "id": "Bawer-3zgnU", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "..." }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - { "id": "Zpohq-3zgnU", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "..." }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - { "id": "Lkmbn-3zgnU", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "..." }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - { "id": "Poqff-3zgnU", "label": "MyFolder", "path": "...", "type": "sendreceive", "devices": [{ "deviceID": "..." }], "rescanIntervalS": 60, "ignorePerms": false, "autoNormalize": true, "minDiskFreePct": 1, "versioning": { "type": "simple", "params": { "keep": "5" } }, "copiers": 0, "pullers": 0, "hashers": 0, "order": "random", "ignoreDelete": false, "scanProgressIntervalS": 0, "pullerSleepS": 0, "pullerPauseS": 0, "maxConflicts": 10, "disableSparseFiles": false, "disableTempIndexes": false, "fsync": false, "invalid": "" }, - ], - "devices": [ - { "deviceID": "YZJBJFX-RDBL7WY-6ZGKJ2D-4MJB4E7-ZATSDUY-LD6Y3L3-MLFUYWE-AEMXJAC", "name": "Laptop", "addresses": ["dynamic", "tcp://192.168.1.2:22000"], "compression": "metadata", "certName": "", "introducer": false }, - { "deviceID": "...", "name": "Server", "addresses": ["dynamic", "tcp://192.168.1.3:22000"], "compression": "metadata", "certName": "", "introducer": false }, - ], - "gui": { - "enabled": true, - "address": "127.0.0.1:8384", - "user": "Username", - "password": "$2a$10$ZFws69T4FlvWwsqeIwL.TOo5zOYqsa/.TxlUnsGYS.j3JvjFTmxo6", - "useTLS": false, - "apiKey": "pGahcht56664QU5eoFQW6szbEG6Ec2Cr", - "insecureAdminAccess": false, - "theme": "default" - }, - "options": { - "listenAddresses": [ - "default" - ], - "globalAnnounceServers": [ - "default" - ], - "globalAnnounceEnabled": true, - "localAnnounceEnabled": true, - "localAnnouncePort": 21027, - "localAnnounceMCAddr": "[ff12::8384]:21027", - "maxSendKbps": 0, - "maxRecvKbps": 0, - "reconnectionIntervalS": 60, - "relaysEnabled": true, - "relayReconnectIntervalM": 10, - "startBrowser": false, - "natEnabled": true, - "natLeaseMinutes": 60, - "natRenewalMinutes": 30, - "natTimeoutSeconds": 10, - "urAccepted": -1, - "urUniqueId": "", - "urURL": "https://data.syncthing.net/newdata", - "urPostInsecurely": false, - "urInitialDelayS": 1800, - "restartOnWakeup": true, - "autoUpgradeIntervalH": 12, - "keepTemporariesH": 24, - "cacheIgnoredFiles": false, - "progressUpdateIntervalS": 5, - "limitBandwidthInLan": false, - "minHomeDiskFreePct": 1, - "releasesURL": "https://upgrades.syncthing.net/meta.json", - "alwaysLocalNets": [], - "overwriteRemoteDeviceNamesOnConnect": false, - "tempIndexMinBlocks": 10 - }, - "ignoredDevices": [], - "ignoredFolders": [] -} \ No newline at end of file diff --git a/next-gen-gui/src/app/mocks/mock-system-connections.ts b/next-gen-gui/src/app/mocks/mock-system-connections.ts deleted file mode 100644 index 4e77f506f..000000000 --- a/next-gen-gui/src/app/mocks/mock-system-connections.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const connections = { - "total": { - "paused": false, - "clientVersion": "", - "at": "2015-11-07T17:29:47.691637262+01:00", - "connected": false, - "inBytesTotal": 1479, - "type": "", - "outBytesTotal": 1318, - "address": "" - }, - "connections": { - "YZJBJFX-RDBL7WY-6ZGKJ2D-4MJB4E7-ZATSDUY-LD6Y3L3-MLFUYWE-AEMXJAC": { - "connected": true, - "inBytesTotal": 556, - "paused": false, - "at": "2015-11-07T17:29:47.691548971+01:00", - "clientVersion": "v0.12.1", - "address": "127.0.0.1:22002", - "type": "TCP (Client)", - "outBytesTotal": 550 - }, - "DOVII4U-SQEEESM-VZ2CVTC-CJM4YN5-QNV7DCU-5U3ASRL-YVFG6TH-W5DV5AA": { - "outBytesTotal": 0, - "type": "", - "address": "", - "at": "0001-01-01T00:00:00Z", - "clientVersion": "", - "paused": false, - "inBytesTotal": 0, - "connected": false - }, - "UYGDMA4-TPHOFO5-2VQYDCC-7CWX7XW-INZINQT-LE4B42N-4JUZTSM-IWCSXA4": { - "address": "", - "type": "", - "outBytesTotal": 0, - "connected": false, - "inBytesTotal": 0, - "paused": false, - "at": "0001-01-01T00:00:00Z", - "clientVersion": "" - } - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/mocks/mock-system-status.ts b/next-gen-gui/src/app/mocks/mock-system-status.ts deleted file mode 100644 index 1a6472f19..000000000 --- a/next-gen-gui/src/app/mocks/mock-system-status.ts +++ /dev/null @@ -1,59 +0,0 @@ -export const systemStatus = { - "alloc": 30618136, - "connectionServiceStatus": { - "dynamic+https://relays.syncthing.net/endpoint": { - "error": null, - "lanAddresses": [ - "relay://23.92.71.120:443/?id=53STGR7-YBM6FCX-PAZ2RHM-YPY6OEJ-WYHVZO7-PCKQRCK-PZLTP7T-434XCAD&pingInterval=1m0s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=canton7" - ], - "wanAddresses": [ - "relay://23.92.71.120:443/?id=53STGR7-YBM6FCX-PAZ2RHM-YPY6OEJ-WYHVZO7-PCKQRCK-PZLTP7T-434XCAD&pingInterval=1m0s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=canton7" - ] - }, - "tcp://0.0.0.0:22000": { - "error": null, - "lanAddresses": [ - "tcp://0.0.0.0:22000" - ], - "wanAddresses": [ - "tcp://0.0.0.0:22000" - ] - } - }, - "cpuPercent": 0, - "discoveryEnabled": true, - "discoveryErrors": { - "global@https://discovery-v4-1.syncthing.net/v2/": "500 Internal Server Error", - "global@https://discovery-v4-2.syncthing.net/v2/": "Post https://discovery-v4-2.syncthing.net/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)", - "global@https://discovery-v4-3.syncthing.net/v2/": "Post https://discovery-v4-3.syncthing.net/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)", - "global@https://discovery-v6-1.syncthing.net/v2/": "Post https://discovery-v6-1.syncthing.net/v2/: dial tcp [2001:470:28:4d6::5]:443: connect: no route to host", - "global@https://discovery-v6-2.syncthing.net/v2/": "Post https://discovery-v6-2.syncthing.net/v2/: dial tcp [2604:a880:800:10::182:a001]:443: connect: no route to host", - "global@https://discovery-v6-3.syncthing.net/v2/": "Post https://discovery-v6-3.syncthing.net/v2/: dial tcp [2400:6180:0:d0::d9:d001]:443: connect: no route to host" - }, - "discoveryMethods": 8, - "goroutines": 49, - "lastDialStatus": { - "tcp://10.20.30.40": { - "when": "2019-05-16T07:41:23Z", - "error": "dial tcp 10.20.30.40:22000: i/o timeout" - }, - "tcp://172.16.33.3:22000": { - "when": "2019-05-16T07:40:43Z", - "ok": true - }, - "tcp://83.233.120.221:22000": { - "when": "2019-05-16T07:41:13Z", - "error": "dial tcp 83.233.120.221:22000: connect: connection refused" - } - }, - "myID": "YZJBJFX-RDBL7WY-6ZGKJ2D-4MJB4E7-ZATSDUY-LD6Y3L3-MLFUYWE-AEMXJAC", - "pathSeparator": "/", - "startTime": "2016-06-06T19:41:43.039284753+02:00", - "sys": 42092792, - "themes": [ - "default", - "dark" - ], - "tilde": "/Users/jb", - "uptime": 2635 -} \ No newline at end of file diff --git a/next-gen-gui/src/app/services/cookie.service.spec.ts b/next-gen-gui/src/app/services/cookie.service.spec.ts deleted file mode 100644 index 43ea274f5..000000000 --- a/next-gen-gui/src/app/services/cookie.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { CookieService } from './cookie.service'; - -describe('CookieService', () => { - let service: CookieService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(CookieService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/cookie.service.ts b/next-gen-gui/src/app/services/cookie.service.ts deleted file mode 100644 index f6d3cf467..000000000 --- a/next-gen-gui/src/app/services/cookie.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class CookieService { - - constructor() { } - - getCookie(name: string): string { - let ca: Array = document.cookie.split(';'); - let caLen: number = ca.length; - let cookieName = `${name}=`; - let c: string; - - for (let i: number = 0; i < caLen; i += 1) { - c = ca[i].replace(/^\s+/g, ''); - if (c.indexOf(cookieName) == 0) { - return c.substring(cookieName.length, c.length); - } - } - return ''; - } - - deleteCookie(name): void { - this.setCookie(name, "", -1); - } - - setCookie(name: string, value: string, expireDays: number, path: string = ""): void { - let d: Date = new Date(); - d.setTime(d.getTime() + expireDays * 24 * 60 * 60 * 1000); - let expires: string = "expires=" + d.toUTCString(); - document.cookie = name + "=" + value + "; " + expires + (path.length > 0 ? "; path=" + path : ""); - } -} diff --git a/next-gen-gui/src/app/services/db-completion.service.spec.ts b/next-gen-gui/src/app/services/db-completion.service.spec.ts deleted file mode 100644 index e6a07d016..000000000 --- a/next-gen-gui/src/app/services/db-completion.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { DbCompletionService } from './db-completion.service'; -import { HttpClientModule } from '@angular/common/http'; - -describe('DbCompletionService', () => { - let service: DbCompletionService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [DbCompletionService] - }); - service = TestBed.inject(DbCompletionService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/db-completion.service.ts b/next-gen-gui/src/app/services/db-completion.service.ts deleted file mode 100644 index e3dffa7c6..000000000 --- a/next-gen-gui/src/app/services/db-completion.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; -import { environment } from 'src/environments/environment'; -import { apiURL } from '../api-utils'; -import { Completion } from '../completion'; -import { map } from 'rxjs/operators'; -import { Observable } from 'rxjs'; -import { StType } from '../type'; - -@Injectable({ - providedIn: 'root' -}) -export class DbCompletionService { - private dbStatusUrl = environment.production ? apiURL + 'rest/db/completion' : 'api/dbCompletion'; - - constructor(private http: HttpClient) { } - - getCompletion(type: StType, id: string): Observable { - let httpOptions: { params: HttpParams }; - if (id) { - switch (type) { - case StType.Device: - httpOptions = { - params: new HttpParams().set('device', id) - }; - break; - case StType.Folder: - httpOptions = { - params: new HttpParams().set('folder', id) - }; - break; - } - } else { } - - return this.http - .get(this.dbStatusUrl, httpOptions) - .pipe( - map(res => { - // Remove from array in development - // in-memory-web-api returns arrays - if (!environment.production) { - const a: any = res as any; - if (a.length > 0) { - res = res[0]; - } - } - return res; - }) - ); - } -} diff --git a/next-gen-gui/src/app/services/db-status.service.spec.ts b/next-gen-gui/src/app/services/db-status.service.spec.ts deleted file mode 100644 index 65c7c871c..000000000 --- a/next-gen-gui/src/app/services/db-status.service.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { DbStatusService } from './db-status.service'; -import { HttpClientModule } from '@angular/common/http'; - -describe('DbStatusService', () => { - let service: DbStatusService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [DbStatusService] - }); - TestBed.configureTestingModule({}); - service = TestBed.inject(DbStatusService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/db-status.service.ts b/next-gen-gui/src/app/services/db-status.service.ts deleted file mode 100644 index 7c0bfe8d4..000000000 --- a/next-gen-gui/src/app/services/db-status.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; - -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import { environment } from '../../environments/environment' -import { apiURL } from '../api-utils' -import Folder from '../folder' - -@Injectable({ - providedIn: 'root' -}) -export class DbStatusService { - private dbStatusUrl = environment.production ? apiURL + 'rest/db/status' : 'api/dbStatus'; - - constructor(private http: HttpClient) { } - - getFolderStatus(id: string): Observable { - let httpOptions: { params: HttpParams }; - if (id) { - httpOptions = { - params: new HttpParams().set('folder', id) - }; - } else { } - - return this.http - .get(this.dbStatusUrl, httpOptions) - .pipe( - map(res => { - // Remove from array in development - // in-memory-web-api returns arrays - if (!environment.production) { - const a: any = res as any; - if (a.length > 0) { - res = res[0]; - } - } - return res; - }) - ); - } -} diff --git a/next-gen-gui/src/app/services/device.service.spec.ts b/next-gen-gui/src/app/services/device.service.spec.ts deleted file mode 100644 index 35b94a052..000000000 --- a/next-gen-gui/src/app/services/device.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { DeviceService } from './device.service'; -import { HttpClientModule } from '@angular/common/http'; - -describe('DeviceService', () => { - let service: DeviceService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [DeviceService] - }); - service = TestBed.inject(DeviceService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/device.service.ts b/next-gen-gui/src/app/services/device.service.ts deleted file mode 100644 index a1c0d995f..000000000 --- a/next-gen-gui/src/app/services/device.service.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Injectable } from '@angular/core'; -import Device from '../device'; -import { Observable, Subscriber, ReplaySubject, Subject } from 'rxjs'; -import { SystemConfigService } from './system-config.service'; -import { SystemConnectionsService } from './system-connections.service'; -import { DbCompletionService } from './db-completion.service'; -import { SystemConnections } from '../connections'; -import { SystemStatusService } from './system-status.service'; -import { ProgressService } from './progress.service'; -import { StType } from '../type'; - -@Injectable({ - providedIn: 'root' -}) -export class DeviceService { - private devices: Device[]; - private sysConns: SystemConnections; - private devicesSubject: ReplaySubject = new ReplaySubject(1); - devicesUpdated$ = this.devicesSubject.asObservable(); - private thisDevice: Device; - - private deviceAddedSource = new Subject(); - deviceAdded$ = this.deviceAddedSource.asObservable(); - - constructor( - private systemConfigService: SystemConfigService, - private systemConnectionsService: SystemConnectionsService, - private dbCompletionService: DbCompletionService, - private systemStatusService: SystemStatusService, - private progressService: ProgressService, - ) { } - - getDeviceStatusInOrder(startIndex: number) { - // Return if there aren't any device at the index - if (startIndex >= (this.devices.length)) { - this.devicesSubject.next(this.devices); - // this.devicesSubject.complete(); - // this.deviceAddedSource.complete(); - return; - } - const device: Device = this.devices[startIndex]; - startIndex = startIndex + 1; - - // Check if device in the connections - if (this.sysConns.connections[device.deviceID] === undefined) { - device.stateType = Device.StateType.Unknown; - } else { - // Set connected - device.connected = this.sysConns.connections[device.deviceID].connected; - - // TODO ? temporarily set to connected - if (device.deviceID === this.thisDevice.deviceID) { - device.connected = true; - } - } - - this.dbCompletionService.getCompletion(StType.Device, device.deviceID).subscribe( - c => { - device.completion = c; - Device.recalcCompletion(device); - device.stateType = Device.getStateType(device); - device.state = Device.stateTypeToString(device.stateType); - - this.deviceAddedSource.next(device); - this.progressService.addToProgress(1); - - // recursively get the status of the next device - this.getDeviceStatusInOrder(startIndex); - }); - } - - /** - * getEach() returns each device - */ - requestDevices() { - this.systemConfigService.getDevices().subscribe( - devices => { - this.devices = devices; - - // First check to see which device is local 'thisDevice' - this.systemStatusService.getSystemStatus().subscribe( - status => { - this.devices.forEach(device => { - if (device.deviceID === status.myID) { - // TODO Determine if it should ignore thisDevice - this.thisDevice = device; - } - }); - - // Check folder devices to see if the device is used - this.systemConfigService.getFolders().subscribe( - folders => { - // Loop through all folder devices to see if the device is used - this.devices.forEach(device => { - // Alloc array if needed - if (!device.folders) { - device.folders = []; - } - - folders.forEach(folder => { - folder.devices.forEach(fdevice => { - if (device.deviceID === fdevice.deviceID) { - // The device is used by a folder - device.used = true; - - // Add a reference to the folder to the device - device.folders.push(folder); - } - }); - }); - }); - - // See if the connection is connected or undefined - this.systemConnectionsService.getSystemConnections().subscribe( - c => { - this.sysConns = c; - - // Synchronously get the status of each device - this.getDeviceStatusInOrder(0); - } - ); - }); - } - ) - } - ); - } -} diff --git a/next-gen-gui/src/app/services/filter.service.spec.ts b/next-gen-gui/src/app/services/filter.service.spec.ts deleted file mode 100644 index 279827c5c..000000000 --- a/next-gen-gui/src/app/services/filter.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { FilterService } from './filter.service'; - -describe('FilterService', () => { - let service: FilterService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(FilterService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/filter.service.ts b/next-gen-gui/src/app/services/filter.service.ts deleted file mode 100644 index a30fe9a42..000000000 --- a/next-gen-gui/src/app/services/filter.service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Injectable } from '@angular/core'; -import { StType } from '../type'; -import { Subject } from 'rxjs'; - -export interface FilterInput { - type: StType; - text: string -} - -@Injectable({ - providedIn: 'root' -}) -export class FilterService { - previousInputs = new Map( - [ - [StType.Folder, ""], - [StType.Device, ""], - ] - ) - - constructor() { } - - private filterChangeSource = new Subject(); - filterChanged$ = this.filterChangeSource.asObservable(); - - changeFilter(input: FilterInput) { - this.previousInputs.set(input.type, input.text) - this.filterChangeSource.next(input); - } -} diff --git a/next-gen-gui/src/app/services/folder.service.spec.ts b/next-gen-gui/src/app/services/folder.service.spec.ts deleted file mode 100644 index 6382d4ac8..000000000 --- a/next-gen-gui/src/app/services/folder.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { FolderService } from './folder.service'; -import { HttpClientModule } from '@angular/common/http'; - -describe('FolderService', () => { - let service: FolderService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [FolderService] - }); - service = TestBed.inject(FolderService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/folder.service.ts b/next-gen-gui/src/app/services/folder.service.ts deleted file mode 100644 index d31683edd..000000000 --- a/next-gen-gui/src/app/services/folder.service.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Injectable } from '@angular/core'; -import { SystemConfigService } from './system-config.service'; -import { Observable, Subscriber, Subject, ReplaySubject } from 'rxjs'; -import Folder from '../folder'; -import { DbStatusService } from './db-status.service'; -import { ProgressService } from './progress.service'; -import { DbCompletionService } from './db-completion.service'; -import { StType } from '../type'; -import { DeviceService } from './device.service'; - -@Injectable({ - providedIn: 'root' -}) -export class FolderService { - private folders: Folder[]; - private foldersSubject: ReplaySubject = new ReplaySubject(1); - foldersUpdated$ = this.foldersSubject.asObservable(); - private folderAddedSource = new Subject(); - folderAdded$ = this.folderAddedSource.asObservable(); - - constructor( - private systemConfigService: SystemConfigService, - private deviceService: DeviceService, - private dbStatusService: DbStatusService, - private dbCompletionService: DbCompletionService, - private progressService: ProgressService, - ) { } - - getFolderStatusInOrder(startIndex: number) { - // Return if there aren't any folders at the index - if (startIndex >= (this.folders.length)) { - this.foldersSubject.next(this.folders); - // this.folderAddedSource.complete(); - return; - } - const folder: Folder = this.folders[startIndex]; - startIndex = startIndex + 1; - - // Folder devices array only has deviceID - // and we want all the device info - this.systemConfigService.getDevices().subscribe( - devices => { - devices.forEach(device => { - // Update any device this folder - // has reference to - folder.devices.forEach((folderDevice, index) => { - if (folderDevice.deviceID === device.deviceID) { - console.log("find device match?", device.name) - folder.devices[index] = device; - - console.log("update?", folder.devices); - } - }); - }); - - // Gather the folder information from the status and - // completion services - this.dbStatusService.getFolderStatus(folder.id).subscribe( - status => { - folder.status = status; - - this.dbCompletionService.getCompletion(StType.Folder, folder.id).subscribe( - c => { - folder.completion = c; - folder.stateType = Folder.getStateType(folder); - folder.state = Folder.stateTypeToString(folder.stateType); - - this.folderAddedSource.next(folder); - this.progressService.addToProgress(1); - - // Now that we have all the folder information - // recursively get the status of the next folder - this.getFolderStatusInOrder(startIndex); - }); - } - ); - } - ) - - } - - /** - * requestFolders() requests each folder and uses db status service to - * set all their statuses and db completion service to find - * completion in order. Updating folderAdded$ and foldersUpdate$ - * observers - */ - requestFolders() { - this.systemConfigService.getFolders().subscribe( - folders => { - this.folders = folders; - - // Synchronously get the status of each folder - this.getFolderStatusInOrder(0); - } - ); - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/services/in-memory-config-data.service.spec.ts b/next-gen-gui/src/app/services/in-memory-config-data.service.spec.ts deleted file mode 100644 index 4650f227e..000000000 --- a/next-gen-gui/src/app/services/in-memory-config-data.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { InMemoryConfigDataService } from './in-memory-config-data.service'; - -describe('InMemoryDataService', () => { - let service: InMemoryConfigDataService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(InMemoryConfigDataService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/in-memory-config-data.service.ts b/next-gen-gui/src/app/services/in-memory-config-data.service.ts deleted file mode 100644 index d44fbb50c..000000000 --- a/next-gen-gui/src/app/services/in-memory-config-data.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@angular/core'; -import { config } from '../mocks/mock-system-config'; -import { dbStatus } from '../mocks/mock-db-status'; -import { connections } from '../mocks/mock-system-connections'; -import { dbCompletion } from '../mocks/mock-db-completion'; -import { systemStatus } from '../mocks/mock-system-status'; - -@Injectable({ - providedIn: 'root' -}) -export class InMemoryConfigDataService { - createDb() { - return { config, dbStatus, connections, dbCompletion, systemStatus }; - } - - constructor() { } -} diff --git a/next-gen-gui/src/app/services/message.service.spec.ts b/next-gen-gui/src/app/services/message.service.spec.ts deleted file mode 100644 index 1db761b5e..000000000 --- a/next-gen-gui/src/app/services/message.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { MessageService } from './message.service'; - -describe('MessageService', () => { - let service: MessageService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(MessageService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/message.service.ts b/next-gen-gui/src/app/services/message.service.ts deleted file mode 100644 index 39dc5d954..000000000 --- a/next-gen-gui/src/app/services/message.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs'; - -@Injectable({ - providedIn: 'root' -}) -export class MessageService { - messages: string[] = []; - private messageAddedSource = new Subject(); - messageAdded$ = this.messageAddedSource.asObservable(); - - add(message: string) { - this.messages.push(message); - this.messageAddedSource.next(message); - } - - clear() { - this.messages = []; - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/services/progress.service.spec.ts b/next-gen-gui/src/app/services/progress.service.spec.ts deleted file mode 100644 index 9b0f96bcc..000000000 --- a/next-gen-gui/src/app/services/progress.service.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { ProgressService } from './progress.service'; -import { stringToKeyValue } from '@angular/flex-layout/extended/typings/style/style-transforms'; - -describe('ProgressService', () => { - let service: ProgressService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ProgressService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('#percentValue should return 0 - 100', () => { - interface iTest { - total: number, - progress: number, - expected: number, - } - const tests: Map = new Map([ - ["default", { total: 0, progress: 0, expected: 0 }], - ["NaN return 0", { total: 0, progress: 100, expected: 0 }], - ["greater than 100 return 100", { total: 10, progress: 100, expected: 100 }], - ["valid", { total: 100, progress: 100, expected: 100 }], - ["valid", { total: 100, progress: 50, expected: 50 }], - ["test floor", { total: 133, progress: 41, expected: 30 }], - ]); - - service = new ProgressService(); - for (let test of tests.values()) { - service.total = test.total; - service.updateProgress(test.progress); - expect(service.percentValue).toBe(test.expected); - } - }); -}); diff --git a/next-gen-gui/src/app/services/progress.service.ts b/next-gen-gui/src/app/services/progress.service.ts deleted file mode 100644 index 953b56810..000000000 --- a/next-gen-gui/src/app/services/progress.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class ProgressService { - private progress: number = 0; - private _total: number = 0; - set total(t: number) { - this._total = t; - } - - get percentValue(): number { - let p: number = Math.floor((this.progress / this._total) * 100); - if (p < 0 || isNaN(p) || p === Infinity) { - p = 0; - } else if (p > 100) { - p = 100; - } - return p; - } - - constructor() { } - - addToProgress(n: number) { - if (n < 0 || isNaN(n) || n === Infinity) { - n = 0; - } - - this.progress += n; - } - - updateProgress(n: number) { - if (n < 0 || isNaN(n) || n === Infinity) { - n = 0 - } else if (n > 100) { - n = 100 - } - - this.progress = n; - } - - isComplete(): boolean { - if (this.progress >= this._total && this.progress > 0 && this._total > 0) { - return true; - } - - return false; - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/services/request-cache.service.spec.ts b/next-gen-gui/src/app/services/request-cache.service.spec.ts deleted file mode 100644 index 4d65caa17..000000000 --- a/next-gen-gui/src/app/services/request-cache.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { RequestCacheService } from './request-cache.service'; - -describe('RequestCacheService', () => { - let service: RequestCacheService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(RequestCacheService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/request-cache.service.ts b/next-gen-gui/src/app/services/request-cache.service.ts deleted file mode 100644 index 2396378b6..000000000 --- a/next-gen-gui/src/app/services/request-cache.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpResponse, HttpRequest } from '@angular/common/http'; - -export interface RequestCacheEntry { - url: string; - response: HttpResponse; - lastRead: number; -} - -const maxAge = 30000; // milliseconds - -@Injectable({ - providedIn: 'root' -}) -export class RequestCacheService { - private cache: Map = new Map(); - - constructor() { } - - get(req: HttpRequest): HttpResponse | undefined { - const url = req.urlWithParams; - const cached = this.cache.get(url); - - if (!cached) { - return undefined; - } - - const isExpired = cached.lastRead < (Date.now() - maxAge); - return isExpired ? undefined : cached.response; - } - - put(req: HttpRequest, response: HttpResponse): void { - const url = req.urlWithParams; - - const entry = { url, response, lastRead: Date.now() }; - this.cache.set(url, entry); - - // Remove expired cache entries - const expired = Date.now() - maxAge; - this.cache.forEach(entry => { - if (entry.lastRead < expired) { - this.cache.delete(entry.url); - } - }); - } - - clearAll(): void { - this.cache = new Map(); - } -} diff --git a/next-gen-gui/src/app/services/system-config.service.spec.ts b/next-gen-gui/src/app/services/system-config.service.spec.ts deleted file mode 100644 index 011706140..000000000 --- a/next-gen-gui/src/app/services/system-config.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { SystemConfigService } from './system-config.service'; -import { HttpClientModule } from '@angular/common/http'; - -describe('SystemConfigService', () => { - let service: SystemConfigService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [SystemConfigService] - }); - service = TestBed.inject(SystemConfigService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/system-config.service.ts b/next-gen-gui/src/app/services/system-config.service.ts deleted file mode 100644 index f18ba79fd..000000000 --- a/next-gen-gui/src/app/services/system-config.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; - -import { Observable, ReplaySubject } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import Folder from '../folder'; -import Device from '../device'; -import { environment } from '../../environments/environment' -import { apiURL } from '../api-utils' -import { ProgressService } from './progress.service'; - -@Injectable({ - providedIn: 'root' -}) -export class SystemConfigService { - private folders: Folder[]; - private devices: Device[]; - private foldersSubject: ReplaySubject = new ReplaySubject(1); - private devicesSubject: ReplaySubject = new ReplaySubject(1); - - private systemConfigUrl = environment.production ? apiURL + 'rest/system/config' : 'api/config'; - private guiConfigUrl = environment.production ? apiURL + 'rest/config/gui' : 'api/config/gui'; - - constructor( - private http: HttpClient, - private progressService: ProgressService, - ) { } - - getSystemConfig(): Observable { - return this.http - .get(this.systemConfigUrl) - .pipe( - map(res => { - this.folders = res['folders']; - this.devices = res['devices']; - - // Set the total for the progress service - this.progressService.total = this.folders.length + this.devices.length; - - this.foldersSubject.next(this.folders); - this.devicesSubject.next(this.devices); - - return res; - }) - ); - } - - getFolders(): Observable { - return this.foldersSubject.asObservable(); - } - - getDevices(): Observable { - return this.devicesSubject.asObservable(); - } - - setGUITheme(theme: String): Observable { - return this.http.patch(this.guiConfigUrl, { theme: theme }) - } -} \ No newline at end of file diff --git a/next-gen-gui/src/app/services/system-connections.service.spec.ts b/next-gen-gui/src/app/services/system-connections.service.spec.ts deleted file mode 100644 index c55566e12..000000000 --- a/next-gen-gui/src/app/services/system-connections.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { SystemConnectionsService } from './system-connections.service'; -import { HttpClientModule } from '@angular/common/http'; - -describe('SystemConnectionsService', () => { - let service: SystemConnectionsService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [SystemConnectionsService] - }); - service = TestBed.inject(SystemConnectionsService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/system-connections.service.ts b/next-gen-gui/src/app/services/system-connections.service.ts deleted file mode 100644 index d196da228..000000000 --- a/next-gen-gui/src/app/services/system-connections.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@angular/core'; -import { environment } from 'src/environments/environment'; -import { apiURL } from '../api-utils'; -import { HttpClient } from '@angular/common/http'; -import { map } from 'rxjs/operators'; -import { Observable } from 'rxjs'; -import { SystemConnections } from '../connections'; - -@Injectable({ - providedIn: 'root' -}) -export class SystemConnectionsService { - private systemConfigUrl = environment.production ? apiURL + 'rest/system/connections' : 'api/connections'; - - constructor(private http: HttpClient) { } - - getSystemConnections(): Observable { - return this.http - .get(this.systemConfigUrl) - .pipe( - map(res => { - return res; - }) - ); - } -} diff --git a/next-gen-gui/src/app/services/system-status.service.spec.ts b/next-gen-gui/src/app/services/system-status.service.spec.ts deleted file mode 100644 index a292daa68..000000000 --- a/next-gen-gui/src/app/services/system-status.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { SystemStatusService } from './system-status.service'; -import { HttpClientModule } from '@angular/common/http'; - -describe('SystemStatusService', () => { - let service: SystemStatusService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientModule], - providers: [SystemStatusService] - }); - service = TestBed.inject(SystemStatusService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/services/system-status.service.ts b/next-gen-gui/src/app/services/system-status.service.ts deleted file mode 100644 index ca9a18bbe..000000000 --- a/next-gen-gui/src/app/services/system-status.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Injectable } from '@angular/core'; -import { environment } from 'src/environments/environment'; -import { apiURL } from '../api-utils'; -import { Observable } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; -import { map } from 'rxjs/operators'; -import { SystemStatus } from '../system-status'; - -@Injectable({ - providedIn: 'root' -}) -export class SystemStatusService { - - private systemStatusUrl = environment.production ? apiURL + 'rest/system/status' : 'api/systemStatus'; - - constructor(private http: HttpClient) { } - - getSystemStatus(): Observable { - return this.http - .get(this.systemStatusUrl) - .pipe( - map(res => { - return res; - }) - ); - } -} diff --git a/next-gen-gui/src/app/style.ts b/next-gen-gui/src/app/style.ts deleted file mode 100644 index 7caaba665..000000000 --- a/next-gen-gui/src/app/style.ts +++ /dev/null @@ -1,11 +0,0 @@ -// material design elevation for all dashboard components -export const cardElevation: string = "mat-elevation-z2"; -export const dataTableElevation: string = "mat-elevation-z0"; - -export const colors: Map = new Map([ - ["blue", "#0891D1"], - ["yellow", "#FFC400"], - ["green", "#56C568"], - ["grey", "#DDDDDD"], - ["red", "#EB5757"] -]); \ No newline at end of file diff --git a/next-gen-gui/src/app/system-status.ts b/next-gen-gui/src/app/system-status.ts deleted file mode 100644 index 5f4be45ed..000000000 --- a/next-gen-gui/src/app/system-status.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface SystemStatus { - alloc: number; - connectionServiceStatus: any; - cpuPercent: number; // allows returns 0 - discoveryEnabled: boolean; - discoveryErrors: any; - discoveryMethods: number; - goroutines: number; - lastDialStatus: any; - myID: string; - pathSeparator: string; - startTime: string; - sys: number; - themes: string[]; - tilde: string; - uptime: number; -} \ No newline at end of file diff --git a/next-gen-gui/src/app/trim.pipe.spec.ts b/next-gen-gui/src/app/trim.pipe.spec.ts deleted file mode 100644 index 200699000..000000000 --- a/next-gen-gui/src/app/trim.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TrimPipe } from './trim.pipe'; - -describe('TrimPipe', () => { - it('create an instance', () => { - const pipe = new TrimPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/next-gen-gui/src/app/trim.pipe.ts b/next-gen-gui/src/app/trim.pipe.ts deleted file mode 100644 index d922492cf..000000000 --- a/next-gen-gui/src/app/trim.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({ - name: 'trim' -}) -export class TrimPipe implements PipeTransform { - - transform(value: string, ...args: string[]): string { - return value.trim(); - } - -} diff --git a/next-gen-gui/src/app/type.ts b/next-gen-gui/src/app/type.ts deleted file mode 100644 index 408bfb869..000000000 --- a/next-gen-gui/src/app/type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum StType { - Folder = 1, - Device, -} \ No newline at end of file diff --git a/next-gen-gui/src/assets/.gitkeep b/next-gen-gui/src/assets/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/next-gen-gui/src/assets/logo-horizontal.svg b/next-gen-gui/src/assets/logo-horizontal.svg deleted file mode 100644 index de8850f53..000000000 --- a/next-gen-gui/src/assets/logo-horizontal.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/next-gen-gui/src/environments/environment.prod.ts b/next-gen-gui/src/environments/environment.prod.ts deleted file mode 100644 index 3612073bc..000000000 --- a/next-gen-gui/src/environments/environment.prod.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const environment = { - production: true -}; diff --git a/next-gen-gui/src/environments/environment.ts b/next-gen-gui/src/environments/environment.ts deleted file mode 100644 index 7b4f817ad..000000000 --- a/next-gen-gui/src/environments/environment.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file can be replaced during build by using the `fileReplacements` array. -// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. -// The list of file replacements can be found in `angular.json`. - -export const environment = { - production: false -}; - -/* - * For easier debugging in development mode, you can import the following file - * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. - * - * This import should be commented out in production mode because it will have a negative impact - * on performance if an error is thrown. - */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/next-gen-gui/src/favicon.ico b/next-gen-gui/src/favicon.ico deleted file mode 100644 index 997406ad22c29aae95893fb3d666c30258a09537..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 948 zcmV;l155mgP)CBYU7IjCFmI-B}4sMJt3^s9NVg!P0 z6hDQy(L`XWMkB@zOLgN$4KYz;j0zZxq9KKdpZE#5@k0crP^5f9KO};h)ZDQ%ybhht z%t9#h|nu0K(bJ ztIkhEr!*UyrZWQ1k2+YkGqDi8Z<|mIN&$kzpKl{cNP=OQzXHz>vn+c)F)zO|Bou>E z2|-d_=qY#Y+yOu1a}XI?cU}%04)zz%anD(XZC{#~WreV!a$7k2Ug`?&CUEc0EtrkZ zL49MB)h!_K{H(*l_93D5tO0;BUnvYlo+;yss%n^&qjt6fZOa+}+FDO(~2>G z2dx@=JZ?DHP^;b7*Y1as5^uphBsh*s*z&MBd?e@I>-9kU>63PjP&^#5YTOb&x^6Cf z?674rmSHB5Fk!{Gv7rv!?qX#ei_L(XtwVqLX3L}$MI|kJ*w(rhx~tc&L&xP#?cQow zX_|gx$wMr3pRZIIr_;;O|8fAjd;1`nOeu5K(pCu7>^3E&D2OBBq?sYa(%S?GwG&_0-s%_v$L@R!5H_fc)lOb9ZoOO#p`Nn`KU z3LTTBtjwo`7(HA6 z7gmO$yTR!5L>Bsg!X8616{JUngg_@&85%>W=mChTR;x4`P=?PJ~oPuy5 zU-L`C@_!34D21{fD~Y8NVnR3t;aqZI3fIhmgmx}$oc-dKDC6Ap$Gy>a!`A*x2L1v0 WcZ@i?LyX}70000 - - - - - TechUi - - - - - - - -
-
-
- - - - \ No newline at end of file diff --git a/next-gen-gui/src/main.ts b/next-gen-gui/src/main.ts deleted file mode 100644 index c7b673cf4..000000000 --- a/next-gen-gui/src/main.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); diff --git a/next-gen-gui/src/polyfills.ts b/next-gen-gui/src/polyfills.ts deleted file mode 100644 index 03711e5d9..000000000 --- a/next-gen-gui/src/polyfills.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * - */ - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/next-gen-gui/src/styles.scss b/next-gen-gui/src/styles.scss deleted file mode 100644 index 15bed241b..000000000 --- a/next-gen-gui/src/styles.scss +++ /dev/null @@ -1,140 +0,0 @@ -// Custom Theming for Angular Material -// For more information: https://material.angular.io/guide/theming -@import '~@angular/material/theming'; -@import './app/card/card.component.scss'; -@import './app/charts/chart-item/chart-item.component.scss'; -@import './app/lists/status-list/status-list.component.scss'; - - -// Custom typography -$tech-ui-typography: mat-typography-config( - $font-family: '"Lucida Console", Monaco, monospace', - $display-4: mat-typography-level(112px, 112px, 300), - $display-3: mat-typography-level(56px, 56px, 400), - $display-2: mat-typography-level(45px, 48px, 400), - $display-1: mat-typography-level(34px, 40px, 400), - $headline: mat-typography-level(24px, 32px, 400), - $title: mat-typography-level(20px, 32px, 500), - $subheading-2: mat-typography-level(16px, 28px, 400), - $subheading-1: mat-typography-level(15px, 24px, 400), - $body-2: mat-typography-level(14px, 24px, 500), - $body-1: mat-typography-level(14px, 20px, 400), - $caption: mat-typography-level(12px, 20px, 400), - $button: mat-typography-level(14px, 14px, 500), - // Line-height must be unit-less fraction of the font-size. - $input: mat-typography-level(16px, 1.25, 400) -); - -// Include the common styles for Angular Material. We include this here so that you only -// have to load a single css file for Angular Material in your app. -// Be sure that you only ever include this mixin once! -@include mat-core($tech-ui-typography); - -// Define the palettes for your theme using the Material Design palettes available in palette.scss -// (imported above). For each palette, you can optionally specify a default, lighter, and darker -// hue. Available color palettes: https://material.io/design/color/ -$tech-ui-blue: ( - 50: #a5e0fc, - 100: #8fd5f7, - 200: #74c7ef, - 300: #51beed, - 400: #3eafe3, - 500: #0891D1, - 600: #0891D1, - 700: #0891D1, - 800: #0891D1, - 900: #0891D1, - A100: #0891D1, - A200: #0891D1, - A400: #0891D1, - A700: #0891D1, - contrast: ( - 50: $dark-primary-text, - 100: $dark-primary-text, - 200: $dark-primary-text, - 300: $dark-primary-text, - 400: $dark-primary-text, - 500: $light-primary-text, - 600: $light-primary-text, - 700: $light-primary-text, - 800: $light-primary-text, - 900: $light-primary-text, - A100: $dark-primary-text, - A200: $light-primary-text, - A400: $light-primary-text, - A700: $light-primary-text, - ) -); - -$tech-ui-primary: mat-palette($tech-ui-blue); -$tech-ui-accent: mat-palette($tech-ui-blue, A200, A100, A400); - -// The warn palette is optional (defaults to red). -$tech-ui-warn: mat-palette($mat-red); - -// Create the theme object (a Sass map containing all of the palettes). -$tech-ui-theme: mat-light-theme($tech-ui-primary, $tech-ui-accent, $tech-ui-warn); -$tech-ui-dark-theme: mat-dark-theme($tech-ui-primary, $tech-ui-accent, $tech-ui-warn); - -// Include theme styles for core and each component used in your app. -// Alternatively, you can import and @include the theme mixins for each component -// that you are using. -@include angular-material-theme($tech-ui-theme); -@include tui-card-theme($tech-ui-theme); -@include chart-item-theme($tech-ui-theme); - -html, body { - height: 100%; - background-color: #eeeeee; - font-size: mat-font-size($tech-ui-typography, body-1); - font-family: mat-font-family($tech-ui-typography); - line-height: mat-line-height($tech-ui-typography, body-1); - color: #303030; -} - -a { - text-decoration: underline; - color: #303030; -} - -@media (prefers-color-scheme: dark) { - @include angular-material-theme($tech-ui-dark-theme); - @include tui-card-theme($tech-ui-dark-theme); - @include chart-item-theme($tech-ui-dark-theme); - - html, body { - background-color: #303030; - color: white; - } - - a { - color: white; - } -} - -.status-list .tui-card .tui-card-content { - padding-top: 0; -} - -// Chart.js styles -#chartjs-tooltip { - /* max z-index value */ - z-index: 2147483647; - opacity: 0; - position: absolute; - background: rgba(0, 0, 0, .7); - color: white; - border-radius: 3px; - -webkit-transition: opacity .3s ease; - transition: opacity .3s ease; - pointer-events: none; - -webkit-transform: translate(-50%, 0); - transform: translate(-50%, 0); -} - -.chartjs-tooltip-key { - display: inline-block; - width: 10px; - height: 10px; - margin-right: 10px; -} \ No newline at end of file diff --git a/next-gen-gui/src/test.ts b/next-gen-gui/src/test.ts deleted file mode 100644 index 50193eb0f..000000000 --- a/next-gen-gui/src/test.ts +++ /dev/null @@ -1,25 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/dist/zone-testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: { - context(path: string, deep?: boolean, filter?: RegExp): { - keys(): string[]; - (id: string): T; - }; -}; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/next-gen-gui/tsconfig.app.json b/next-gen-gui/tsconfig.app.json deleted file mode 100644 index f758d9820..000000000 --- a/next-gen-gui/tsconfig.app.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/next-gen-gui/tsconfig.json b/next-gen-gui/tsconfig.json deleted file mode 100644 index 8c4ef3bba..000000000 --- a/next-gen-gui/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "module": "esnext", - "moduleResolution": "node", - "importHelpers": true, - "target": "es2015", - "lib": [ - "es2018", - "dom" - ] - }, - "angularCompilerOptions": { - "fullTemplateTypeCheck": true, - "strictInjectionParameters": true - } -} diff --git a/next-gen-gui/tsconfig.spec.json b/next-gen-gui/tsconfig.spec.json deleted file mode 100644 index 6400fde7d..000000000 --- a/next-gen-gui/tsconfig.spec.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": [ - "jasmine", - "node" - ] - }, - "files": [ - "src/test.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] -} diff --git a/next-gen-gui/tslint.json b/next-gen-gui/tslint.json deleted file mode 100644 index c8d70f152..000000000 --- a/next-gen-gui/tslint.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "extends": "tslint:recommended", - "rules": { - "array-type": false, - "arrow-parens": false, - "deprecation": { - "severity": "warning" - }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "interface-name": false, - "max-classes-per-file": false, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-consecutive-blank-lines": false, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-empty": false, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-var-requires": false, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "object-literal-sort-keys": false, - "ordered-imports": false, - "quotemark": [ - true, - "single" - ], - "trailing-comma": false, - "no-conflicting-lifecycle": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-inputs-metadata-property": true, - "no-output-native": true, - "no-output-on-prefix": true, - "no-output-rename": true, - "no-outputs-metadata-property": true, - "template-banana-in-box": true, - "template-no-negated-async": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true - }, - "rulesDirectory": [ - "codelyzer" - ] -} \ No newline at end of file From b1c8f88a448166a304a46528c7188d05b8209a6f Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 29 Mar 2025 12:21:10 +0000 Subject: [PATCH 02/62] chore: remove weak hashing which does not pull its weight (#10005) We've had weak/rolling hashing in the code for quite a while. It was a popular request for a while, based on the belief that rsync does this and we should too. However, the benefit is quite small; we save on average about 0.8% of transferred blocks over the population as a whole: Screenshot 2025-03-28 at 17 09 02 This would be fine if the cost was comparably low, however the downside of attempting rolling hash matching is that we (by default) do a complete file read on the destination in order to look for matches before we starting pulling blocks for the file. For any larger file this means a sometimes long, I/O-intensive pause before the file starts syncing, for usually no benefit. I propose we simply rip off the bandaid and save the effort. --- cmd/dev/stfileinfo/main.go | 2 +- cmd/syncthing/decrypt/decrypt.go | 2 +- go.mod | 1 - go.sum | 2 - internal/gen/bep/bep.pb.go | 309 +++++++++++------------- lib/api/testdata/config/config.xml | 2 - lib/config/config_test.go | 14 +- lib/config/folderconfiguration.go | 5 - lib/config/testdata/overridenvalues.xml | 1 - lib/model/fakeconns_test.go | 2 +- lib/model/folder_recvonly_test.go | 2 +- lib/model/folder_sendrecv.go | 156 +++--------- lib/model/folder_sendrecv_test.go | 133 +--------- lib/model/metrics.go | 12 +- lib/model/mocks/model.go | 54 ++--- lib/model/model.go | 22 +- lib/model/model_test.go | 8 +- lib/model/requests_test.go | 12 +- lib/model/sharedpullerstate.go | 9 - lib/protocol/bep_fileinfo.go | 24 +- lib/protocol/bep_request_response.go | 3 - lib/protocol/common_test.go | 2 - lib/scanner/blockqueue.go | 6 +- lib/scanner/blocks.go | 33 +-- lib/scanner/blocks_test.go | 111 +-------- lib/scanner/walk_test.go | 4 +- lib/syncthing/internals.go | 2 +- lib/syncthing/syncthing.go | 2 +- lib/ur/contract/contract.go | 18 +- lib/ur/usage_report.go | 17 +- lib/weakhash/benchmark_test.go | 151 ------------ lib/weakhash/weakhash.go | 118 --------- lib/weakhash/weakhash_test.go | 68 ------ proto/bep/bep.proto | 4 +- test/h1/config.xml | 2 - test/h2/config.xml | 2 - test/h3/config.xml | 2 - test/h4/config.xml | 1 - 38 files changed, 288 insertions(+), 1030 deletions(-) delete mode 100644 lib/weakhash/benchmark_test.go delete mode 100644 lib/weakhash/weakhash.go delete mode 100644 lib/weakhash/weakhash_test.go diff --git a/cmd/dev/stfileinfo/main.go b/cmd/dev/stfileinfo/main.go index 805a1294b..4c1e2aedb 100644 --- a/cmd/dev/stfileinfo/main.go +++ b/cmd/dev/stfileinfo/main.go @@ -72,7 +72,7 @@ func main() { if *standardBlocks || blockSize < protocol.MinBlockSize { blockSize = protocol.BlockSize(fi.Size()) } - bs, err := scanner.Blocks(context.TODO(), fd, blockSize, fi.Size(), nil, true) + bs, err := scanner.Blocks(context.TODO(), fd, blockSize, fi.Size(), nil) if err != nil { log.Fatal(err) } diff --git a/cmd/syncthing/decrypt/decrypt.go b/cmd/syncthing/decrypt/decrypt.go index ed162964c..4545e119c 100644 --- a/cmd/syncthing/decrypt/decrypt.go +++ b/cmd/syncthing/decrypt/decrypt.go @@ -238,7 +238,7 @@ func (c *CLI) decryptFile(encFi *protocol.FileInfo, plainFi *protocol.FileInfo, } // Verify the hash against the plaintext block info - if !scanner.Validate(dec, plainBlock.Hash, 0) { + if !scanner.Validate(dec, plainBlock.Hash) { // The block decrypted correctly but fails the hash check. This // is odd and unexpected, but it it's still a valid block from // the source. The file might have changed while we pulled it? diff --git a/go.mod b/go.mod index f2ce0ee4d..4ebd75662 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/calmh/incontainer v1.0.0 github.com/calmh/xdr v1.2.0 github.com/ccding/go-stun v0.1.5 - github.com/chmduquesne/rollinghash v4.0.0+incompatible github.com/d4l3k/messagediff v1.2.1 github.com/getsentry/raven-go v0.2.0 github.com/go-ldap/ldap/v3 v3.4.10 diff --git a/go.sum b/go.sum index bd69955ed..2d7cbc8ae 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,6 @@ github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chmduquesne/rollinghash v4.0.0+incompatible h1:hnREQO+DXjqIw3rUTzWN7/+Dpw+N5Um8zpKV0JOEgbo= -github.com/chmduquesne/rollinghash v4.0.0+incompatible/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= diff --git a/internal/gen/bep/bep.pb.go b/internal/gen/bep/bep.pb.go index 686fc6c04..11c0badc3 100644 --- a/internal/gen/bep/bep.pb.go +++ b/internal/gen/bep/bep.pb.go @@ -1093,10 +1093,9 @@ type BlockInfo struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Hash []byte `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty"` - Offset int64 `protobuf:"varint,1,opt,name=offset,proto3" json:"offset,omitempty"` - Size int32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` - WeakHash uint32 `protobuf:"varint,4,opt,name=weak_hash,json=weakHash,proto3" json:"weak_hash,omitempty"` + Hash []byte `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty"` + Offset int64 `protobuf:"varint,1,opt,name=offset,proto3" json:"offset,omitempty"` + Size int32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` } func (x *BlockInfo) Reset() { @@ -1150,13 +1149,6 @@ func (x *BlockInfo) GetSize() int32 { return 0 } -func (x *BlockInfo) GetWeakHash() uint32 { - if x != nil { - return x.WeakHash - } - return 0 -} - type Vector struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1578,7 +1570,6 @@ type Request struct { Size int32 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"` Hash []byte `protobuf:"bytes,6,opt,name=hash,proto3" json:"hash,omitempty"` FromTemporary bool `protobuf:"varint,7,opt,name=from_temporary,json=fromTemporary,proto3" json:"from_temporary,omitempty"` - WeakHash uint32 `protobuf:"varint,8,opt,name=weak_hash,json=weakHash,proto3" json:"weak_hash,omitempty"` BlockNo int32 `protobuf:"varint,9,opt,name=block_no,json=blockNo,proto3" json:"block_no,omitempty"` } @@ -1661,13 +1652,6 @@ func (x *Request) GetFromTemporary() bool { return false } -func (x *Request) GetWeakHash() uint32 { - if x != nil { - return x.WeakHash - } - return 0 -} - func (x *Request) GetBlockNo() int32 { if x != nil { return x.BlockNo @@ -2079,158 +2063,155 @@ var file_bep_bep_proto_rawDesc = []byte{ 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x6f, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x68, 0x0a, 0x09, 0x42, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x65, 0x61, 0x6b, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x77, 0x65, 0x61, - 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x32, 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, - 0x28, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0c, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, - 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x07, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xfd, 0x01, 0x0a, 0x0c, 0x50, - 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x04, 0x75, - 0x6e, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x65, 0x70, 0x2e, - 0x55, 0x6e, 0x69, 0x78, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x78, 0x12, 0x2a, - 0x0a, 0x07, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x61, 0x74, - 0x61, 0x52, 0x07, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x69, - 0x6e, 0x75, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, - 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x75, 0x78, - 0x12, 0x26, 0x0a, 0x06, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, - 0x52, 0x06, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x12, 0x28, 0x0a, 0x07, 0x66, 0x72, 0x65, 0x65, - 0x62, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, - 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x66, 0x72, 0x65, 0x65, 0x62, - 0x73, 0x64, 0x12, 0x26, 0x0a, 0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x32, + 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x62, 0x65, 0x70, + 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, + 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0xfd, 0x01, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x55, 0x6e, 0x69, 0x78, 0x44, 0x61, 0x74, + 0x61, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x78, 0x12, 0x2a, 0x0a, 0x07, 0x77, 0x69, 0x6e, 0x64, 0x6f, + 0x77, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x57, + 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x77, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x73, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x22, 0x6c, 0x0a, 0x08, 0x55, 0x6e, - 0x69, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, 0x65, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x03, 0x67, 0x69, 0x64, 0x22, 0x52, 0x0a, 0x0b, 0x57, 0x69, 0x6e, 0x64, - 0x6f, 0x77, 0x73, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, - 0x69, 0x73, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x73, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x2f, 0x0a, 0x09, - 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x06, 0x78, 0x61, 0x74, - 0x74, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x62, 0x65, 0x70, 0x2e, - 0x58, 0x61, 0x74, 0x74, 0x72, 0x52, 0x06, 0x78, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22, 0x31, 0x0a, - 0x05, 0x58, 0x61, 0x74, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x22, 0xe4, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, - 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x72, 0x6f, 0x6d, - 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x12, - 0x1b, 0x0a, 0x09, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x08, 0x77, 0x65, 0x61, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x19, 0x0a, 0x08, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x22, 0x52, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x65, 0x0a, 0x10, 0x44, - 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x46, + 0x74, 0x61, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x12, 0x26, 0x0a, 0x06, 0x64, 0x61, 0x72, + 0x77, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, + 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x06, 0x64, 0x61, 0x72, 0x77, 0x69, + 0x6e, 0x12, 0x28, 0x0a, 0x07, 0x66, 0x72, 0x65, 0x65, 0x62, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x52, 0x07, 0x66, 0x72, 0x65, 0x65, 0x62, 0x73, 0x64, 0x12, 0x26, 0x0a, 0x06, 0x6e, + 0x65, 0x74, 0x62, 0x73, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, + 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x06, 0x6e, 0x65, 0x74, + 0x62, 0x73, 0x64, 0x22, 0x6c, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, + 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x67, 0x69, + 0x64, 0x22, 0x52, 0x0a, 0x0b, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x61, 0x74, 0x61, + 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x73, 0x5f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x73, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x2f, 0x0a, 0x09, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x22, 0x0a, 0x06, 0x78, 0x61, 0x74, 0x74, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x52, 0x06, + 0x78, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22, 0x31, 0x0a, 0x05, 0x58, 0x61, 0x74, 0x74, 0x72, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xcd, 0x01, 0x0a, 0x07, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, + 0x61, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x54, + 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x6e, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x4e, 0x6f, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x22, 0x52, 0x0a, 0x08, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x65, 0x0a, + 0x10, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x07, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x65, 0x70, + 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, + 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x1a, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x1a, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x46, 0x69, 0x6c, - 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, - 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x62, - 0x65, 0x70, 0x2e, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x00, 0x52, 0x0c, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, - 0x6e, 0x67, 0x22, 0x1f, 0x0a, 0x05, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x2a, 0xed, 0x01, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, - 0x49, 0x47, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, - 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x44, - 0x45, 0x58, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x4d, - 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, - 0x45, 0x53, 0x54, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x10, 0x04, - 0x12, 0x22, 0x0a, 0x1e, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, - 0x53, 0x53, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x4d, - 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, - 0x45, 0x10, 0x07, 0x2a, 0x4f, 0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, - 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x53, + 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, + 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x00, 0x52, + 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x1d, 0x0a, + 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x06, 0x0a, 0x04, + 0x50, 0x69, 0x6e, 0x67, 0x22, 0x1f, 0x0a, 0x05, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2a, 0xed, 0x01, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x4f, + 0x4e, 0x46, 0x49, 0x47, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, + 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x01, 0x12, 0x1d, + 0x0a, 0x19, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, + 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, + 0x14, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, + 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x53, 0x53, 0x41, + 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, + 0x10, 0x04, 0x12, 0x22, 0x0a, 0x1e, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, + 0x52, 0x45, 0x53, 0x53, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, + 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x16, 0x0a, + 0x12, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, + 0x4f, 0x53, 0x45, 0x10, 0x07, 0x2a, 0x4f, 0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x4d, + 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, + 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, - 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x45, 0x53, 0x53, 0x41, - 0x47, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, - 0x5a, 0x34, 0x10, 0x01, 0x2a, 0x56, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, - 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, 0x12, 0x15, 0x0a, - 0x11, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x45, 0x56, - 0x45, 0x52, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, - 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4c, 0x57, 0x41, 0x59, 0x53, 0x10, 0x02, 0x2a, 0xb0, 0x01, 0x0a, - 0x0c, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, - 0x13, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x46, 0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, - 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x4f, - 0x52, 0x59, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1b, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, - 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x46, - 0x49, 0x4c, 0x45, 0x10, 0x02, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x28, 0x0a, 0x20, 0x46, 0x49, 0x4c, - 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, - 0x49, 0x4e, 0x4b, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x03, 0x1a, - 0x02, 0x08, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x10, 0x04, 0x2a, - 0x76, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x13, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, - 0x4f, 0x44, 0x45, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x49, 0x43, 0x10, 0x01, 0x12, 0x1b, 0x0a, - 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x53, - 0x55, 0x43, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, - 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x7e, 0x0a, 0x1e, 0x46, 0x69, 0x6c, 0x65, 0x44, - 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x29, 0x46, 0x49, 0x4c, - 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, - 0x45, 0x53, 0x53, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x2d, 0x0a, 0x29, 0x46, 0x49, 0x4c, 0x45, - 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, - 0x53, 0x53, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, - 0x4f, 0x52, 0x47, 0x45, 0x54, 0x10, 0x01, 0x42, 0x70, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x2e, 0x62, - 0x65, 0x70, 0x42, 0x08, 0x42, 0x65, 0x70, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, - 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x62, 0x65, 0x70, 0xa2, - 0x02, 0x03, 0x42, 0x58, 0x58, 0xaa, 0x02, 0x03, 0x42, 0x65, 0x70, 0xca, 0x02, 0x03, 0x42, 0x65, - 0x70, 0xe2, 0x02, 0x0f, 0x42, 0x65, 0x70, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0xea, 0x02, 0x03, 0x42, 0x65, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x5f, 0x4c, 0x5a, 0x34, 0x10, 0x01, 0x2a, 0x56, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, + 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, 0x12, + 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, + 0x45, 0x56, 0x45, 0x52, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, + 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4c, 0x57, 0x41, 0x59, 0x53, 0x10, 0x02, 0x2a, 0xb0, + 0x01, 0x0a, 0x0c, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x17, 0x0a, 0x13, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x49, 0x4c, 0x45, + 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, + 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1b, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, + 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, + 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x02, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x28, 0x0a, 0x20, 0x46, + 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, + 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x59, 0x10, + 0x03, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, + 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x10, + 0x04, 0x2a, 0x76, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, + 0x0a, 0x13, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x49, 0x43, 0x10, 0x01, 0x12, + 0x1b, 0x0a, 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, + 0x5f, 0x53, 0x55, 0x43, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, + 0x49, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x7e, 0x0a, 0x1e, 0x46, 0x69, 0x6c, + 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x29, 0x46, + 0x49, 0x4c, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, + 0x47, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x00, 0x12, 0x2d, 0x0a, 0x29, 0x46, 0x49, + 0x4c, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, + 0x52, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x46, 0x4f, 0x52, 0x47, 0x45, 0x54, 0x10, 0x01, 0x42, 0x70, 0x0a, 0x07, 0x63, 0x6f, 0x6d, + 0x2e, 0x62, 0x65, 0x70, 0x42, 0x08, 0x42, 0x65, 0x70, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x79, 0x6e, + 0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x62, 0x65, + 0x70, 0xa2, 0x02, 0x03, 0x42, 0x58, 0x58, 0xaa, 0x02, 0x03, 0x42, 0x65, 0x70, 0xca, 0x02, 0x03, + 0x42, 0x65, 0x70, 0xe2, 0x02, 0x0f, 0x42, 0x65, 0x70, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x03, 0x42, 0x65, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/lib/api/testdata/config/config.xml b/lib/api/testdata/config/config.xml index 5bd47cc87..3f547e0e9 100644 --- a/lib/api/testdata/config/config.xml +++ b/lib/api/testdata/config/config.xml @@ -18,7 +18,6 @@ false false false - 25 .stfolder true
@@ -39,7 +38,6 @@ false false false - 25 .stfolder true
diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 14ef53308..6359f42ab 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -114,10 +114,9 @@ func TestDefaultValues(t *testing.T) { CleanupIntervalS: 3600, Params: map[string]string{}, }, - MaxConflicts: 10, - WeakHashThresholdPct: 25, - MarkerName: ".stfolder", - MaxConcurrentWrites: 2, + MaxConflicts: 10, + MarkerName: ".stfolder", + MaxConcurrentWrites: 2, XattrFilter: XattrFilter{ Entries: []XattrFilterEntry{}, MaxSingleEntrySize: 1024, @@ -190,10 +189,9 @@ func TestDeviceConfig(t *testing.T) { Versioning: VersioningConfiguration{ Params: map[string]string{}, }, - WeakHashThresholdPct: 25, - MarkerName: DefaultMarkerName, - JunctionsAsDirs: true, - MaxConcurrentWrites: maxConcurrentWritesDefault, + MarkerName: DefaultMarkerName, + JunctionsAsDirs: true, + MaxConcurrentWrites: maxConcurrentWritesDefault, XattrFilter: XattrFilter{ Entries: []XattrFilterEntry{}, }, diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index d8ec913e8..76c3b3e39 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -70,7 +70,6 @@ type FolderConfiguration struct { DisableSparseFiles bool `json:"disableSparseFiles" xml:"disableSparseFiles"` DisableTempIndexes bool `json:"disableTempIndexes" xml:"disableTempIndexes"` Paused bool `json:"paused" xml:"paused"` - WeakHashThresholdPct int `json:"weakHashThresholdPct" xml:"weakHashThresholdPct"` MarkerName string `json:"markerName" xml:"markerName"` CopyOwnershipFromParent bool `json:"copyOwnershipFromParent" xml:"copyOwnershipFromParent"` RawModTimeWindowS int `json:"modTimeWindowS" xml:"modTimeWindowS"` @@ -311,10 +310,6 @@ func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices ma f.Versioning.CleanupIntervalS = 0 } - if f.WeakHashThresholdPct == 0 { - f.WeakHashThresholdPct = 25 - } - if f.MarkerName == "" { f.MarkerName = DefaultMarkerName } diff --git a/lib/config/testdata/overridenvalues.xml b/lib/config/testdata/overridenvalues.xml index 9503607e2..1c142a59f 100644 --- a/lib/config/testdata/overridenvalues.xml +++ b/lib/config/testdata/overridenvalues.xml @@ -70,7 +70,6 @@ false false false - 25 .stfolder false 0 diff --git a/lib/model/fakeconns_test.go b/lib/model/fakeconns_test.go index 0d929bb82..831198276 100644 --- a/lib/model/fakeconns_test.go +++ b/lib/model/fakeconns_test.go @@ -75,7 +75,7 @@ func (f *fakeConnection) DownloadProgress(_ context.Context, dp *protocol.Downlo func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol.FileInfoType, data []byte, version protocol.Vector, localFlags uint32) { blockSize := protocol.BlockSize(int64(len(data))) - blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), blockSize, int64(len(data)), nil, true) + blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), blockSize, int64(len(data)), nil) file := protocol.FileInfo{ Name: name, diff --git a/lib/model/folder_recvonly_test.go b/lib/model/folder_recvonly_test.go index 534677ed2..729b68ebb 100644 --- a/lib/model/folder_recvonly_test.go +++ b/lib/model/folder_recvonly_test.go @@ -560,7 +560,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi if err != nil { t.Fatal(err) } - blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil, true) + blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil) knownFiles := []protocol.FileInfo{ { Name: "knownDir", diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 73e3ef184..8f6544126 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -31,7 +31,6 @@ import ( "github.com/syncthing/syncthing/lib/semaphore" "github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/versioner" - "github.com/syncthing/syncthing/lib/weakhash" ) var ( @@ -1136,12 +1135,12 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot func (f *sendReceiveFolder) reuseBlocks(blocks []protocol.BlockInfo, reused []int, file protocol.FileInfo, tempName string) ([]protocol.BlockInfo, []int) { // Check for an old temporary file which might have some blocks we could // reuse. - tempBlocks, err := scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil, false) + tempBlocks, err := scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil) if err != nil { var caseErr *fs.ErrCaseConflict if errors.As(err, &caseErr) { if rerr := f.mtimefs.Rename(caseErr.Real, tempName); rerr == nil { - tempBlocks, err = scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil, false) + tempBlocks, err = scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil) } } } @@ -1310,8 +1309,6 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch f.model.progressEmitter.Register(state.sharedPullerState) } - weakHashFinder, file := f.initWeakHashFinder(state) - blocks: for _, block := range state.blocks { select { @@ -1336,75 +1333,49 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) - var found bool - if f.Type != config.FolderTypeReceiveEncrypted { - found, err = weakHashFinder.Iterate(block.WeakHash, buf, func(offset int64) bool { - if f.verifyBuffer(buf, block) != nil { - return true - } - - err = f.limitedWriteAt(dstFd, buf, block.Offset) - if err != nil { - state.fail(fmt.Errorf("dst write: %w", err)) - } - if offset == block.Offset { - state.copiedFromOrigin(block.Size) - } else { - state.copiedFromOriginShifted(block.Size) - } - - return false - }) + found := f.model.finder.Iterate(folders, block.Hash, func(folder, path string, index int32) bool { + ffs := folderFilesystems[folder] + fd, err := ffs.Open(path) if err != nil { - l.Debugln("weak hasher iter", err) + return false } - } + defer fd.Close() - if !found { - found = f.model.finder.Iterate(folders, block.Hash, func(folder, path string, index int32) bool { - ffs := folderFilesystems[folder] - fd, err := ffs.Open(path) - if err != nil { + srcOffset := int64(state.file.BlockSize()) * int64(index) + _, err = fd.ReadAt(buf, srcOffset) + if err != nil { + return false + } + + // Hash is not SHA256 as it's an encrypted hash token. In that + // case we can't verify the block integrity so we'll take it on + // trust. (The other side can and will verify.) + if f.Type != config.FolderTypeReceiveEncrypted { + if err := f.verifyBuffer(buf, block); err != nil { + l.Debugln("Finder failed to verify buffer", err) return false } - defer fd.Close() + } - srcOffset := int64(state.file.BlockSize()) * int64(index) - _, err = fd.ReadAt(buf, srcOffset) - if err != nil { - return false - } - - // Hash is not SHA256 as it's an encrypted hash token. In that - // case we can't verify the block integrity so we'll take it on - // trust. (The other side can and will verify.) - if f.Type != config.FolderTypeReceiveEncrypted { - if err := f.verifyBuffer(buf, block); err != nil { - l.Debugln("Finder failed to verify buffer", err) - return false - } - } - - if f.CopyRangeMethod != config.CopyRangeMethodStandard { - err = f.withLimiter(func() error { - dstFd.mut.Lock() - defer dstFd.mut.Unlock() - return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, srcOffset, block.Offset, int64(block.Size)) - }) - } else { - err = f.limitedWriteAt(dstFd, buf, block.Offset) - } - if err != nil { - state.fail(fmt.Errorf("dst write: %w", err)) - } - if path == state.file.Name { - state.copiedFromOrigin(block.Size) - } else { - state.copiedFromElsewhere(block.Size) - } - return true - }) - } + if f.CopyRangeMethod != config.CopyRangeMethodStandard { + err = f.withLimiter(func() error { + dstFd.mut.Lock() + defer dstFd.mut.Unlock() + return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, srcOffset, block.Offset, int64(block.Size)) + }) + } else { + err = f.limitedWriteAt(dstFd, buf, block.Offset) + } + if err != nil { + state.fail(fmt.Errorf("dst write: %w", err)) + } + if path == state.file.Name { + state.copiedFromOrigin(block.Size) + } else { + state.copiedFromElsewhere(block.Size) + } + return true + }) if state.failed() != nil { break @@ -1421,58 +1392,11 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch state.copyDone(block) } } - if file != nil { - // os.File used to return invalid argument if nil. - // fs.File panics as it's an interface. - file.Close() - } out <- state.sharedPullerState } } -func (f *sendReceiveFolder) initWeakHashFinder(state copyBlocksState) (*weakhash.Finder, fs.File) { - if f.Type == config.FolderTypeReceiveEncrypted { - l.Debugln("not weak hashing due to folder type", f.Type) - return nil, nil - } - - blocksPercentChanged := 0 - if tot := len(state.file.Blocks); tot > 0 { - blocksPercentChanged = (tot - state.have) * 100 / tot - } - - if blocksPercentChanged < f.WeakHashThresholdPct { - l.Debugf("not weak hashing %s. not enough changed %.02f < %d", state.file.Name, blocksPercentChanged, f.WeakHashThresholdPct) - return nil, nil - } - - hashesToFind := make([]uint32, 0, len(state.blocks)) - for _, block := range state.blocks { - if block.WeakHash != 0 { - hashesToFind = append(hashesToFind, block.WeakHash) - } - } - - if len(hashesToFind) == 0 { - l.Debugf("not weak hashing %s. file did not contain any weak hashes", state.file.Name) - return nil, nil - } - - file, err := f.mtimefs.Open(state.file.Name) - if err != nil { - l.Debugln("weak hasher", err) - return nil, nil - } - - weakHashFinder, err := weakhash.NewFinder(f.ctx, file, state.file.BlockSize(), hashesToFind) - if err != nil { - l.Debugln("weak hasher", err) - return nil, file - } - return weakHashFinder, file -} - func (*sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) error { if len(buf) != int(block.Size) { return fmt.Errorf("length mismatch %d != %d", len(buf), block.Size) @@ -1574,7 +1498,7 @@ loop: activity.using(selected) var buf []byte blockNo := int(state.block.Offset / int64(state.file.BlockSize())) - buf, lastError = f.model.RequestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, int(state.block.Size), state.block.Hash, state.block.WeakHash, selected.FromTemporary) + buf, lastError = f.model.RequestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, int(state.block.Size), state.block.Hash, selected.FromTemporary) activity.done(selected) if lastError != nil { l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, selected.ID.Short(), "returned error:", lastError) diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index 69b35c018..ee013467b 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -25,7 +25,6 @@ import ( "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/ignore" "github.com/syncthing/syncthing/lib/protocol" - "github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/scanner" "github.com/syncthing/syncthing/lib/sync" ) @@ -300,7 +299,7 @@ func TestCopierFinder(t *testing.T) { } // Verify that the fetched blocks have actually been written to the temp file - blks, err := scanner.HashFile(context.TODO(), f.ID, f.Filesystem(nil), tempFile, protocol.MinBlockSize, nil, false) + blks, err := scanner.HashFile(context.TODO(), f.ID, f.Filesystem(nil), tempFile, protocol.MinBlockSize, nil) if err != nil { t.Log(err) } @@ -312,128 +311,6 @@ func TestCopierFinder(t *testing.T) { } } -func TestWeakHash(t *testing.T) { - // Setup the model/pull environment - _, fo, wcfgCancel := setupSendReceiveFolder(t) - defer wcfgCancel() - ffs := fo.Filesystem(nil) - - tempFile := fs.TempName("weakhash") - var shift int64 = 10 - var size int64 = 1 << 20 - expectBlocks := int(size / protocol.MinBlockSize) - expectPulls := int(shift / protocol.MinBlockSize) - if shift > 0 { - expectPulls++ - } - - f, err := ffs.Create("weakhash") - must(t, err) - defer f.Close() - _, err = io.CopyN(f, rand.Reader, size) - if err != nil { - t.Error(err) - } - info, err := f.Stat() - if err != nil { - t.Error(err) - } - - // Create two files, second file has `shifted` bytes random prefix, yet - // both are of the same length, for example: - // File 1: abcdefgh - // File 2: xyabcdef - f.Seek(0, io.SeekStart) - existing, err := scanner.Blocks(context.TODO(), f, protocol.MinBlockSize, size, nil, true) - if err != nil { - t.Error(err) - } - - f.Seek(0, io.SeekStart) - remainder := io.LimitReader(f, size-shift) - prefix := io.LimitReader(rand.Reader, shift) - nf := io.MultiReader(prefix, remainder) - desired, err := scanner.Blocks(context.TODO(), nf, protocol.MinBlockSize, size, nil, true) - if err != nil { - t.Error(err) - } - - existingFile := protocol.FileInfo{ - Name: "weakhash", - Blocks: existing, - Size: size, - ModifiedS: info.ModTime().Unix(), - ModifiedNs: int32(info.ModTime().Nanosecond()), - } - desiredFile := protocol.FileInfo{ - Name: "weakhash", - Size: size, - Blocks: desired, - ModifiedS: info.ModTime().Unix() + 1, - } - - fo.updateLocalsFromScanning([]protocol.FileInfo{existingFile}) - - copyChan := make(chan copyBlocksState) - pullChan := make(chan pullBlockState, expectBlocks) - finisherChan := make(chan *sharedPullerState, 1) - - // Run a single fetcher routine - go fo.copierRoutine(copyChan, pullChan, finisherChan) - defer close(copyChan) - - // Test 1 - no weak hashing, file gets fully repulled (`expectBlocks` pulls). - fo.WeakHashThresholdPct = 101 - fo.handleFile(desiredFile, fsetSnapshot(t, fo.fset), copyChan) - - var pulls []pullBlockState - timeout := time.After(10 * time.Second) - for len(pulls) < expectBlocks { - select { - case pull := <-pullChan: - pulls = append(pulls, pull) - case <-timeout: - t.Fatalf("timed out, got %d pulls expected %d", len(pulls), expectPulls) - } - } - finish := <-finisherChan - - select { - case <-pullChan: - t.Fatal("Pull channel has data to be read") - case <-finisherChan: - t.Fatal("Finisher channel has data to be read") - default: - } - - cleanupSharedPullerState(finish) - if err := ffs.Remove(tempFile); err != nil { - t.Fatal(err) - } - - // Test 2 - using weak hash, expectPulls blocks pulled. - fo.WeakHashThresholdPct = -1 - fo.handleFile(desiredFile, fsetSnapshot(t, fo.fset), copyChan) - - pulls = pulls[:0] - for len(pulls) < expectPulls { - select { - case pull := <-pullChan: - pulls = append(pulls, pull) - case <-time.After(10 * time.Second): - t.Fatalf("timed out, got %d pulls expected %d", len(pulls), expectPulls) - } - } - - finish = <-finisherChan - cleanupSharedPullerState(finish) - - expectShifted := expectBlocks - expectPulls - if finish.copyOriginShifted != expectShifted { - t.Errorf("did not copy %d shifted", expectShifted) - } -} - // Test that updating a file removes its old blocks from the blockmap func TestCopierCleanup(t *testing.T) { iterFn := func(folder, file string, index int32) bool { @@ -709,8 +586,8 @@ func TestIssue3164(t *testing.T) { func TestDiff(t *testing.T) { for i, test := range diffTestData { - a, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.a), test.s, -1, nil, false) - b, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.b), test.s, -1, nil, false) + a, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.a), test.s, -1, nil) + b, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.b), test.s, -1, nil) _, d := blockDiff(a, b) if len(d) != len(test.d) { t.Fatalf("Incorrect length for diff %d; %d != %d", i, len(d), len(test.d)) @@ -730,8 +607,8 @@ func TestDiff(t *testing.T) { func BenchmarkDiff(b *testing.B) { testCases := make([]struct{ a, b []protocol.BlockInfo }, 0, len(diffTestData)) for _, test := range diffTestData { - a, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.a), test.s, -1, nil, false) - b, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.b), test.s, -1, nil, false) + a, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.a), test.s, -1, nil) + b, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.b), test.s, -1, nil) testCases = append(testCases, struct{ a, b []protocol.BlockInfo }{a, b}) } b.ReportAllocs() diff --git a/lib/model/metrics.go b/lib/model/metrics.go index ad211aecb..faae3238a 100644 --- a/lib/model/metrics.go +++ b/lib/model/metrics.go @@ -55,16 +55,15 @@ var ( Namespace: "syncthing", Subsystem: "model", Name: "folder_processed_bytes_total", - Help: "Total amount of data processed during folder syncing, per folder ID and data source (network/local_origin/local_other/local_shifted/skipped)", + Help: "Total amount of data processed during folder syncing, per folder ID and data source (network/local_origin/local_other/skipped)", }, []string{"folder", "source"}) ) const ( - metricSourceNetwork = "network" // from the network - metricSourceLocalOrigin = "local_origin" // from the existing version of the local file - metricSourceLocalOther = "local_other" // from a different local file - metricSourceLocalShifted = "local_shifted" // from the existing version of the local file, rolling hash shifted - metricSourceSkipped = "skipped" // block of all zeroes, invented out of thin air + metricSourceNetwork = "network" // from the network + metricSourceLocalOrigin = "local_origin" // from the existing version of the local file + metricSourceLocalOther = "local_other" // from a different local file + metricSourceSkipped = "skipped" // block of all zeroes, invented out of thin air metricScopeGlobal = "global" metricScopeLocal = "local" @@ -88,6 +87,5 @@ func registerFolderMetrics(folderID string) { metricFolderProcessedBytesTotal.WithLabelValues(folderID, metricSourceNetwork) metricFolderProcessedBytesTotal.WithLabelValues(folderID, metricSourceLocalOrigin) metricFolderProcessedBytesTotal.WithLabelValues(folderID, metricSourceLocalOther) - metricFolderProcessedBytesTotal.WithLabelValues(folderID, metricSourceLocalShifted) metricFolderProcessedBytesTotal.WithLabelValues(folderID, metricSourceSkipped) } diff --git a/lib/model/mocks/model.go b/lib/model/mocks/model.go index 3855f7e99..6aa17ef60 100644 --- a/lib/model/mocks/model.go +++ b/lib/model/mocks/model.go @@ -435,19 +435,18 @@ type Model struct { result1 protocol.RequestResponse result2 error } - RequestGlobalStub func(context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, uint32, bool) ([]byte, error) + RequestGlobalStub func(context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, bool) ([]byte, error) requestGlobalMutex sync.RWMutex requestGlobalArgsForCall []struct { - arg1 context.Context - arg2 protocol.DeviceID - arg3 string - arg4 string - arg5 int - arg6 int64 - arg7 int - arg8 []byte - arg9 uint32 - arg10 bool + arg1 context.Context + arg2 protocol.DeviceID + arg3 string + arg4 string + arg5 int + arg6 int64 + arg7 int + arg8 []byte + arg9 bool } requestGlobalReturns struct { result1 []byte @@ -2580,7 +2579,7 @@ func (fake *Model) RequestReturnsOnCall(i int, result1 protocol.RequestResponse, }{result1, result2} } -func (fake *Model) RequestGlobal(arg1 context.Context, arg2 protocol.DeviceID, arg3 string, arg4 string, arg5 int, arg6 int64, arg7 int, arg8 []byte, arg9 uint32, arg10 bool) ([]byte, error) { +func (fake *Model) RequestGlobal(arg1 context.Context, arg2 protocol.DeviceID, arg3 string, arg4 string, arg5 int, arg6 int64, arg7 int, arg8 []byte, arg9 bool) ([]byte, error) { var arg8Copy []byte if arg8 != nil { arg8Copy = make([]byte, len(arg8)) @@ -2589,23 +2588,22 @@ func (fake *Model) RequestGlobal(arg1 context.Context, arg2 protocol.DeviceID, a fake.requestGlobalMutex.Lock() ret, specificReturn := fake.requestGlobalReturnsOnCall[len(fake.requestGlobalArgsForCall)] fake.requestGlobalArgsForCall = append(fake.requestGlobalArgsForCall, struct { - arg1 context.Context - arg2 protocol.DeviceID - arg3 string - arg4 string - arg5 int - arg6 int64 - arg7 int - arg8 []byte - arg9 uint32 - arg10 bool - }{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8Copy, arg9, arg10}) + arg1 context.Context + arg2 protocol.DeviceID + arg3 string + arg4 string + arg5 int + arg6 int64 + arg7 int + arg8 []byte + arg9 bool + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8Copy, arg9}) stub := fake.RequestGlobalStub fakeReturns := fake.requestGlobalReturns - fake.recordInvocation("RequestGlobal", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8Copy, arg9, arg10}) + fake.recordInvocation("RequestGlobal", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8Copy, arg9}) fake.requestGlobalMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) } if specificReturn { return ret.result1, ret.result2 @@ -2619,17 +2617,17 @@ func (fake *Model) RequestGlobalCallCount() int { return len(fake.requestGlobalArgsForCall) } -func (fake *Model) RequestGlobalCalls(stub func(context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, uint32, bool) ([]byte, error)) { +func (fake *Model) RequestGlobalCalls(stub func(context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, bool) ([]byte, error)) { fake.requestGlobalMutex.Lock() defer fake.requestGlobalMutex.Unlock() fake.RequestGlobalStub = stub } -func (fake *Model) RequestGlobalArgsForCall(i int) (context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, uint32, bool) { +func (fake *Model) RequestGlobalArgsForCall(i int) (context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, bool) { fake.requestGlobalMutex.RLock() defer fake.requestGlobalMutex.RUnlock() argsForCall := fake.requestGlobalArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7, argsForCall.arg8, argsForCall.arg9, argsForCall.arg10 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7, argsForCall.arg8, argsForCall.arg9 } func (fake *Model) RequestGlobalReturns(result1 []byte, result2 error) { diff --git a/lib/model/model.go b/lib/model/model.go index 9e898395d..63bdbea2f 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -118,7 +118,7 @@ type Model interface { GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly bool) ([]*TreeEntry, error) - RequestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) + RequestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) } type model struct { @@ -606,8 +606,6 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe report.BlockStats.Pulled = v case "copyOrigin": report.BlockStats.CopyOrigin = v - case "copyOriginShifted": - report.BlockStats.CopyOriginShifted = v case "copyElsewhere": report.BlockStats.CopyElsewhere = v } @@ -2036,7 +2034,7 @@ func (m *model) Request(conn protocol.Connection, req *protocol.Request) (out pr return nil, protocol.ErrNoSuchFile } _, err := readOffsetIntoBuf(folderFs, tempFn, req.Offset, res.data) - if err == nil && scanner.Validate(res.data, req.Hash, req.WeakHash) { + if err == nil && scanner.Validate(res.data, req.Hash) { return res, nil } // Fall through to reading from a non-temp file, just in case the temp @@ -2065,8 +2063,8 @@ func (m *model) Request(conn protocol.Connection, req *protocol.Request) (out pr return nil, protocol.ErrGeneric } - if folderCfg.Type != config.FolderTypeReceiveEncrypted && len(req.Hash) > 0 && !scanner.Validate(res.data[:n], req.Hash, req.WeakHash) { - m.recheckFile(deviceID, req.Folder, req.Name, req.Offset, req.Hash, req.WeakHash) + if folderCfg.Type != config.FolderTypeReceiveEncrypted && len(req.Hash) > 0 && !scanner.Validate(res.data[:n], req.Hash) { + m.recheckFile(deviceID, req.Folder, req.Name, req.Offset, req.Hash) l.Debugf("%v REQ(in) failed validating data: %s: %q / %q o=%d s=%d", m, deviceID.Short(), req.Folder, req.Name, req.Offset, req.Size) return nil, protocol.ErrNoSuchFile } @@ -2092,7 +2090,7 @@ func newLimitedRequestResponse(size int, limiters ...*semaphore.Semaphore) *requ return res } -func (m *model) recheckFile(deviceID protocol.DeviceID, folder, name string, offset int64, hash []byte, weakHash uint32) { +func (m *model) recheckFile(deviceID protocol.DeviceID, folder, name string, offset int64, hash []byte) { cf, ok, err := m.CurrentFolderFile(folder, name) if err != nil { l.Debugf("%v recheckFile: %s: %q / %q: current file error: %v", m, deviceID, folder, name, err) @@ -2121,10 +2119,6 @@ func (m *model) recheckFile(deviceID protocol.DeviceID, folder, name string, off l.Debugf("%v recheckFile: %s: %q / %q i=%d: hash mismatch %x != %x", m, deviceID, folder, name, blockIndex, block.Hash, hash) return } - if weakHash != 0 && block.WeakHash != weakHash { - l.Debugf("%v recheckFile: %s: %q / %q i=%d: weak hash mismatch %v != %v", m, deviceID, folder, name, blockIndex, block.WeakHash, weakHash) - return - } // The hashes provided part of the request match what we expect to find according // to what we have in the database, yet the content we've read off the filesystem doesn't @@ -2462,14 +2456,14 @@ func (m *model) deviceDidCloseRLocked(deviceID protocol.DeviceID, duration time. } } -func (m *model) RequestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) { +func (m *model) RequestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) { conn, connOK := m.requestConnectionForDevice(deviceID) if !connOK { return nil, fmt.Errorf("requestGlobal: no connection to device: %s", deviceID.Short()) } - l.Debugf("%v REQ(out): %s (%s): %q / %q b=%d o=%d s=%d h=%x wh=%x ft=%t", m, deviceID.Short(), conn, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary) - return conn.Request(ctx, &protocol.Request{Folder: folder, Name: name, BlockNo: blockNo, Offset: offset, Size: size, Hash: hash, WeakHash: weakHash, FromTemporary: fromTemporary}) + l.Debugf("%v REQ(out): %s (%s): %q / %q b=%d o=%d s=%d h=%x ft=%t", m, deviceID.Short(), conn, folder, name, blockNo, offset, size, hash, fromTemporary) + return conn.Request(ctx, &protocol.Request{Folder: folder, Name: name, BlockNo: blockNo, Offset: offset, Size: size, Hash: hash, FromTemporary: fromTemporary}) } // requestConnectionForDevice returns a connection to the given device, to diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 1c79f0e1e..0c6274248 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -222,7 +222,7 @@ func BenchmarkRequestOut(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - data, err := m.RequestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 0, 32, nil, 0, false) + data, err := m.RequestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 0, 32, nil, false) if err != nil { b.Error(err) } @@ -2819,9 +2819,9 @@ func TestIssue5002(t *testing.T) { } blockSize := int32(file.BlockSize()) - m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size-int64(blockSize), []byte{1, 2, 3, 4}, 0) - m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size, []byte{1, 2, 3, 4}, 0) // panic - m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size+int64(blockSize), []byte{1, 2, 3, 4}, 0) + m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size-int64(blockSize), []byte{1, 2, 3, 4}) + m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size, []byte{1, 2, 3, 4}) // panic + m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size+int64(blockSize), []byte{1, 2, 3, 4}) } func TestParentOfUnignored(t *testing.T) { diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go index 75a02e33c..991656ed3 100644 --- a/lib/model/requests_test.go +++ b/lib/model/requests_test.go @@ -436,11 +436,8 @@ func TestRescanIfHaveInvalidContent(t *testing.T) { } f := checkReceived(<-received) - if f.Blocks[0].WeakHash != 103547413 { - t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash) - } - res, err := m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: len(payload), Hash: f.Blocks[0].Hash, WeakHash: f.Blocks[0].WeakHash}) + res, err := m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: len(payload), Hash: f.Blocks[0].Hash}) if err != nil { t.Fatal(err) } @@ -454,17 +451,14 @@ func TestRescanIfHaveInvalidContent(t *testing.T) { writeFile(t, tfs, "foo", payload) - _, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: len(payload), Hash: f.Blocks[0].Hash, WeakHash: f.Blocks[0].WeakHash}) + _, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: len(payload), Hash: f.Blocks[0].Hash}) if err == nil { t.Fatalf("expected failure") } select { case fs := <-received: - f := checkReceived(fs) - if f.Blocks[0].WeakHash != 41943361 { - t.Fatalf("unexpected weak hash: %d != 41943361", f.Blocks[0].WeakHash) - } + checkReceived(fs) case <-time.After(time.Second): t.Fatalf("timed out") } diff --git a/lib/model/sharedpullerstate.go b/lib/model/sharedpullerstate.go index 70b82dc31..623011cbc 100644 --- a/lib/model/sharedpullerstate.go +++ b/lib/model/sharedpullerstate.go @@ -285,15 +285,6 @@ func (s *sharedPullerState) skippedSparseBlock(bytes int) { metricFolderProcessedBytesTotal.WithLabelValues(s.folder, metricSourceSkipped).Add(float64(bytes)) } -func (s *sharedPullerState) copiedFromOriginShifted(bytes int) { - s.mut.Lock() - s.copyOrigin++ - s.copyOriginShifted++ - s.updated = time.Now() - s.mut.Unlock() - metricFolderProcessedBytesTotal.WithLabelValues(s.folder, metricSourceLocalShifted).Add(float64(bytes)) -} - func (s *sharedPullerState) pullStarted() { s.mut.Lock() s.copyTotal-- diff --git a/lib/protocol/bep_fileinfo.go b/lib/protocol/bep_fileinfo.go index fa5f78d5d..f29fd1ae7 100644 --- a/lib/protocol/bep_fileinfo.go +++ b/lib/protocol/bep_fileinfo.go @@ -544,32 +544,29 @@ func (f *FileInfo) setNoContent() { } type BlockInfo struct { - Hash []byte - Offset int64 - Size int - WeakHash uint32 + Hash []byte + Offset int64 + Size int } func (b BlockInfo) ToWire() *bep.BlockInfo { return &bep.BlockInfo{ - Hash: b.Hash, - Offset: b.Offset, - Size: int32(b.Size), - WeakHash: b.WeakHash, + Hash: b.Hash, + Offset: b.Offset, + Size: int32(b.Size), } } func BlockInfoFromWire(w *bep.BlockInfo) BlockInfo { return BlockInfo{ - Hash: w.Hash, - Offset: w.Offset, - Size: int(w.Size), - WeakHash: w.WeakHash, + Hash: w.Hash, + Offset: w.Offset, + Size: int(w.Size), } } func (b BlockInfo) String() string { - return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash) + return fmt.Sprintf("Block{%d/%d/%x}", b.Offset, b.Size, b.Hash) } // For each block size, the hash of a block of all zeroes @@ -596,7 +593,6 @@ func BlocksHash(bs []BlockInfo) []byte { h := sha256.New() for _, b := range bs { _, _ = h.Write(b.Hash) - _ = binary.Write(h, binary.BigEndian, b.WeakHash) } return h.Sum(nil) } diff --git a/lib/protocol/bep_request_response.go b/lib/protocol/bep_request_response.go index 2affd3724..db0722791 100644 --- a/lib/protocol/bep_request_response.go +++ b/lib/protocol/bep_request_response.go @@ -25,7 +25,6 @@ type Request struct { Size int Hash []byte FromTemporary bool - WeakHash uint32 BlockNo int } @@ -38,7 +37,6 @@ func (r *Request) toWire() *bep.Request { Size: int32(r.Size), Hash: r.Hash, FromTemporary: r.FromTemporary, - WeakHash: r.WeakHash, BlockNo: int32(r.BlockNo), } } @@ -52,7 +50,6 @@ func requestFromWire(w *bep.Request) *Request { Size: int(w.Size), Hash: w.Hash, FromTemporary: w.FromTemporary, - WeakHash: w.WeakHash, BlockNo: int(w.BlockNo), } } diff --git a/lib/protocol/common_test.go b/lib/protocol/common_test.go index 74200df10..b6e25c2b9 100644 --- a/lib/protocol/common_test.go +++ b/lib/protocol/common_test.go @@ -17,7 +17,6 @@ type TestModel struct { offset int64 size int32 hash []byte - weakHash uint32 fromTemporary bool indexFn func(string, []FileInfo) ccFn func(*ClusterConfig) @@ -48,7 +47,6 @@ func (t *TestModel) Request(_ Connection, req *Request) (RequestResponse, error) t.offset = req.Offset t.size = int32(req.Size) t.hash = req.Hash - t.weakHash = req.WeakHash t.fromTemporary = req.FromTemporary buf := make([]byte, len(t.data)) copy(buf, t.data) diff --git a/lib/scanner/blockqueue.go b/lib/scanner/blockqueue.go index 911b482f0..8182ef74d 100644 --- a/lib/scanner/blockqueue.go +++ b/lib/scanner/blockqueue.go @@ -16,7 +16,7 @@ import ( ) // HashFile hashes the files and returns a list of blocks representing the file. -func HashFile(ctx context.Context, folderID string, fs fs.Filesystem, path string, blockSize int, counter Counter, useWeakHashes bool) ([]protocol.BlockInfo, error) { +func HashFile(ctx context.Context, folderID string, fs fs.Filesystem, path string, blockSize int, counter Counter) ([]protocol.BlockInfo, error) { fd, err := fs.Open(path) if err != nil { l.Debugln("open:", err) @@ -36,7 +36,7 @@ func HashFile(ctx context.Context, folderID string, fs fs.Filesystem, path strin // Hash the file. This may take a while for large files. - blocks, err := Blocks(ctx, fd, blockSize, size, counter, useWeakHashes) + blocks, err := Blocks(ctx, fd, blockSize, size, counter) if err != nil { l.Debugln("blocks:", err) return nil, err @@ -108,7 +108,7 @@ func (ph *parallelHasher) hashFiles(ctx context.Context) { panic("Bug. Asked to hash a directory or a deleted file.") } - blocks, err := HashFile(ctx, ph.folderID, ph.fs, f.Name, f.BlockSize(), ph.counter, true) + blocks, err := HashFile(ctx, ph.folderID, ph.fs, f.Name, f.BlockSize(), ph.counter) if err != nil { handleError(ctx, "hashing", f.Name, err, ph.outbox) continue diff --git a/lib/scanner/blocks.go b/lib/scanner/blocks.go index d875d1e19..e299ca359 100644 --- a/lib/scanner/blocks.go +++ b/lib/scanner/blocks.go @@ -10,8 +10,6 @@ import ( "bytes" "context" "crypto/sha256" - "hash" - "hash/adler32" "io" "github.com/syncthing/syncthing/lib/protocol" @@ -24,7 +22,7 @@ type Counter interface { } // Blocks returns the blockwise hash of the reader. -func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, counter Counter, useWeakHashes bool) ([]protocol.BlockInfo, error) { +func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, counter Counter) ([]protocol.BlockInfo, error) { if counter == nil { counter = &noopCounter{} } @@ -32,15 +30,6 @@ func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, cou hf := sha256.New() const hashLength = sha256.Size - var weakHf hash.Hash32 = noopHash{} - var multiHf io.Writer = hf - if useWeakHashes { - // Use an actual weak hash function, make the multiHf - // write to both hash functions. - weakHf = adler32.New() - multiHf = io.MultiWriter(hf, weakHf) - } - var blocks []protocol.BlockInfo var hashes, thisHash []byte @@ -70,7 +59,7 @@ func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, cou } lr.N = int64(blocksize) - n, err := io.CopyBuffer(multiHf, lr, buf) + n, err := io.CopyBuffer(hf, lr, buf) if err != nil { return nil, err } @@ -87,17 +76,15 @@ func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, cou thisHash, hashes = hashes[:hashLength], hashes[hashLength:] b := protocol.BlockInfo{ - Size: int(n), - Offset: offset, - Hash: thisHash, - WeakHash: weakHf.Sum32(), + Size: int(n), + Offset: offset, + Hash: thisHash, } blocks = append(blocks, b) offset += n hf.Reset() - weakHf.Reset() } if len(blocks) == 0 { @@ -112,14 +99,8 @@ func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, cou return blocks, nil } -// Validate quickly validates buf against the 32-bit weakHash, if not zero, -// else against the cryptohash hash, if len(hash)>0. It is satisfied if -// either hash matches or neither hash is given. -func Validate(buf, hash []byte, weakHash uint32) bool { - if weakHash != 0 && adler32.Checksum(buf) == weakHash { - return true - } - +// Validate validates the hash, if len(hash)>0. +func Validate(buf, hash []byte) bool { if len(hash) > 0 { hbuf := sha256.Sum256(buf) return bytes.Equal(hbuf[:], hash) diff --git a/lib/scanner/blocks_test.go b/lib/scanner/blocks_test.go index fd98e3ff2..b54eb4f74 100644 --- a/lib/scanner/blocks_test.go +++ b/lib/scanner/blocks_test.go @@ -9,51 +9,40 @@ package scanner import ( "bytes" "context" - "crypto/rand" "crypto/sha256" "fmt" - origAdler32 "hash/adler32" mrand "math/rand" "testing" - "testing/quick" - - rollingAdler32 "github.com/chmduquesne/rollinghash/adler32" - "github.com/syncthing/syncthing/lib/protocol" ) var blocksTestData = []struct { data []byte blocksize int hash []string - weakhash []uint32 }{ { []byte(""), 1024, []string{ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", }, - []uint32{0}, }, { []byte("contents"), 1024, []string{ "d1b2a59fbea7e20077af9f91b27e95e865061b270be03ff539ab3b73587882e8", }, - []uint32{0x0f3a036f}, }, { []byte("contents"), 9, []string{ "d1b2a59fbea7e20077af9f91b27e95e865061b270be03ff539ab3b73587882e8", }, - []uint32{0x0f3a036f}, }, { []byte("contents"), 8, []string{ "d1b2a59fbea7e20077af9f91b27e95e865061b270be03ff539ab3b73587882e8", }, - []uint32{0x0f3a036f}, }, { []byte("contents"), 7, @@ -61,7 +50,6 @@ var blocksTestData = []struct { "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", "043a718774c572bd8a25adbeb1bfcd5c0256ae11cecf9f9c3f925d0e52beaf89", }, - []uint32{0x0bcb02fc, 0x00740074}, }, { []byte("contents"), 3, @@ -70,7 +58,6 @@ var blocksTestData = []struct { "e4432baa90819aaef51d2a7f8e148bf7e679610f3173752fabb4dcb2d0f418d3", "44ad63f60af0f6db6fdde6d5186ef78176367df261fa06be3079b6c80c8adba4", }, - []uint32{0x02780141, 0x02970148, 0x015d00e8}, }, { []byte("conconts"), 3, @@ -79,7 +66,6 @@ var blocksTestData = []struct { "1143da2bc54c495c4be31d3868785d39ffdfd56df5668f0645d8f14d47647952", "44ad63f60af0f6db6fdde6d5186ef78176367df261fa06be3079b6c80c8adba4", }, - []uint32{0x02780141, 0x02780141, 0x015d00e8}, }, { []byte("contenten"), 3, @@ -88,14 +74,13 @@ var blocksTestData = []struct { "e4432baa90819aaef51d2a7f8e148bf7e679610f3173752fabb4dcb2d0f418d3", "e4432baa90819aaef51d2a7f8e148bf7e679610f3173752fabb4dcb2d0f418d3", }, - []uint32{0x02780141, 0x02970148, 0x02970148}, }, } func TestBlocks(t *testing.T) { for testNo, test := range blocksTestData { buf := bytes.NewBuffer(test.data) - blocks, err := Blocks(context.TODO(), buf, test.blocksize, -1, nil, true) + blocks, err := Blocks(context.TODO(), buf, test.blocksize, -1, nil) if err != nil { t.Fatal(err) } @@ -119,9 +104,6 @@ func TestBlocks(t *testing.T) { if h := fmt.Sprintf("%x", blocks[i].Hash); h != test.hash[i] { t.Errorf("%d/%d: Incorrect block hash %q != %q", testNo, i, h, test.hash[i]) } - if h := blocks[i].WeakHash; h != test.weakhash[i] { - t.Errorf("%d/%d: Incorrect block weakhash 0x%08x != 0x%08x", testNo, i, h, test.weakhash[i]) - } i++ } @@ -129,85 +111,10 @@ func TestBlocks(t *testing.T) { } } -func TestAdler32Variants(t *testing.T) { - // Verify that the two adler32 functions give matching results for a few - // different blocks of data. - - hf1 := origAdler32.New() - hf2 := rollingAdler32.New() - - checkFn := func(data []byte) bool { - hf1.Write(data) - sum1 := hf1.Sum32() - - hf2.Write(data) - sum2 := hf2.Sum32() - - hf1.Reset() - hf2.Reset() - - // Make sure whatever we use in Validate matches too resp. this - // tests gets adjusted if we ever switch the weak hash algo. - return sum1 == sum2 && Validate(data, nil, sum1) - } - - // protocol block sized data - data := make([]byte, protocol.MinBlockSize) - for i := 0; i < 5; i++ { - rand.Read(data) - if !checkFn(data) { - t.Errorf("Hash mismatch on block sized data") - } - } - - // random small blocks - if err := quick.Check(checkFn, nil); err != nil { - t.Error(err) - } - - // rolling should have the same result as the individual blocks - // themselves. - - windowSize := 128 - - hf3 := rollingAdler32.New() - hf3.Write(data[:windowSize]) - - for i := windowSize; i < len(data); i++ { - if i%windowSize == 0 { - // let the reference function catch up - window := data[i-windowSize : i] - hf1.Reset() - hf1.Write(window) - hf2.Reset() - hf2.Write(window) - - // verify that they are in sync with the rolling function - sum1 := hf1.Sum32() - sum2 := hf2.Sum32() - sum3 := hf3.Sum32() - t.Logf("At i=%d, sum2=%08x, sum3=%08x", i, sum2, sum3) - if sum2 != sum3 { - t.Errorf("Mismatch after roll; i=%d, sum2=%08x, sum3=%08x", i, sum2, sum3) - break - } - if sum1 != sum3 { - t.Errorf("Mismatch after roll; i=%d, sum1=%08x, sum3=%08x", i, sum1, sum3) - break - } - if !Validate(window, nil, sum1) { - t.Errorf("Validation failure after roll; i=%d", i) - } - } - hf3.Roll(data[i]) - } -} - func BenchmarkValidate(b *testing.B) { type block struct { - data []byte - hash [sha256.Size]byte - weakhash uint32 + data []byte + hash [sha256.Size]byte } var blocks []block const blocksPerType = 100 @@ -220,16 +127,6 @@ func BenchmarkValidate(b *testing.B) { b.data = make([]byte, 128<<10) r.Read(b.data) b.hash = sha256.Sum256(b.data) - b.weakhash = origAdler32.Checksum(b.data) - blocks = append(blocks, b) - } - // Blocks where the hash matches, but the weakhash doesn't. - for i := 0; i < blocksPerType; i++ { - var b block - b.data = make([]byte, 128<<10) - r.Read(b.data) - b.hash = sha256.Sum256(b.data) - b.weakhash = 1 // Zeros causes Validate to skip the weakhash. blocks = append(blocks, b) } @@ -238,7 +135,7 @@ func BenchmarkValidate(b *testing.B) { for i := 0; i < b.N; i++ { for _, b := range blocks { - Validate(b.data, b.hash[:], b.weakhash) + Validate(b.data, b.hash[:]) } } } diff --git a/lib/scanner/walk_test.go b/lib/scanner/walk_test.go index 5c6254d29..ebfedb2e0 100644 --- a/lib/scanner/walk_test.go +++ b/lib/scanner/walk_test.go @@ -162,7 +162,7 @@ func TestVerify(t *testing.T) { progress := newByteCounter() defer progress.Close() - blocks, err := Blocks(context.TODO(), buf, blocksize, -1, progress, false) + blocks, err := Blocks(context.TODO(), buf, blocksize, -1, progress) if err != nil { t.Fatal(err) } @@ -640,7 +640,7 @@ func BenchmarkHashFile(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - if _, err := HashFile(context.TODO(), "", testFs, testdataName, protocol.MinBlockSize, nil, true); err != nil { + if _, err := HashFile(context.TODO(), "", testFs, testdataName, protocol.MinBlockSize, nil); err != nil { b.Fatal(err) } } diff --git a/lib/syncthing/internals.go b/lib/syncthing/internals.go index 704cfba2a..135b29263 100644 --- a/lib/syncthing/internals.go +++ b/lib/syncthing/internals.go @@ -42,7 +42,7 @@ func (m *Internals) SetIgnores(folderID string, content []string) error { } func (m *Internals) DownloadBlock(ctx context.Context, deviceID protocol.DeviceID, folderID string, path string, blockNumber int, blockInfo protocol.BlockInfo, allowFromTemporary bool) ([]byte, error) { - return m.model.RequestGlobal(ctx, deviceID, folderID, path, int(blockNumber), blockInfo.Offset, blockInfo.Size, blockInfo.Hash, blockInfo.WeakHash, allowFromTemporary) + return m.model.RequestGlobal(ctx, deviceID, folderID, path, int(blockNumber), blockInfo.Offset, blockInfo.Size, blockInfo.Hash, allowFromTemporary) } func (m *Internals) BlockAvailability(folderID string, file protocol.FileInfo, block protocol.BlockInfo) ([]model.Availability, error) { diff --git a/lib/syncthing/syncthing.go b/lib/syncthing/syncthing.go index 7d562abf7..60ae452c9 100644 --- a/lib/syncthing/syncthing.go +++ b/lib/syncthing/syncthing.go @@ -177,7 +177,7 @@ func (a *App) startup() error { }() } - perf := ur.CpuBench(context.Background(), 3, 150*time.Millisecond, true) + perf := ur.CpuBench(context.Background(), 3, 150*time.Millisecond) l.Infof("Hashing performance is %.02f MB/s", perf) if err := db.UpdateSchema(a.ll); err != nil { diff --git a/lib/ur/contract/contract.go b/lib/ur/contract/contract.go index bc51c560c..1af5d3d66 100644 --- a/lib/ur/contract/contract.go +++ b/lib/ur/contract/contract.go @@ -111,8 +111,6 @@ type Report struct { ConflictsOther int `json:"conflictsOther,omitempty" metric:"folder_feature{feature=ConflictsOther},summary" since:"3"` DisableSparseFiles int `json:"disableSparseFiles,omitempty" metric:"folder_feature{feature=DisableSparseFiles},summary" since:"3"` DisableTempIndexes int `json:"disableTempIndexes,omitempty" metric:"folder_feature{feature=DisableTempIndexes},summary" since:"3"` - AlwaysWeakHash int `json:"alwaysWeakHash,omitempty" metric:"folder_feature{feature=AlwaysWeakhash},summary" since:"3"` - CustomWeakHashThreshold int `json:"customWeakHashThreshold,omitempty" metric:"folder_feature{feature=CustomWeakhashThreshold},summary" since:"3"` FsWatcherEnabled int `json:"fsWatcherEnabled,omitempty" metric:"folder_feature{feature=FSWatcherEnabled},summary" since:"3"` PullOrder map[string]int `json:"pullOrder,omitempty" metric:"folder_pull_order,summaryVec:order" since:"3"` FilesystemType map[string]int `json:"filesystemType,omitempty" metric:"folder_file_system_type,summaryVec:type" since:"3"` @@ -152,13 +150,12 @@ type Report struct { } `json:"guiStats,omitempty" since:"3"` BlockStats struct { - Total int `json:"total,omitempty" metric:"blocks_processed_total,gauge" since:"3"` - Renamed int `json:"renamed,omitempty" metric:"blocks_processed{source=renamed},gauge" since:"3"` - Reused int `json:"reused,omitempty" metric:"blocks_processed{source=reused},gauge" since:"3"` - Pulled int `json:"pulled,omitempty" metric:"blocks_processed{source=pulled},gauge" since:"3"` - CopyOrigin int `json:"copyOrigin,omitempty" metric:"blocks_processed{source=copy_origin},gauge" since:"3"` - CopyOriginShifted int `json:"copyOriginShifted,omitempty" metric:"blocks_processed{source=copy_origin_shifted},gauge" since:"3"` - CopyElsewhere int `json:"copyElsewhere,omitempty" metric:"blocks_processed{source=copy_elsewhere},gauge" since:"3"` + Total int `json:"total,omitempty" metric:"blocks_processed_total,gauge" since:"3"` + Renamed int `json:"renamed,omitempty" metric:"blocks_processed{source=renamed},gauge" since:"3"` + Reused int `json:"reused,omitempty" metric:"blocks_processed{source=reused},gauge" since:"3"` + Pulled int `json:"pulled,omitempty" metric:"blocks_processed{source=pulled},gauge" since:"3"` + CopyOrigin int `json:"copyOrigin,omitempty" metric:"blocks_processed{source=copy_origin},gauge" since:"3"` + CopyElsewhere int `json:"copyElsewhere,omitempty" metric:"blocks_processed{source=copy_elsewhere},gauge" since:"3"` } `json:"blockStats,omitempty" since:"3"` TransportStats map[string]int `json:"transportStats,omitempty" since:"3"` @@ -175,9 +172,6 @@ type Report struct { Stars int `json:"stars,omitempty" metric:"folder_ignore_lines{kind=stars},summary" since:"3"` } `json:"ignoreStats,omitempty" since:"3"` - // V3 fields added late in the RC - WeakHashEnabled bool `json:"weakHashEnabled,omitempty" metric:"-" since:"3"` // Deprecated and not provided client-side anymore - // Added in post processing Received time.Time `json:"received,omitempty"` Date string `json:"date,omitempty"` diff --git a/lib/ur/usage_report.go b/lib/ur/usage_report.go index 21aea826e..a76e1512e 100644 --- a/lib/ur/usage_report.go +++ b/lib/ur/usage_report.go @@ -116,8 +116,8 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) ( report.TotMiB = int(totBytes / 1024 / 1024) report.FolderMaxMiB = int(maxBytes / 1024 / 1024) report.MemoryUsageMiB = int((mem.Sys - mem.HeapReleased) / 1024 / 1024) - report.SHA256Perf = CpuBench(ctx, 5, 125*time.Millisecond, false) - report.HashPerf = CpuBench(ctx, 5, 125*time.Millisecond, true) + report.SHA256Perf = CpuBench(ctx, 5, 125*time.Millisecond) + report.HashPerf = report.SHA256Perf report.MemorySize = int(memorySize() / 1024 / 1024) report.NumCPU = runtime.NumCPU() @@ -252,11 +252,6 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) ( if cfg.DisableTempIndexes { report.FolderUsesV3.DisableTempIndexes++ } - if cfg.WeakHashThresholdPct < 0 { - report.FolderUsesV3.AlwaysWeakHash++ - } else if cfg.WeakHashThresholdPct != 25 { - report.FolderUsesV3.CustomWeakHashThreshold++ - } if cfg.FSWatcherEnabled { report.FolderUsesV3.FsWatcherEnabled++ } @@ -443,7 +438,7 @@ var ( ) // CpuBench returns CPU performance as a measure of single threaded SHA-256 MiB/s -func CpuBench(ctx context.Context, iterations int, duration time.Duration, useWeakHash bool) float64 { +func CpuBench(ctx context.Context, iterations int, duration time.Duration) float64 { blocksResultMut.Lock() defer blocksResultMut.Unlock() @@ -454,7 +449,7 @@ func CpuBench(ctx context.Context, iterations int, duration time.Duration, useWe var perf float64 for i := 0; i < iterations; i++ { - if v := cpuBenchOnce(ctx, duration, useWeakHash, bs); v > perf { + if v := cpuBenchOnce(ctx, duration, bs); v > perf { perf = v } } @@ -467,13 +462,13 @@ func CpuBench(ctx context.Context, iterations int, duration time.Duration, useWe return perf } -func cpuBenchOnce(ctx context.Context, duration time.Duration, useWeakHash bool, bs []byte) float64 { +func cpuBenchOnce(ctx context.Context, duration time.Duration, bs []byte) float64 { t0 := time.Now() b := 0 var err error for time.Since(t0) < duration { r := bytes.NewReader(bs) - blocksResult, err = scanner.Blocks(ctx, r, protocol.MinBlockSize, int64(len(bs)), nil, useWeakHash) + blocksResult, err = scanner.Blocks(ctx, r, protocol.MinBlockSize, int64(len(bs)), nil) if err != nil { return 0 // Context done } diff --git a/lib/weakhash/benchmark_test.go b/lib/weakhash/benchmark_test.go deleted file mode 100644 index bd30e8bbf..000000000 --- a/lib/weakhash/benchmark_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// 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 https://mozilla.org/MPL/2.0/. - -package weakhash - -import ( - "bytes" - "context" - "fmt" - "hash" - vadler32 "hash/adler32" - "io" - "math/rand" - "os" - "testing" - - "github.com/chmduquesne/rollinghash/adler32" - "github.com/chmduquesne/rollinghash/bozo32" - "github.com/chmduquesne/rollinghash/buzhash32" - "github.com/chmduquesne/rollinghash/buzhash64" -) - -const ( - testFile = "../model/testdata/tmpfile" - size = 128 << 10 -) - -func BenchmarkFind1MFile(b *testing.B) { - b.ReportAllocs() - b.SetBytes(1 << 20) - for i := 0; i < b.N; i++ { - fd, err := os.Open(testFile) - if err != nil { - b.Fatal(err) - } - _, err = Find(context.Background(), fd, []uint32{0, 1, 2}, size) - if err != nil { - b.Fatal(err) - } - fd.Close() - } -} - -type RollingHash interface { - hash.Hash - Roll(byte) -} - -func BenchmarkBlock(b *testing.B) { - tests := []struct { - name string - hash hash.Hash - }{ - { - "adler32", adler32.New(), - }, - { - "bozo32", bozo32.New(), - }, - { - "buzhash32", buzhash32.New(), - }, - { - "buzhash64", buzhash64.New(), - }, - { - "vanilla-adler32", vadler32.New(), - }, - } - - sizes := []int64{128 << 10, 16 << 20} - - buf := make([]byte, 16<<20) - rand.Read(buf) - - for _, testSize := range sizes { - for _, test := range tests { - b.Run(test.name+"-"+fmt.Sprint(testSize), func(bb *testing.B) { - bb.Run("", func(bbb *testing.B) { - bbb.ResetTimer() - for i := 0; i < bbb.N; i++ { - lr := io.LimitReader(bytes.NewReader(buf), testSize) - n, err := io.Copy(test.hash, lr) - if err != nil { - bbb.Error(err) - } - if n != testSize { - bbb.Errorf("%d != %d", n, testSize) - } - - test.hash.Sum(nil) - test.hash.Reset() - } - - bbb.SetBytes(testSize) - bbb.ReportAllocs() - }) - }) - } - } -} - -func BenchmarkRoll(b *testing.B) { - tests := []struct { - name string - hash RollingHash - }{ - { - "adler32", adler32.New(), - }, - { - "bozo32", bozo32.New(), - }, - { - "buzhash32", buzhash32.New(), - }, - { - "buzhash64", buzhash64.New(), - }, - } - - sizes := []int64{128 << 10, 16 << 20} - - for _, testSize := range sizes { - for _, test := range tests { - b.Run(test.name+"-"+fmt.Sprint(testSize), func(bb *testing.B) { - bb.Run("", func(bbb *testing.B) { - data := make([]byte, testSize) - - if _, err := test.hash.Write(data); err != nil { - bbb.Error(err) - } - - bbb.ResetTimer() - - for i := 0; i < bbb.N; i++ { - for j := int64(0); j <= testSize; j++ { - test.hash.Roll('a') - } - } - - bbb.SetBytes(testSize) - bbb.ReportAllocs() - }) - }) - } - } -} diff --git a/lib/weakhash/weakhash.go b/lib/weakhash/weakhash.go deleted file mode 100644 index 0ecce4c7b..000000000 --- a/lib/weakhash/weakhash.go +++ /dev/null @@ -1,118 +0,0 @@ -// 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 https://mozilla.org/MPL/2.0/. - -package weakhash - -import ( - "bufio" - "context" - "io" - - "github.com/chmduquesne/rollinghash/adler32" -) - -const ( - Size = 4 - - // don't track more hits than this for any given weakhash - maxWeakhashFinderHits = 10 -) - -// Find finds all the blocks of the given size within io.Reader that matches -// the hashes provided, and returns a hash -> slice of offsets within reader -// map, that produces the same weak hash. -func Find(ctx context.Context, ir io.Reader, hashesToFind []uint32, size int) (map[uint32][]int64, error) { - if ir == nil || len(hashesToFind) == 0 { - return nil, nil - } - - r := bufio.NewReader(ir) - hf := adler32.New() - - n, err := io.CopyN(hf, r, int64(size)) - if err == io.EOF { - return nil, nil - } - if err != nil { - return nil, err - } - if n != int64(size) { - return nil, io.ErrShortBuffer - } - - offsets := make(map[uint32][]int64) - for _, hashToFind := range hashesToFind { - offsets[hashToFind] = make([]int64, 0, maxWeakhashFinderHits) - } - - var i int64 - var hash uint32 - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - hash = hf.Sum32() - if existing, ok := offsets[hash]; ok && len(existing) < maxWeakhashFinderHits { - offsets[hash] = append(existing, i) - } - i++ - - bt, err := r.ReadByte() - if err == io.EOF { - break - } else if err != nil { - return offsets, err - } - hf.Roll(bt) - } - return offsets, nil -} - -func NewFinder(ctx context.Context, ir io.ReadSeeker, size int, hashesToFind []uint32) (*Finder, error) { - offsets, err := Find(ctx, ir, hashesToFind, size) - if err != nil { - return nil, err - } - - return &Finder{ - reader: ir, - size: size, - offsets: offsets, - }, nil -} - -type Finder struct { - reader io.ReadSeeker - size int - offsets map[uint32][]int64 -} - -// Iterate iterates all available blocks that matches the provided hash, reads -// them into buf, and calls the iterator function. The iterator function should -// return whether it wishes to continue iterating. -func (h *Finder) Iterate(hash uint32, buf []byte, iterFunc func(int64) bool) (bool, error) { - if h == nil || hash == 0 || len(buf) != h.size { - return false, nil - } - - for _, offset := range h.offsets[hash] { - _, err := h.reader.Seek(offset, io.SeekStart) - if err != nil { - return false, err - } - _, err = h.reader.Read(buf) - if err != nil { - return false, err - } - if !iterFunc(offset) { - return true, nil - } - } - return false, nil -} diff --git a/lib/weakhash/weakhash_test.go b/lib/weakhash/weakhash_test.go deleted file mode 100644 index 78c2d3906..000000000 --- a/lib/weakhash/weakhash_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// 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 https://mozilla.org/MPL/2.0/. - -// The existence of this file means we get 0% test coverage rather than no -// test coverage at all. Remove when implementing an actual test. - -package weakhash - -import ( - "bytes" - "context" - "io" - "os" - "reflect" - "testing" -) - -var payload = []byte("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz") - -func TestFinder(t *testing.T) { - f, err := os.CreateTemp("", "") - if err != nil { - t.Error(err) - } - defer os.Remove(f.Name()) - defer f.Close() - - if _, err := f.Write(payload); err != nil { - t.Error(err) - } - if _, err := f.Seek(0, io.SeekStart); err != nil { - t.Error(err) - } - - hashes := []uint32{65143183, 65798547} - finder, err := NewFinder(context.Background(), f, 4, hashes) - if err != nil { - t.Error(err) - } - - expected := map[uint32][]int64{ - 65143183: {1, 27, 53, 79}, - 65798547: {2, 28, 54, 80}, - } - actual := make(map[uint32][]int64) - - b := make([]byte, Size) - - for _, hash := range hashes { - _, err := finder.Iterate(hash, b[:4], func(offset int64) bool { - if !bytes.Equal(b, payload[offset:offset+4]) { - t.Errorf("Not equal at %d: %s != %s", offset, string(b), string(payload[offset:offset+4])) - } - actual[hash] = append(actual[hash], offset) - return true - }) - if err != nil { - t.Error(err) - } - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Not equal: %#v != %#v", actual, expected) - } -} diff --git a/proto/bep/bep.proto b/proto/bep/bep.proto index 4cac50597..0d6d9bd17 100644 --- a/proto/bep/bep.proto +++ b/proto/bep/bep.proto @@ -145,7 +145,7 @@ message BlockInfo { bytes hash = 3; int64 offset = 1; int32 size = 2; - uint32 weak_hash = 4; + reserved 4; } message Vector { @@ -203,8 +203,8 @@ message Request { int32 size = 5; bytes hash = 6; bool from_temporary = 7; - uint32 weak_hash = 8; int32 block_no = 9; + reserved 8; } // Response diff --git a/test/h1/config.xml b/test/h1/config.xml index c55cc5349..c7e965a88 100644 --- a/test/h1/config.xml +++ b/test/h1/config.xml @@ -24,7 +24,6 @@ false false false - 25 .stfolder false 0 @@ -153,7 +152,6 @@ false false false - 25 .stfolder false 0 diff --git a/test/h2/config.xml b/test/h2/config.xml index 8a2e1ab9b..81276c1d1 100644 --- a/test/h2/config.xml +++ b/test/h2/config.xml @@ -24,7 +24,6 @@ false false false - 25 .stfolder false 0 @@ -151,7 +150,6 @@ false false false - 25 .stfolder false 0 diff --git a/test/h3/config.xml b/test/h3/config.xml index ac99d8102..440c15b0d 100644 --- a/test/h3/config.xml +++ b/test/h3/config.xml @@ -20,7 +20,6 @@ false false false - 25 .stfolder false 0 @@ -50,7 +49,6 @@ false false false - 25 .stfolder false 0 diff --git a/test/h4/config.xml b/test/h4/config.xml index d0627cd91..8347f39c6 100644 --- a/test/h4/config.xml +++ b/test/h4/config.xml @@ -17,7 +17,6 @@ false false false - 25 .stfolder false 0 From 025905fcdf7c0cb201b8d73394e6afb877dc4697 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 29 Mar 2025 12:50:08 +0000 Subject: [PATCH 03/62] chore: switch database engine to sqlite (fixes #9954) (#9965) Switch the database from LevelDB to SQLite, for greater stability and simpler code. Co-authored-by: Tommy van der Vorst Co-authored-by: bt90 --- .github/workflows/build-syncthing.yaml | 178 +- .golangci.yml | 6 + build.go | 24 +- cmd/syncthing/cli/debug.go | 1 - cmd/syncthing/cli/index.go | 32 - cmd/syncthing/cli/index_accounting.go | 62 - cmd/syncthing/cli/index_dump.go | 162 -- cmd/syncthing/cli/index_dumpsize.go | 88 - cmd/syncthing/cli/index_idxck.go | 434 ---- cmd/syncthing/cli/utils.go | 6 - cmd/syncthing/main.go | 106 +- go.mod | 11 +- go.sum | 46 +- .../syncthing/core/aboutModalView.html | 3 + internal/db/counts.go | 73 + internal/db/interface.go | 123 ++ internal/db/metrics.go | 229 ++ {lib => internal}/db/observed.go | 142 +- .../db/olddb}/backend/backend.go | 21 - internal/db/olddb/backend/leveldb_backend.go | 113 + internal/db/olddb/backend/leveldb_open.go | 32 + {lib/db => internal/db/olddb}/keyer.go | 2 +- internal/db/olddb/lowlevel.go | 70 + internal/db/olddb/set.go | 67 + {lib/db => internal/db/olddb}/smallindex.go | 49 +- internal/db/olddb/transactions.go | 193 ++ internal/db/sqlite/db.go | 77 + internal/db/sqlite/db_bench_test.go | 243 +++ internal/db/sqlite/db_counts.go | 137 ++ internal/db/sqlite/db_global.go | 189 ++ internal/db/sqlite/db_global_test.go | 493 +++++ internal/db/sqlite/db_indexid.go | 163 ++ internal/db/sqlite/db_indexid_test.go | 81 + internal/db/sqlite/db_kv.go | 78 + internal/db/sqlite/db_local.go | 126 ++ internal/db/sqlite/db_local_test.go | 202 ++ internal/db/sqlite/db_mtimes.go | 54 + internal/db/sqlite/db_mtimes_test.go | 54 + internal/db/sqlite/db_open.go | 203 ++ internal/db/sqlite/db_open_cgo.go | 18 + internal/db/sqlite/db_open_nocgo.go | 23 + internal/db/sqlite/db_prepared.go | 44 + internal/db/sqlite/db_schema.go | 88 + internal/db/sqlite/db_service.go | 141 ++ internal/db/sqlite/db_test.go | 1145 ++++++++++ internal/db/sqlite/db_update.go | 549 +++++ {lib/db => internal/db/sqlite}/debug.go | 10 +- internal/db/sqlite/sql/README.md | 8 + .../sqlite/sql/migrations/01-placeholder.sql | 7 + internal/db/sqlite/sql/schema/00-indexes.sql | 19 + internal/db/sqlite/sql/schema/10-schema.sql | 14 + internal/db/sqlite/sql/schema/20-files.sql | 62 + internal/db/sqlite/sql/schema/30-indexids.sql | 24 + internal/db/sqlite/sql/schema/40-counts.sql | 53 + internal/db/sqlite/sql/schema/50-blocks.sql | 34 + internal/db/sqlite/sql/schema/50-mtimes.sql | 16 + internal/db/sqlite/sql/schema/70-kv.sql | 13 + internal/db/sqlite/util.go | 117 + internal/db/typed.go | 140 ++ internal/db/typed_test.go | 115 + internal/itererr/itererr.go | 83 + internal/protoutil/protoutil.go | 6 + internal/timeutil/timeutil.go | 27 + lib/api/api.go | 37 +- lib/api/api_auth_test.go | 15 +- lib/api/api_csrf.go | 4 +- lib/api/api_test.go | 164 +- lib/api/tokenmanager.go | 8 +- lib/build/build.go | 30 +- lib/config/config_test.go | 4 +- lib/config/folderconfiguration.go | 21 +- lib/config/migrations.go | 8 +- lib/config/optionsconfiguration.go | 1 - lib/config/tuning.go | 46 - lib/config/tuning_test.go | 26 - lib/db/.gitignore | 2 - lib/db/backend/backend_test.go | 76 - lib/db/backend/debug.go | 13 - lib/db/backend/leveldb_backend.go | 233 -- lib/db/backend/leveldb_open.go | 231 -- lib/db/backend/leveldb_test.go | 13 - lib/db/benchmark_test.go | 344 --- lib/db/blockmap.go | 64 - lib/db/blockmap_test.go | 260 --- lib/db/db_test.go | 701 ------ lib/db/keyer_test.go | 80 - lib/db/lowlevel.go | 1453 ------------- lib/db/meta.go | 472 ---- lib/db/meta_test.go | 182 -- lib/db/namespaced.go | 156 -- lib/db/namespaced_test.go | 177 -- lib/db/schemaupdater.go | 271 --- lib/db/set.go | 553 ----- lib/db/set_test.go | 1901 ----------------- lib/db/smallindex_test.go | 62 - lib/db/structs.go | 363 ---- lib/db/transactions.go | 1008 --------- lib/db/util_test.go | 232 -- lib/fs/filesystem_test.go | 4 +- lib/fs/mtimefs.go | 106 +- lib/fs/mtimefs_test.go | 18 +- lib/locations/locations.go | 60 +- lib/model/fakeconns_test.go | 14 +- lib/{db/util.go => model/fileinfobatch.go} | 2 +- lib/model/fileinfobatch_test.go | 64 + lib/model/folder.go | 249 ++- lib/model/folder_recvenc.go | 42 +- lib/model/folder_recvonly.go | 59 +- lib/model/folder_recvonly_test.go | 122 +- lib/model/folder_sendonly.go | 77 +- lib/model/folder_sendrecv.go | 271 +-- lib/model/folder_sendrecv_test.go | 122 +- lib/model/folder_summary.go | 18 +- lib/model/indexhandler.go | 160 +- lib/model/indexhandler_test.go | 4 +- lib/model/mocks/model.go | 887 ++++++-- lib/model/model.go | 430 ++-- lib/model/model_test.go | 350 ++- lib/model/progressemitter.go | 5 - lib/model/queue.go | 51 - lib/model/queue_test.go | 89 - lib/model/requests_test.go | 55 +- lib/model/testos_test.go | 12 +- lib/model/testutils_test.go | 89 +- lib/protocol/bep_fileinfo.go | 12 +- lib/protocol/protocol_test.go | 24 - lib/protocol/vector.go | 41 + lib/relay/protocol/packets_xdr.go | 47 +- lib/stats/debug.go | 13 - lib/stats/device.go | 24 +- lib/stats/folder.go | 27 +- lib/stats/stats_test.go | 15 +- lib/syncthing/globalmigrations.go | 107 +- lib/syncthing/internals.go | 37 +- lib/syncthing/syncthing.go | 67 +- lib/syncthing/syncthing_test.go | 18 +- lib/syncthing/utils.go | 132 +- lib/ur/usage_report.go | 10 +- lib/versioner/external.go | 2 +- lib/versioner/simple.go | 2 +- lib/versioner/simple_test.go | 4 +- lib/versioner/staggered.go | 2 +- lib/versioner/trashcan.go | 2 +- lib/versioner/trashcan_test.go | 6 +- lib/versioner/util.go | 2 +- meta/copyright_test.go | 5 +- 146 files changed, 8315 insertions(+), 11984 deletions(-) delete mode 100644 cmd/syncthing/cli/index.go delete mode 100644 cmd/syncthing/cli/index_accounting.go delete mode 100644 cmd/syncthing/cli/index_dump.go delete mode 100644 cmd/syncthing/cli/index_dumpsize.go delete mode 100644 cmd/syncthing/cli/index_idxck.go create mode 100644 internal/db/counts.go create mode 100644 internal/db/interface.go create mode 100644 internal/db/metrics.go rename {lib => internal}/db/observed.go (55%) rename {lib/db => internal/db/olddb}/backend/backend.go (93%) create mode 100644 internal/db/olddb/backend/leveldb_backend.go create mode 100644 internal/db/olddb/backend/leveldb_open.go rename {lib/db => internal/db/olddb}/keyer.go (99%) create mode 100644 internal/db/olddb/lowlevel.go create mode 100644 internal/db/olddb/set.go rename {lib/db => internal/db/olddb}/smallindex.go (71%) create mode 100644 internal/db/olddb/transactions.go create mode 100644 internal/db/sqlite/db.go create mode 100644 internal/db/sqlite/db_bench_test.go create mode 100644 internal/db/sqlite/db_counts.go create mode 100644 internal/db/sqlite/db_global.go create mode 100644 internal/db/sqlite/db_global_test.go create mode 100644 internal/db/sqlite/db_indexid.go create mode 100644 internal/db/sqlite/db_indexid_test.go create mode 100644 internal/db/sqlite/db_kv.go create mode 100644 internal/db/sqlite/db_local.go create mode 100644 internal/db/sqlite/db_local_test.go create mode 100644 internal/db/sqlite/db_mtimes.go create mode 100644 internal/db/sqlite/db_mtimes_test.go create mode 100644 internal/db/sqlite/db_open.go create mode 100644 internal/db/sqlite/db_open_cgo.go create mode 100644 internal/db/sqlite/db_open_nocgo.go create mode 100644 internal/db/sqlite/db_prepared.go create mode 100644 internal/db/sqlite/db_schema.go create mode 100644 internal/db/sqlite/db_service.go create mode 100644 internal/db/sqlite/db_test.go create mode 100644 internal/db/sqlite/db_update.go rename {lib/db => internal/db/sqlite}/debug.go (58%) create mode 100644 internal/db/sqlite/sql/README.md create mode 100644 internal/db/sqlite/sql/migrations/01-placeholder.sql create mode 100644 internal/db/sqlite/sql/schema/00-indexes.sql create mode 100644 internal/db/sqlite/sql/schema/10-schema.sql create mode 100644 internal/db/sqlite/sql/schema/20-files.sql create mode 100644 internal/db/sqlite/sql/schema/30-indexids.sql create mode 100644 internal/db/sqlite/sql/schema/40-counts.sql create mode 100644 internal/db/sqlite/sql/schema/50-blocks.sql create mode 100644 internal/db/sqlite/sql/schema/50-mtimes.sql create mode 100644 internal/db/sqlite/sql/schema/70-kv.sql create mode 100644 internal/db/sqlite/util.go create mode 100644 internal/db/typed.go create mode 100644 internal/db/typed_test.go create mode 100644 internal/itererr/itererr.go create mode 100644 internal/timeutil/timeutil.go delete mode 100644 lib/config/tuning.go delete mode 100644 lib/config/tuning_test.go delete mode 100644 lib/db/.gitignore delete mode 100644 lib/db/backend/backend_test.go delete mode 100644 lib/db/backend/debug.go delete mode 100644 lib/db/backend/leveldb_backend.go delete mode 100644 lib/db/backend/leveldb_open.go delete mode 100644 lib/db/backend/leveldb_test.go delete mode 100644 lib/db/benchmark_test.go delete mode 100644 lib/db/blockmap.go delete mode 100644 lib/db/blockmap_test.go delete mode 100644 lib/db/db_test.go delete mode 100644 lib/db/keyer_test.go delete mode 100644 lib/db/lowlevel.go delete mode 100644 lib/db/meta.go delete mode 100644 lib/db/meta_test.go delete mode 100644 lib/db/namespaced.go delete mode 100644 lib/db/namespaced_test.go delete mode 100644 lib/db/schemaupdater.go delete mode 100644 lib/db/set.go delete mode 100644 lib/db/set_test.go delete mode 100644 lib/db/smallindex_test.go delete mode 100644 lib/db/structs.go delete mode 100644 lib/db/transactions.go delete mode 100644 lib/db/util_test.go rename lib/{db/util.go => model/fileinfobatch.go} (99%) create mode 100644 lib/model/fileinfobatch_test.go delete mode 100644 lib/stats/debug.go diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index 40810699c..a574c7e9e 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -13,8 +13,6 @@ env: GO_VERSION: "~1.24.0" # Optimize compatibility on the slow archictures. - GO386: softfloat - GOARM: "5" GOMIPS: softfloat # Avoid hilarious amounts of obscuring log output when running tests. @@ -24,6 +22,8 @@ env: BUILD_USER: builder BUILD_HOST: github.syncthing.net + TAGS: "netgo osusergo sqlite_omit_load_extension" + # A note on actions and third party code... The actions under actions/ (like # `uses: actions/checkout`) are maintained by GitHub, and we need to trust # GitHub to maintain their code and infrastructure or we're in deep shit in @@ -85,6 +85,7 @@ jobs: LOKI_USER: ${{ secrets.LOKI_USER }} LOKI_PASSWORD: ${{ secrets.LOKI_PASSWORD }} LOKI_LABELS: "go=${{ matrix.go }},runner=${{ matrix.runner }},repo=${{ github.repository }},ref=${{ github.ref }}" + CGO_ENABLED: "1" # # Meta checks for formatting, copyright, etc @@ -136,17 +137,8 @@ jobs: package-windows: name: Package for Windows - runs-on: windows-latest + runs-on: ubuntu-latest steps: - - name: Set git to use LF - # Without this, the checkout will happen with CRLF line endings, - # which is fine for the source code but messes up tests that depend - # on data on disk being as expected. Ideally, those tests should be - # fixed, but not today. - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -158,17 +150,14 @@ jobs: cache: false check-latest: true - - name: Get actual Go version - run: | - go version - echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV + - uses: mlugg/setup-zig@v1 - uses: actions/cache@v4 with: path: | - ~\AppData\Local\go-build - ~\go\pkg\mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }} + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-windows-${{ hashFiles('**/go.sum') }} - name: Install dependencies run: | @@ -176,15 +165,14 @@ jobs: - name: Create packages run: | - $targets = 'syncthing', 'stdiscosrv', 'strelaysrv' - $archs = 'amd64', 'arm', 'arm64', '386' - foreach ($arch in $archs) { - foreach ($tgt in $targets) { - go run build.go -goarch $arch zip $tgt - } - } + for tgt in syncthing stdiscosrv strelaysrv ; do + go run build.go -tags "${{env.TAGS}}" -goos windows -goarch amd64 -cc "zig cc -target x86_64-windows" zip $tgt + go run build.go -tags "${{env.TAGS}}" -goos windows -goarch 386 -cc "zig cc -target x86-windows" zip $tgt + go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm64 -cc "zig cc -target aarch64-windows" zip $tgt + # go run build.go -tags "${{env.TAGS}}" -goos windows -goarch arm -cc "zig cc -target thumb-windows" zip $tgt # failes with linker errors + done env: - CGO_ENABLED: "0" + CGO_ENABLED: "1" - name: Archive artifacts uses: actions/upload-artifact@v4 @@ -194,7 +182,7 @@ jobs: codesign-windows: name: Codesign for Windows - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) environment: release runs-on: windows-latest needs: @@ -269,6 +257,8 @@ jobs: go version echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV + - uses: mlugg/setup-zig@v1 + - uses: actions/cache@v4 with: path: | @@ -278,14 +268,25 @@ jobs: - name: Create packages run: | - archs=$(go tool dist list | grep linux | sed 's#linux/##') - for goarch in $archs ; do - for tgt in syncthing stdiscosrv strelaysrv ; do - go run build.go -goarch "$goarch" tar "$tgt" - done + sudo apt-get install -y gcc-mips64-linux-gnuabi64 gcc-mips64el-linux-gnuabi64 + for tgt in syncthing stdiscosrv strelaysrv ; do + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch 386 -cc "zig cc -target x86-linux-musl" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mips -cc "zig cc -target mips-linux-musleabi" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mipsle -cc "zig cc -target mipsel-linux-musleabi" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mips64 -cc mips64-linux-gnuabi64-gcc tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mips64le -cc mips64el-linux-gnuabi64-gcc tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch riscv64 -cc "zig cc -target riscv64-linux-musl" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch s390x -cc "zig cc -target s390x-linux-musl" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch loong64 -cc "zig cc -target loongarch64-linux-musl" tar "$tgt" + # go run build.go -tags "${{env.TAGS}}" -goos linux -goarch ppc64 -cc "zig cc -target powerpc64-linux-musl" tar "$tgt" # fails with linkmode not supported + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch ppc64le -cc "zig cc -target powerpc64le-linux-musl" tar "$tgt" done env: - CGO_ENABLED: "0" + CGO_ENABLED: "1" + EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static" - name: Archive artifacts uses: actions/upload-artifact@v4 @@ -303,6 +304,8 @@ jobs: name: Package for macOS if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) environment: release + env: + CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }} runs-on: macos-latest steps: - uses: actions/checkout@v4 @@ -329,6 +332,7 @@ jobs: key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }} - name: Import signing certificate + if: env.CODESIGN_IDENTITY != '' run: | # Set up a run-specific keychain, making it available for the # `codesign` tool. @@ -356,7 +360,7 @@ jobs: - name: Create package (amd64) run: | for tgt in syncthing stdiscosrv strelaysrv ; do - go run build.go -goarch amd64 zip "$tgt" + go run build.go -tags "${{env.TAGS}}" -goarch amd64 zip "$tgt" done env: CGO_ENABLED: "1" @@ -372,7 +376,7 @@ jobs: EOT chmod 755 xgo.sh for tgt in syncthing stdiscosrv strelaysrv ; do - go run build.go -gocmd ./xgo.sh -goarch arm64 zip "$tgt" + go run build.go -tags "${{env.TAGS}}" -gocmd ./xgo.sh -goarch arm64 zip "$tgt" done env: CGO_ENABLED: "1" @@ -401,7 +405,7 @@ jobs: notarize-macos: name: Notarize for macOS - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) environment: release needs: - package-macos @@ -483,7 +487,7 @@ jobs: goarch="${plat#*/}" echo "::group ::$plat" for tgt in syncthing stdiscosrv strelaysrv ; do - if ! go run build.go -goos "$goos" -goarch "$goarch" tar "$tgt" 2>/dev/null; then + if ! go run build.go -goos "$goos" -goarch "$goarch" tar "$tgt" ; then echo "::warning ::Failed to build $tgt for $plat" fi done @@ -545,7 +549,7 @@ jobs: sign-for-upgrade: name: Sign for upgrade - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) environment: release needs: - codesign-windows @@ -663,6 +667,8 @@ jobs: run: | gem install fpm + - uses: mlugg/setup-zig@v1 + - uses: actions/cache@v4 with: path: | @@ -670,15 +676,17 @@ jobs: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-debian-${{ hashFiles('**/go.sum') }} - - name: Package for Debian + - name: Package for Debian (CGO) run: | - for arch in amd64 i386 armhf armel arm64 ; do - for tgt in syncthing stdiscosrv strelaysrv ; do - go run build.go -no-upgrade -installsuffix=no-upgrade -goarch "$arch" deb "$tgt" - done + for tgt in syncthing stdiscosrv strelaysrv ; do + go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" deb "$tgt" + go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi" deb "$tgt" + go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" deb "$tgt" done env: BUILD_USER: debian + CGO_ENABLED: "1" + EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static" - name: Archive artifacts uses: actions/upload-artifact@v4 @@ -692,7 +700,7 @@ jobs: publish-nightly: name: Publish nightly build - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && startsWith(github.ref, 'refs/heads/release-nightly') + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && startsWith(github.ref, 'refs/heads/release-nightly') environment: release needs: - sign-for-upgrade @@ -742,7 +750,7 @@ jobs: publish-release-files: name: Publish release files - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v')) + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v')) environment: release needs: - sign-for-upgrade @@ -809,7 +817,7 @@ jobs: publish-apt: name: Publish APT - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v1')) environment: release needs: - package-debian @@ -836,7 +844,9 @@ jobs: - name: Prepare packages run: | kind=stable - if [[ $VERSION == *-rc.[0-9] ]] ; then + if [[ $VERSION == v2* ]] ; then + kind=v2 + elif [[ $VERSION == *-rc.[0-9] ]] ; then kind=candidate elif [[ $VERSION == *-* ]] ; then kind=nightly @@ -888,8 +898,10 @@ jobs: docker-syncthing: name: Build and push Docker images runs-on: ubuntu-latest - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' environment: docker + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} permissions: contents: read packages: write @@ -902,13 +914,13 @@ jobs: include: - pkg: syncthing dockerfile: Dockerfile - image: syncthing/syncthing + image: syncthing - pkg: strelaysrv dockerfile: Dockerfile.strelaysrv - image: syncthing/relaysrv + image: relaysrv - pkg: stdiscosrv dockerfile: Dockerfile.stdiscosrv - image: syncthing/discosrv + image: discosrv steps: - uses: actions/checkout@v4 with: @@ -926,6 +938,8 @@ jobs: go version echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV + - uses: mlugg/setup-zig@v1 + - uses: actions/cache@v4 with: path: | @@ -933,33 +947,34 @@ jobs: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }} - - name: Build binaries + - name: Build binaries (CGO) run: | - for arch in amd64 arm64 arm; do - go run build.go -goos linux -goarch "$arch" -no-upgrade build ${{ matrix.pkg }} - mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch" - done - env: - CGO_ENABLED: "0" - BUILD_USER: docker + # amd64 + go run build.go -goos linux -goarch amd64 -tags "${{env.TAGS}}" -cc "zig cc -target x86_64-linux-musl" -no-upgrade build ${{ matrix.pkg }} + mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-amd64 - - name: Check if we will be able to push images - run: | - if [[ "${{ secrets.DOCKERHUB_TOKEN }}" != "" ]]; then - echo "DOCKER_PUSH=true" >> $GITHUB_ENV; - fi + # arm64 + go run build.go -goos linux -goarch arm64 -tags "${{env.TAGS}}" -cc "zig cc -target aarch64-linux-musl" -no-upgrade build ${{ matrix.pkg }} + mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm64 + + # arm + go run build.go -goos linux -goarch arm -tags "${{env.TAGS}}" -cc "zig cc -target arm-linux-musleabi" -no-upgrade build ${{ matrix.pkg }} + mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm + env: + CGO_ENABLED: "1" + BUILD_USER: docker + EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static" - name: Login to Docker Hub uses: docker/login-action@v3 - if: env.DOCKER_PUSH == 'true' + if: env.DOCKERHUB_USERNAME != '' with: registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} + username: ${{ env.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR uses: docker/login-action@v3 - if: env.DOCKER_PUSH == 'true' with: registry: ghcr.io username: ${{ github.actor }} @@ -972,18 +987,31 @@ jobs: run: | version=$(go run build.go version) version=${version#v} + repo=ghcr.io/${{ github.repository_owner }}/${{ matrix.image }} + ref="${{github.ref_name}}" + ref=${ref//\//-} # slashes to dashes + + # List of tags for ghcr.io if [[ $version == @([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]) ]] ; then - echo Release version, pushing to :latest and version tags major=${version%.*.*} minor=${version%.*} - tags=docker.io/${{ matrix.image }}:$version,ghcr.io/${{ matrix.image }}:$version,docker.io/${{ matrix.image }}:$major,ghcr.io/${{ matrix.image }}:$major,docker.io/${{ matrix.image }}:$minor,ghcr.io/${{ matrix.image }}:$minor,docker.io/${{ matrix.image }}:latest,ghcr.io/${{ matrix.image }}:latest + tags=$repo:$version,$repo:$major,$repo:$minor,$repo:latest elif [[ $version == *-rc.@([0-9]|[0-9][0-9]) ]] ; then - echo Release candidate, pushing to :rc and version tags - tags=docker.io/${{ matrix.image }}:$version,ghcr.io/${{ matrix.image }}:$version,docker.io/${{ matrix.image }}:rc,ghcr.io/${{ matrix.image }}:rc + tags=$repo:$version,$repo:rc + elif [[ $ref == "main" ]] ; then + tags=$repo:edge else - echo Development version, pushing to :edge - tags=docker.io/${{ matrix.image }}:edge,ghcr.io/${{ matrix.image }}:edge + tags=$repo:$ref fi + + # If we have a Docker Hub secret, also push to there. + if [[ $DOCKERHUB_USERNAME != "" ]] ; then + dockerhubtags="${tags//ghcr.io\/syncthing/docker.io\/syncthing}" + tags="$tags,$dockerhubtags" + fi + + echo Pushing to $tags + echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV echo "VERSION=$version" >> $GITHUB_ENV @@ -993,8 +1021,8 @@ jobs: context: . file: ${{ matrix.dockerfile }} platforms: linux/amd64,linux/arm64,linux/arm/7 - push: ${{ env.DOCKER_PUSH == 'true' }} tags: ${{ env.DOCKER_TAGS }} + push: true labels: | org.opencontainers.image.version=${{ env.VERSION }} org.opencontainers.image.revision=${{ github.sha }} diff --git a/.golangci.yml b/.golangci.yml index dfabd1205..68b2c855f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,6 +3,7 @@ linters: disable: - cyclop - depguard + - err113 - exhaustive - exhaustruct - funlen @@ -12,6 +13,7 @@ linters: - gocognit - goconst - gocyclo + - godot - godox - gofmt - goimports @@ -21,15 +23,19 @@ linters: - ireturn - lll - maintidx + - musttag - nestif + - nlreturn - nonamedreturns - paralleltest + - prealloc - protogetter - scopelint - tagalign - tagliatelle - testpackage - varnamelen + - wrapcheck - wsl issues: diff --git a/build.go b/build.go index fd591a41e..7b205edd9 100644 --- a/build.go +++ b/build.go @@ -288,10 +288,10 @@ func runCommand(cmd string, target target) { build(target, tags) case "test": - test(strings.Fields(extraTags), "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...") + test(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...") case "bench": - bench(strings.Fields(extraTags), "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...") + bench(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...") case "integration": integration(false) @@ -474,7 +474,7 @@ func install(target target, tags []string) { defer shouldCleanupSyso(sysoPath) } - args := []string{"install", "-v"} + args := []string{"install"} args = appendParameters(args, tags, target.buildPkgs...) runPrint(goCmd, args...) } @@ -502,7 +502,7 @@ func build(target target, tags []string) { defer shouldCleanupSyso(sysoPath) } - args := []string{"build", "-v"} + args := []string{"build"} if buildOut != "" { args = append(args, "-o", buildOut) } @@ -514,13 +514,6 @@ func setBuildEnvVars() { os.Setenv("GOOS", goos) os.Setenv("GOARCH", goarch) os.Setenv("CC", cc) - if os.Getenv("CGO_ENABLED") == "" { - switch goos { - case "darwin", "solaris": - default: - os.Setenv("CGO_ENABLED", "0") - } - } } func appendParameters(args []string, tags []string, pkgs ...string) []string { @@ -736,12 +729,9 @@ func shouldBuildSyso(dir string) (string, error) { sysoPath := filepath.Join(dir, "cmd", "syncthing", "resource.syso") // See https://github.com/josephspurrier/goversioninfo#command-line-flags - armOption := "" - if strings.Contains(goarch, "arm") { - armOption = "-arm=true" - } - - if _, err := runError("goversioninfo", "-o", sysoPath, armOption); err != nil { + arm := strings.HasPrefix(goarch, "arm") + a64 := strings.Contains(goarch, "64") + if _, err := runError("goversioninfo", "-o", sysoPath, fmt.Sprintf("-arm=%v", arm), fmt.Sprintf("-64=%v", a64)); err != nil { return "", errors.New("failed to create " + sysoPath + ": " + err.Error()) } diff --git a/cmd/syncthing/cli/debug.go b/cmd/syncthing/cli/debug.go index 9425e4310..9f18e1556 100644 --- a/cmd/syncthing/cli/debug.go +++ b/cmd/syncthing/cli/debug.go @@ -41,5 +41,4 @@ func (p *profileCommand) Run(ctx Context) error { type debugCommand struct { File fileCommand `cmd:"" help:"Show information about a file (or directory/symlink)"` Profile profileCommand `cmd:"" help:"Save a profile to help figuring out what Syncthing does"` - Index indexCommand `cmd:"" help:"Show information about the index (database)"` } diff --git a/cmd/syncthing/cli/index.go b/cmd/syncthing/cli/index.go deleted file mode 100644 index 04c9daa37..000000000 --- a/cmd/syncthing/cli/index.go +++ /dev/null @@ -1,32 +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 https://mozilla.org/MPL/2.0/. - -package cli - -import ( - "github.com/alecthomas/kong" -) - -type indexCommand struct { - Dump struct{} `cmd:"" help:"Print the entire db"` - DumpSize struct{} `cmd:"" help:"Print the db size of different categories of information"` - Check struct{} `cmd:"" help:"Check the database for inconsistencies"` - Account struct{} `cmd:"" help:"Print key and value size statistics per key type"` -} - -func (*indexCommand) Run(kongCtx *kong.Context) error { - switch kongCtx.Selected().Name { - case "dump": - return indexDump() - case "dump-size": - return indexDumpSize() - case "check": - return indexCheck() - case "account": - return indexAccount() - } - return nil -} diff --git a/cmd/syncthing/cli/index_accounting.go b/cmd/syncthing/cli/index_accounting.go deleted file mode 100644 index cd22b16bb..000000000 --- a/cmd/syncthing/cli/index_accounting.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2020 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 https://mozilla.org/MPL/2.0/. - -package cli - -import ( - "fmt" - "os" - "text/tabwriter" -) - -// indexAccount prints key and data size statistics per class -func indexAccount() error { - ldb, err := getDB() - if err != nil { - return err - } - - it, err := ldb.NewPrefixIterator(nil) - if err != nil { - return err - } - - var ksizes [256]int - var dsizes [256]int - var counts [256]int - var max [256]int - - for it.Next() { - key := it.Key() - t := key[0] - ds := len(it.Value()) - ks := len(key) - s := ks + ds - - counts[t]++ - ksizes[t] += ks - dsizes[t] += ds - if s > max[t] { - max[t] = s - } - } - - tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', tabwriter.AlignRight) - toti, totds, totks := 0, 0, 0 - for t := range ksizes { - if ksizes[t] > 0 { - // yes metric kilobytes 🤘 - fmt.Fprintf(tw, "0x%02x:\t%d items,\t%d KB keys +\t%d KB data,\t%d B +\t%d B avg,\t%d B max\t\n", t, counts[t], ksizes[t]/1000, dsizes[t]/1000, ksizes[t]/counts[t], dsizes[t]/counts[t], max[t]) - toti += counts[t] - totds += dsizes[t] - totks += ksizes[t] - } - } - fmt.Fprintf(tw, "Total\t%d items,\t%d KB keys +\t%d KB data.\t\n", toti, totks/1000, totds/1000) - tw.Flush() - - return nil -} diff --git a/cmd/syncthing/cli/index_dump.go b/cmd/syncthing/cli/index_dump.go deleted file mode 100644 index 6eff4717c..000000000 --- a/cmd/syncthing/cli/index_dump.go +++ /dev/null @@ -1,162 +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 https://mozilla.org/MPL/2.0/. - -package cli - -import ( - "encoding/binary" - "fmt" - "time" - - "google.golang.org/protobuf/proto" - - "github.com/syncthing/syncthing/internal/gen/bep" - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/protocol" -) - -func indexDump() error { - ldb, err := getDB() - if err != nil { - return err - } - it, err := ldb.NewPrefixIterator(nil) - if err != nil { - return err - } - for it.Next() { - key := it.Key() - switch key[0] { - case db.KeyTypeDevice: - folder := binary.BigEndian.Uint32(key[1:]) - device := binary.BigEndian.Uint32(key[1+4:]) - name := nulString(key[1+4+4:]) - fmt.Printf("[device] F:%d D:%d N:%q", folder, device, name) - - var f bep.FileInfo - err := proto.Unmarshal(it.Value(), &f) - if err != nil { - return err - } - fmt.Printf(" V:%v\n", &f) - - case db.KeyTypeGlobal: - folder := binary.BigEndian.Uint32(key[1:]) - name := nulString(key[1+4:]) - var flv dbproto.VersionList - proto.Unmarshal(it.Value(), &flv) - fmt.Printf("[global] F:%d N:%q V:%s\n", folder, name, &flv) - - case db.KeyTypeBlock: - folder := binary.BigEndian.Uint32(key[1:]) - hash := key[1+4 : 1+4+32] - name := nulString(key[1+4+32:]) - fmt.Printf("[block] F:%d H:%x N:%q I:%d\n", folder, hash, name, binary.BigEndian.Uint32(it.Value())) - - case db.KeyTypeDeviceStatistic: - fmt.Printf("[dstat] K:%x V:%x\n", key, it.Value()) - - case db.KeyTypeFolderStatistic: - fmt.Printf("[fstat] K:%x V:%x\n", key, it.Value()) - - case db.KeyTypeVirtualMtime: - folder := binary.BigEndian.Uint32(key[1:]) - name := nulString(key[1+4:]) - val := it.Value() - var realTime, virtualTime time.Time - realTime.UnmarshalBinary(val[:len(val)/2]) - virtualTime.UnmarshalBinary(val[len(val)/2:]) - fmt.Printf("[mtime] F:%d N:%q R:%v V:%v\n", folder, name, realTime, virtualTime) - - case db.KeyTypeFolderIdx: - key := binary.BigEndian.Uint32(key[1:]) - fmt.Printf("[folderidx] K:%d V:%q\n", key, it.Value()) - - case db.KeyTypeDeviceIdx: - key := binary.BigEndian.Uint32(key[1:]) - val := it.Value() - device := "" - if len(val) > 0 { - dev, err := protocol.DeviceIDFromBytes(val) - if err != nil { - device = fmt.Sprintf("", len(val)) - } else { - device = dev.String() - } - } - fmt.Printf("[deviceidx] K:%d V:%s\n", key, device) - - case db.KeyTypeIndexID: - device := binary.BigEndian.Uint32(key[1:]) - folder := binary.BigEndian.Uint32(key[5:]) - fmt.Printf("[indexid] D:%d F:%d I:%x\n", device, folder, it.Value()) - - case db.KeyTypeFolderMeta: - folder := binary.BigEndian.Uint32(key[1:]) - fmt.Printf("[foldermeta] F:%d", folder) - var cs dbproto.CountsSet - if err := proto.Unmarshal(it.Value(), &cs); err != nil { - fmt.Printf(" (invalid)\n") - } else { - fmt.Printf(" V:%v\n", &cs) - } - - case db.KeyTypeMiscData: - fmt.Printf("[miscdata] K:%q V:%q\n", key[1:], it.Value()) - - case db.KeyTypeSequence: - folder := binary.BigEndian.Uint32(key[1:]) - seq := binary.BigEndian.Uint64(key[5:]) - fmt.Printf("[sequence] F:%d S:%d V:%q\n", folder, seq, it.Value()) - - case db.KeyTypeNeed: - folder := binary.BigEndian.Uint32(key[1:]) - file := string(key[5:]) - fmt.Printf("[need] F:%d V:%q\n", folder, file) - - case db.KeyTypeBlockList: - fmt.Printf("[blocklist] H:%x\n", key[1:]) - - case db.KeyTypeBlockListMap: - folder := binary.BigEndian.Uint32(key[1:]) - hash := key[5:37] - fileName := string(key[37:]) - fmt.Printf("[blocklistmap] F:%d H:%x N:%s\n", folder, hash, fileName) - - case db.KeyTypeVersion: - fmt.Printf("[version] H:%x", key[1:]) - var v bep.Vector - err := proto.Unmarshal(it.Value(), &v) - if err != nil { - fmt.Printf(" (invalid)\n") - } else { - fmt.Printf(" V:%v\n", &v) - } - - case db.KeyTypePendingFolder: - device := binary.BigEndian.Uint32(key[1:]) - folder := string(key[5:]) - var of dbproto.ObservedFolder - proto.Unmarshal(it.Value(), &of) - fmt.Printf("[pendingFolder] D:%d F:%s V:%v\n", device, folder, &of) - - case db.KeyTypePendingDevice: - device := "" - dev, err := protocol.DeviceIDFromBytes(key[1:]) - if err == nil { - device = dev.String() - } - var od dbproto.ObservedDevice - proto.Unmarshal(it.Value(), &od) - fmt.Printf("[pendingDevice] D:%v V:%v\n", device, &od) - - default: - fmt.Printf("[??? %d]\n %x\n %x\n", key[0], key, it.Value()) - } - } - return nil -} diff --git a/cmd/syncthing/cli/index_dumpsize.go b/cmd/syncthing/cli/index_dumpsize.go deleted file mode 100644 index da7a6dbe6..000000000 --- a/cmd/syncthing/cli/index_dumpsize.go +++ /dev/null @@ -1,88 +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 https://mozilla.org/MPL/2.0/. - -package cli - -import ( - "encoding/binary" - "fmt" - "sort" - - "github.com/syncthing/syncthing/lib/db" -) - -func indexDumpSize() error { - type sizedElement struct { - key string - size int - } - - ldb, err := getDB() - if err != nil { - return err - } - - it, err := ldb.NewPrefixIterator(nil) - if err != nil { - return err - } - - var elems []sizedElement - for it.Next() { - var ele sizedElement - - key := it.Key() - switch key[0] { - case db.KeyTypeDevice: - folder := binary.BigEndian.Uint32(key[1:]) - device := binary.BigEndian.Uint32(key[1+4:]) - name := nulString(key[1+4+4:]) - ele.key = fmt.Sprintf("DEVICE:%d:%d:%s", folder, device, name) - - case db.KeyTypeGlobal: - folder := binary.BigEndian.Uint32(key[1:]) - name := nulString(key[1+4:]) - ele.key = fmt.Sprintf("GLOBAL:%d:%s", folder, name) - - case db.KeyTypeBlock: - folder := binary.BigEndian.Uint32(key[1:]) - hash := key[1+4 : 1+4+32] - name := nulString(key[1+4+32:]) - ele.key = fmt.Sprintf("BLOCK:%d:%x:%s", folder, hash, name) - - case db.KeyTypeDeviceStatistic: - ele.key = fmt.Sprintf("DEVICESTATS:%s", key[1:]) - - case db.KeyTypeFolderStatistic: - ele.key = fmt.Sprintf("FOLDERSTATS:%s", key[1:]) - - case db.KeyTypeVirtualMtime: - ele.key = fmt.Sprintf("MTIME:%s", key[1:]) - - case db.KeyTypeFolderIdx: - id := binary.BigEndian.Uint32(key[1:]) - ele.key = fmt.Sprintf("FOLDERIDX:%d", id) - - case db.KeyTypeDeviceIdx: - id := binary.BigEndian.Uint32(key[1:]) - ele.key = fmt.Sprintf("DEVICEIDX:%d", id) - - default: - ele.key = fmt.Sprintf("UNKNOWN:%x", key) - } - ele.size = len(it.Value()) - elems = append(elems, ele) - } - - sort.Slice(elems, func(i, j int) bool { - return elems[i].size > elems[j].size - }) - for _, ele := range elems { - fmt.Println(ele.key, ele.size) - } - - return nil -} diff --git a/cmd/syncthing/cli/index_idxck.go b/cmd/syncthing/cli/index_idxck.go deleted file mode 100644 index a0dab459a..000000000 --- a/cmd/syncthing/cli/index_idxck.go +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package cli - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "sort" - - "google.golang.org/protobuf/proto" - - "github.com/syncthing/syncthing/internal/gen/bep" - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/protocol" -) - -type fileInfoKey struct { - folder uint32 - device uint32 - name string -} - -type globalKey struct { - folder uint32 - name string -} - -type sequenceKey struct { - folder uint32 - sequence uint64 -} - -func indexCheck() (err error) { - ldb, err := getDB() - if err != nil { - return err - } - - folders := make(map[uint32]string) - devices := make(map[uint32]string) - deviceToIDs := make(map[string]uint32) - fileInfos := make(map[fileInfoKey]*bep.FileInfo) - globals := make(map[globalKey]*dbproto.VersionList) - sequences := make(map[sequenceKey]string) - needs := make(map[globalKey]struct{}) - blocklists := make(map[string]struct{}) - versions := make(map[string]*bep.Vector) - usedBlocklists := make(map[string]struct{}) - usedVersions := make(map[string]struct{}) - var localDeviceKey uint32 - success := true - defer func() { - if err == nil { - if success { - fmt.Println("Index check completed successfully.") - } else { - err = errors.New("Inconsistencies found in the index") - } - } - }() - - it, err := ldb.NewPrefixIterator(nil) - if err != nil { - return err - } - for it.Next() { - key := it.Key() - switch key[0] { - case db.KeyTypeDevice: - folder := binary.BigEndian.Uint32(key[1:]) - device := binary.BigEndian.Uint32(key[1+4:]) - name := nulString(key[1+4+4:]) - - var f bep.FileInfo - err := proto.Unmarshal(it.Value(), &f) - if err != nil { - fmt.Println("Unable to unmarshal FileInfo:", err) - success = false - continue - } - - fileInfos[fileInfoKey{folder, device, name}] = &f - - case db.KeyTypeGlobal: - folder := binary.BigEndian.Uint32(key[1:]) - name := nulString(key[1+4:]) - var flv dbproto.VersionList - if err := proto.Unmarshal(it.Value(), &flv); err != nil { - fmt.Println("Unable to unmarshal VersionList:", err) - success = false - continue - } - globals[globalKey{folder, name}] = &flv - - case db.KeyTypeFolderIdx: - key := binary.BigEndian.Uint32(it.Key()[1:]) - folders[key] = string(it.Value()) - - case db.KeyTypeDeviceIdx: - key := binary.BigEndian.Uint32(it.Key()[1:]) - devices[key] = string(it.Value()) - deviceToIDs[string(it.Value())] = key - if bytes.Equal(it.Value(), protocol.LocalDeviceID[:]) { - localDeviceKey = key - } - - case db.KeyTypeSequence: - folder := binary.BigEndian.Uint32(key[1:]) - seq := binary.BigEndian.Uint64(key[5:]) - val := it.Value() - sequences[sequenceKey{folder, seq}] = string(val[9:]) - - case db.KeyTypeNeed: - folder := binary.BigEndian.Uint32(key[1:]) - name := nulString(key[1+4:]) - needs[globalKey{folder, name}] = struct{}{} - - case db.KeyTypeBlockList: - hash := string(key[1:]) - blocklists[hash] = struct{}{} - - case db.KeyTypeVersion: - hash := string(key[1:]) - var v bep.Vector - if err := proto.Unmarshal(it.Value(), &v); err != nil { - fmt.Println("Unable to unmarshal Vector:", err) - success = false - continue - } - versions[hash] = &v - } - } - - if localDeviceKey == 0 { - fmt.Println("Missing key for local device in device index (bailing out)") - success = false - return - } - - var missingSeq []sequenceKey - for fk, fi := range fileInfos { - if fk.name != fi.Name { - fmt.Printf("Mismatching FileInfo name, %q (key) != %q (actual)\n", fk.name, fi.Name) - success = false - } - - folder := folders[fk.folder] - if folder == "" { - fmt.Printf("Unknown folder ID %d for FileInfo %q\n", fk.folder, fk.name) - success = false - continue - } - if devices[fk.device] == "" { - fmt.Printf("Unknown device ID %d for FileInfo %q, folder %q\n", fk.folder, fk.name, folder) - success = false - } - - if fk.device == localDeviceKey { - sk := sequenceKey{fk.folder, uint64(fi.Sequence)} - name, ok := sequences[sk] - if !ok { - fmt.Printf("Sequence entry missing for FileInfo %q, folder %q, seq %d\n", fi.Name, folder, fi.Sequence) - missingSeq = append(missingSeq, sk) - success = false - continue - } - if name != fi.Name { - fmt.Printf("Sequence entry refers to wrong name, %q (seq) != %q (FileInfo), folder %q, seq %d\n", name, fi.Name, folder, fi.Sequence) - success = false - } - } - - if len(fi.Blocks) == 0 && len(fi.BlocksHash) != 0 { - key := string(fi.BlocksHash) - if _, ok := blocklists[key]; !ok { - fmt.Printf("Missing block list for file %q, block list hash %x\n", fi.Name, fi.BlocksHash) - success = false - } else { - usedBlocklists[key] = struct{}{} - } - } - - if fi.VersionHash != nil { - key := string(fi.VersionHash) - if _, ok := versions[key]; !ok { - fmt.Printf("Missing version vector for file %q, version hash %x\n", fi.Name, fi.VersionHash) - success = false - } else { - usedVersions[key] = struct{}{} - } - } - - _, ok := globals[globalKey{fk.folder, fk.name}] - if !ok { - fmt.Printf("Missing global for file %q\n", fi.Name) - success = false - continue - } - } - - // Aggregate the ranges of missing sequence entries, print them - - sort.Slice(missingSeq, func(a, b int) bool { - if missingSeq[a].folder != missingSeq[b].folder { - return missingSeq[a].folder < missingSeq[b].folder - } - return missingSeq[a].sequence < missingSeq[b].sequence - }) - - var folder uint32 - var startSeq, prevSeq uint64 - for _, sk := range missingSeq { - if folder != sk.folder || sk.sequence != prevSeq+1 { - if folder != 0 { - fmt.Printf("Folder %d missing %d sequence entries: #%d - #%d\n", folder, prevSeq-startSeq+1, startSeq, prevSeq) - } - startSeq = sk.sequence - folder = sk.folder - } - prevSeq = sk.sequence - } - if folder != 0 { - fmt.Printf("Folder %d missing %d sequence entries: #%d - #%d\n", folder, prevSeq-startSeq+1, startSeq, prevSeq) - } - - for gk, vl := range globals { - folder := folders[gk.folder] - if folder == "" { - fmt.Printf("Unknown folder ID %d for VersionList %q\n", gk.folder, gk.name) - success = false - } - checkGlobal := func(i int, device []byte, version protocol.Vector, invalid, deleted bool) { - dev, ok := deviceToIDs[string(device)] - if !ok { - fmt.Printf("VersionList %q, folder %q refers to unknown device %q\n", gk.name, folder, device) - success = false - } - fi, ok := fileInfos[fileInfoKey{gk.folder, dev, gk.name}] - if !ok { - fmt.Printf("VersionList %q, folder %q, entry %d refers to unknown FileInfo\n", gk.name, folder, i) - success = false - } - - fiv := fi.Version - if fi.VersionHash != nil { - fiv = versions[string(fi.VersionHash)] - } - if !protocol.VectorFromWire(fiv).Equal(version) { - fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo version mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, version, fi.Version) - success = false - } - ffi := protocol.FileInfoFromDB(fi) - if ffi.IsInvalid() != invalid { - fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo invalid mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, invalid, ffi.IsInvalid()) - success = false - } - if ffi.IsDeleted() != deleted { - fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo deleted mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, deleted, ffi.IsDeleted()) - success = false - } - } - for i, fv := range vl.Versions { - ver := protocol.VectorFromWire(fv.Version) - for _, device := range fv.Devices { - checkGlobal(i, device, ver, false, fv.Deleted) - } - for _, device := range fv.InvalidDevices { - checkGlobal(i, device, ver, true, fv.Deleted) - } - } - - // If we need this file we should have a need entry for it. False - // positives from needsLocally for deleted files, where we might - // legitimately lack an entry if we never had it, and ignored files. - if needsLocally(vl) { - _, ok := needs[gk] - if !ok { - fv, _ := vlGetGlobal(vl) - devB, _ := fvFirstDevice(fv) - dev := deviceToIDs[string(devB)] - fi := protocol.FileInfoFromDB(fileInfos[fileInfoKey{gk.folder, dev, gk.name}]) - if !fi.IsDeleted() && !fi.IsIgnored() { - fmt.Printf("Missing need entry for needed file %q, folder %q\n", gk.name, folder) - } - } - } - } - - seenSeq := make(map[fileInfoKey]uint64) - for sk, name := range sequences { - folder := folders[sk.folder] - if folder == "" { - fmt.Printf("Unknown folder ID %d for sequence entry %d, %q\n", sk.folder, sk.sequence, name) - success = false - continue - } - - if prev, ok := seenSeq[fileInfoKey{folder: sk.folder, name: name}]; ok { - fmt.Printf("Duplicate sequence entry for %q, folder %q, seq %d (prev %d)\n", name, folder, sk.sequence, prev) - success = false - } - seenSeq[fileInfoKey{folder: sk.folder, name: name}] = sk.sequence - - fi, ok := fileInfos[fileInfoKey{sk.folder, localDeviceKey, name}] - if !ok { - fmt.Printf("Missing FileInfo for sequence entry %d, folder %q, %q\n", sk.sequence, folder, name) - success = false - continue - } - if fi.Sequence != int64(sk.sequence) { - fmt.Printf("Sequence mismatch for %q, folder %q, %d (key) != %d (FileInfo)\n", name, folder, sk.sequence, fi.Sequence) - success = false - } - } - - for nk := range needs { - folder := folders[nk.folder] - if folder == "" { - fmt.Printf("Unknown folder ID %d for need entry %q\n", nk.folder, nk.name) - success = false - continue - } - - vl, ok := globals[nk] - if !ok { - fmt.Printf("Missing global for need entry %q, folder %q\n", nk.name, folder) - success = false - continue - } - - if !needsLocally(vl) { - fmt.Printf("Need entry for file we don't need, %q, folder %q\n", nk.name, folder) - success = false - } - } - - if d := len(blocklists) - len(usedBlocklists); d > 0 { - fmt.Printf("%d block list entries out of %d needs GC\n", d, len(blocklists)) - } - if d := len(versions) - len(usedVersions); d > 0 { - fmt.Printf("%d version entries out of %d needs GC\n", d, len(versions)) - } - - return nil -} - -func needsLocally(vl *dbproto.VersionList) bool { - gfv, gok := vlGetGlobal(vl) - if !gok { // That's weird, but we hardly need something non-existent - return false - } - fv, ok := vlGet(vl, protocol.LocalDeviceID[:]) - return db.Need(gfv, ok, protocol.VectorFromWire(fv.Version)) -} - -// Get returns a FileVersion that contains the given device and whether it has -// been found at all. -func vlGet(vl *dbproto.VersionList, device []byte) (*dbproto.FileVersion, bool) { - _, i, _, ok := vlFindDevice(vl, device) - if !ok { - return &dbproto.FileVersion{}, false - } - return vl.Versions[i], true -} - -// GetGlobal returns the current global FileVersion. The returned FileVersion -// may be invalid, if all FileVersions are invalid. Returns false only if -// VersionList is empty. -func vlGetGlobal(vl *dbproto.VersionList) (*dbproto.FileVersion, bool) { - i := vlFindGlobal(vl) - if i == -1 { - return nil, false - } - return vl.Versions[i], true -} - -// findGlobal returns the first version that isn't invalid, or if all versions are -// invalid just the first version (i.e. 0) or -1, if there's no versions at all. -func vlFindGlobal(vl *dbproto.VersionList) int { - for i := range vl.Versions { - if !fvIsInvalid(vl.Versions[i]) { - return i - } - } - if len(vl.Versions) == 0 { - return -1 - } - return 0 -} - -// findDevice returns whether the device is in InvalidVersions or Versions and -// in InvalidDevices or Devices (true for invalid), the positions in the version -// and device slices and whether it has been found at all. -func vlFindDevice(vl *dbproto.VersionList, device []byte) (bool, int, int, bool) { - for i, v := range vl.Versions { - if j := deviceIndex(v.Devices, device); j != -1 { - return false, i, j, true - } - if j := deviceIndex(v.InvalidDevices, device); j != -1 { - return true, i, j, true - } - } - return false, -1, -1, false -} - -func deviceIndex(devices [][]byte, device []byte) int { - for i, dev := range devices { - if bytes.Equal(device, dev) { - return i - } - } - return -1 -} - -func fvFirstDevice(fv *dbproto.FileVersion) ([]byte, bool) { - if len(fv.Devices) != 0 { - return fv.Devices[0], true - } - if len(fv.InvalidDevices) != 0 { - return fv.InvalidDevices[0], true - } - return nil, false -} - -func fvIsInvalid(fv *dbproto.FileVersion) bool { - return fv == nil || len(fv.Devices) == 0 -} diff --git a/cmd/syncthing/cli/utils.go b/cmd/syncthing/cli/utils.go index 7b69bbf57..fde611575 100644 --- a/cmd/syncthing/cli/utils.go +++ b/cmd/syncthing/cli/utils.go @@ -17,8 +17,6 @@ import ( "path/filepath" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/locations" ) func responseToBArray(response *http.Response) ([]byte, error) { @@ -133,10 +131,6 @@ func prettyPrintResponse(response *http.Response) error { return prettyPrintJSON(data) } -func getDB() (backend.Backend, error) { - return backend.OpenLevelDBRO(locations.Get(locations.Database)) -} - func nulString(bs []byte) string { for i := range bs { if bs[i] == 0 { diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 1faff628e..740ac7c5b 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -22,6 +22,7 @@ import ( "path" "path/filepath" "regexp" + "runtime" "runtime/pprof" "sort" "strconv" @@ -38,10 +39,10 @@ import ( "github.com/syncthing/syncthing/cmd/syncthing/cmdutil" "github.com/syncthing/syncthing/cmd/syncthing/decrypt" "github.com/syncthing/syncthing/cmd/syncthing/generate" + "github.com/syncthing/syncthing/internal/db" _ "github.com/syncthing/syncthing/lib/automaxprocs" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" @@ -139,42 +140,41 @@ var entrypoint struct { // serveOptions are the options for the `syncthing serve` command. type serveOptions struct { cmdutil.CommonOptions - AllowNewerConfig bool `help:"Allow loading newer than current config version"` - Audit bool `help:"Write events to audit file"` - AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"` - BrowserOnly bool `help:"Open GUI in browser"` - DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"` - DeviceID bool `help:"Show the device ID"` - GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` // DEPRECATED: replaced by subcommand! - GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"` - GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"` - LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"` - LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"` - LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"` - LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"` - NoBrowser bool `help:"Do not start browser"` - NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"` - NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"` - Paths bool `help:"Show configuration paths"` - Paused bool `help:"Start with all devices and folders paused"` - Unpaused bool `help:"Start with all devices and folders unpaused"` - Upgrade bool `help:"Perform upgrade"` - UpgradeCheck bool `help:"Check for available upgrade"` - UpgradeTo string `placeholder:"URL" help:"Force upgrade directly from specified URL"` - Verbose bool `help:"Print verbose log output"` - Version bool `help:"Show version"` + AllowNewerConfig bool `help:"Allow loading newer than current config version"` + Audit bool `help:"Write events to audit file"` + AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"` + BrowserOnly bool `help:"Open GUI in browser"` + DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"` + DeviceID bool `help:"Show the device ID"` + GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` // DEPRECATED: replaced by subcommand! + GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"` + GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"` + LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"` + LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"` + LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"` + LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"` + NoBrowser bool `help:"Do not start browser"` + NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"` + NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"` + Paths bool `help:"Show configuration paths"` + Paused bool `help:"Start with all devices and folders paused"` + Unpaused bool `help:"Start with all devices and folders unpaused"` + Upgrade bool `help:"Perform upgrade"` + UpgradeCheck bool `help:"Check for available upgrade"` + UpgradeTo string `placeholder:"URL" help:"Force upgrade directly from specified URL"` + Verbose bool `help:"Print verbose log output"` + Version bool `help:"Show version"` + DBMaintenanceInterval time.Duration `env:"STDBMAINTINTERVAL" help:"Database maintenance interval" default:"8h"` // Debug options below - DebugDBIndirectGCInterval time.Duration `env:"STGCINDIRECTEVERY" help:"Database indirection GC interval"` - DebugDBRecheckInterval time.Duration `env:"STRECHECKDBEVERY" help:"Database metadata recalculation interval"` - DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"` - DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"` - DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"` - DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"STCPUPROFILE"` - DebugProfileHeap bool `env:"STHEAPPROFILE" help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases"` - DebugProfilerListen string `placeholder:"ADDR" env:"STPROFILER" help:"Network profiler listen address"` - DebugResetDatabase bool `name:"reset-database" help:"Reset the database, forcing a full rescan and resync"` - DebugResetDeltaIdxs bool `name:"reset-deltas" help:"Reset delta index IDs, forcing a full index exchange"` + DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"` + DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"` + DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"` + DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"STCPUPROFILE"` + DebugProfileHeap bool `env:"STHEAPPROFILE" help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases"` + DebugProfilerListen string `placeholder:"ADDR" env:"STPROFILER" help:"Network profiler listen address"` + DebugResetDatabase bool `name:"reset-database" help:"Reset the database, forcing a full rescan and resync"` + DebugResetDeltaIdxs bool `name:"reset-deltas" help:"Reset delta index IDs, forcing a full index exchange"` // Internal options, not shown to users InternalRestarting bool `env:"STRESTART" hidden:"1"` @@ -592,8 +592,12 @@ func syncthingMain(options serveOptions) { }) } - dbFile := locations.Get(locations.Database) - ldb, err := syncthing.OpenDBBackend(dbFile, cfgWrapper.Options().DatabaseTuning) + if err := syncthing.TryMigrateDatabase(); err != nil { + l.Warnln("Failed to migrate old-style database:", err) + os.Exit(1) + } + + sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database)) if err != nil { l.Warnln("Error opening database:", err) os.Exit(1) @@ -602,11 +606,11 @@ func syncthingMain(options serveOptions) { // Check if auto-upgrades is possible, and if yes, and it's enabled do an initial // upgrade immediately. The auto-upgrade routine can only be started // later after App is initialised. - autoUpgradePossible := autoUpgradePossible(options) if autoUpgradePossible && cfgWrapper.Options().AutoUpgradeEnabled() { // try to do upgrade directly and log the error if relevant. - release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb)) + miscDB := db.NewMiscDB(sdb) + release, err := initialAutoUpgradeCheck(miscDB) if err == nil { err = upgrade.To(release) } @@ -617,7 +621,7 @@ func syncthingMain(options serveOptions) { l.Infoln("Initial automatic upgrade:", err) } } else { - l.Infof("Upgraded to %q, exiting now.", release.Tag) + l.Infof("Upgraded to %q, should exit now.", release.Tag) os.Exit(svcutil.ExitUpgrade.AsInt()) } } @@ -629,24 +633,17 @@ func syncthingMain(options serveOptions) { } appOpts := syncthing.Options{ - NoUpgrade: options.NoUpgrade, - ProfilerAddr: options.DebugProfilerListen, - ResetDeltaIdxs: options.DebugResetDeltaIdxs, - Verbose: options.Verbose, - DBRecheckInterval: options.DebugDBRecheckInterval, - DBIndirectGCInterval: options.DebugDBIndirectGCInterval, + NoUpgrade: options.NoUpgrade, + ProfilerAddr: options.DebugProfilerListen, + ResetDeltaIdxs: options.DebugResetDeltaIdxs, + Verbose: options.Verbose, + DBMaintenanceInterval: options.DBMaintenanceInterval, } if options.Audit { appOpts.AuditWriter = auditWriter(options.AuditFile) } - if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil { - appOpts.DBRecheckInterval = dur - } - if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil { - appOpts.DBIndirectGCInterval = dur - } - app, err := syncthing.New(cfgWrapper, ldb, evLogger, cert, appOpts) + app, err := syncthing.New(cfgWrapper, sdb, evLogger, cert, appOpts) if err != nil { l.Warnln("Failed to start Syncthing:", err) os.Exit(svcutil.ExitError.AsInt()) @@ -692,6 +689,7 @@ func syncthingMain(options serveOptions) { pprof.StopCPUProfile() } + runtime.KeepAlive(lf) // ensure lock is still held to this point os.Exit(int(status)) } @@ -833,7 +831,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) } } -func initialAutoUpgradeCheck(misc *db.NamespacedKV) (upgrade.Release, error) { +func initialAutoUpgradeCheck(misc *db.Typed) (upgrade.Release, error) { if last, ok, err := misc.Time(upgradeCheckKey); err == nil && ok && time.Since(last) < upgradeCheckInterval { return upgrade.Release{}, errTooEarlyUpgradeCheck } diff --git a/go.mod b/go.mod index 4ebd75662..bd70f08e7 100644 --- a/go.mod +++ b/go.mod @@ -14,13 +14,14 @@ require ( github.com/go-ldap/ldap/v3 v3.4.10 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.12.1 - github.com/greatroar/blobloom v0.8.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/jackpal/gateway v1.0.16 github.com/jackpal/go-nat-pmp v1.0.2 + github.com/jmoiron/sqlx v1.4.0 github.com/julienschmidt/httprouter v1.3.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/maruel/panicparse/v2 v2.4.0 + github.com/mattn/go-sqlite3 v1.14.24 github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 github.com/maxmind/geoipupdate/v6 v6.1.0 github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 @@ -46,6 +47,7 @@ require ( golang.org/x/time v0.11.0 golang.org/x/tools v0.31.0 google.golang.org/protobuf v1.36.5 + modernc.org/sqlite v1.36.0 sigs.k8s.io/yaml v1.4.0 ) @@ -57,6 +59,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/ebitengine/purego v0.8.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect @@ -70,7 +73,9 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/oschwald/maxminddb-golang v1.13.1 // indirect @@ -81,6 +86,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -93,6 +99,9 @@ require ( golang.org/x/mod v0.24.0 // indirect golang.org/x/sync v0.12.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.61.13 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.8.2 // indirect ) // https://github.com/gobwas/glob/pull/55 diff --git a/go.sum b/go.sum index 2d7cbc8ae..4d740cd89 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f h1:GmH5lT+moM7PbAJFBq57nH9WJ+wRnBXr/tyaYWbSAx8= github.com/AudriusButkevicius/recli v0.0.7-0.20220911121932-d000ce8fbf0f/go.mod h1:Nhfib1j/VFnLrXL9cHgA+/n2O6P5THuWelOnbfPNd78= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= @@ -39,6 +41,8 @@ github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkE github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -58,6 +62,8 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -89,8 +95,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/greatroar/blobloom v0.8.0 h1:I9RlEkfqK9/6f1v9mFmDYegDQ/x0mISCpiNpAm23Pt4= -github.com/greatroar/blobloom v0.8.0/go.mod h1:mjMJ1hh1wjGVfr93QIHJ6FfDNVrA0IELv8OvMHJxHKs= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -126,6 +130,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -138,10 +144,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/maruel/panicparse/v2 v2.4.0 h1:yQKMIbQ0DKfinzVkTkcUzQyQ60UCiNnYfR7PWwTs2VI= github.com/maruel/panicparse/v2 v2.4.0/go.mod h1:nOY2OKe8csO3F3SA5+hsxot05JLgukrF54B9x88fVp4= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 h1:yVCLo4+ACVroOEr4iFU1iH46Ldlzz2rTuu18Ra7M8sU= github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ= github.com/maxmind/geoipupdate/v6 v6.1.0 h1:sdtTHzzQNJlXF5+fd/EoPTucRHyMonYt/Cok8xzzfqA= @@ -150,6 +163,8 @@ github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 h1:cUVxyR+U github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75/go.mod h1:pBbZyGwC5i16IBkjVKoy/sznA8jPD/K9iedwe1ESE6w= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= @@ -201,6 +216,8 @@ github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzuk github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= @@ -328,6 +345,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -397,5 +415,29 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= +modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= +modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= +modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= +modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= +modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8= +modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/gui/default/syncthing/core/aboutModalView.html b/gui/default/syncthing/core/aboutModalView.html index a58917d30..bb5a4dcab 100644 --- a/gui/default/syncthing/core/aboutModalView.html +++ b/gui/default/syncthing/core/aboutModalView.html @@ -59,9 +59,12 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Al
  • golang/protobuf, Copyright © 2010 The Go Authors.
  • golang/snappy, Copyright © 2011 The Snappy-Go Authors.
  • jackpal/gateway, Copyright © 2010 Jack Palevich.
  • +
  • jmoiron/sqlx, Copyright © 2013 Jason Moiron.
  • kballard/go-shellquote, Copyright © 2014 Kevin Ballard.
  • mattn/go-isatty, Copyright © Yasuhiro MATSUMOTO.
  • +
  • mattn/go-sqlite3, Copyright © 2014 Yasuhiro Matsumoto
  • matttproud/golang_protobuf_extensions, Copyright © 2012 Matt T. Proud.
  • +
  • modernc.org/sqlite, Copyright © 2017 The Sqlite Authors
  • oschwald/geoip2-golang, Copyright © 2015, Gregory J. Oschwald.
  • oschwald/maxminddb-golang, Copyright © 2015, Gregory J. Oschwald.
  • petermattis/goid, Copyright © 2015-2016 Peter Mattis.
  • diff --git a/internal/db/counts.go b/internal/db/counts.go new file mode 100644 index 000000000..a24ecdb2f --- /dev/null +++ b/internal/db/counts.go @@ -0,0 +1,73 @@ +// 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 https://mozilla.org/MPL/2.0/. + +package db + +import ( + "fmt" + "strings" + + "github.com/syncthing/syncthing/lib/protocol" +) + +type Counts struct { + Files int + Directories int + Symlinks int + Deleted int + Bytes int64 + Sequence int64 // zero for the global state + DeviceID protocol.DeviceID // device ID for remote devices, or special values for local/global + LocalFlags uint32 // the local flag for this count bucket +} + +func (c Counts) Add(other Counts) Counts { + return Counts{ + Files: c.Files + other.Files, + Directories: c.Directories + other.Directories, + Symlinks: c.Symlinks + other.Symlinks, + Deleted: c.Deleted + other.Deleted, + Bytes: c.Bytes + other.Bytes, + Sequence: c.Sequence + other.Sequence, + DeviceID: protocol.EmptyDeviceID, + LocalFlags: c.LocalFlags | other.LocalFlags, + } +} + +func (c Counts) TotalItems() int { + return c.Files + c.Directories + c.Symlinks + c.Deleted +} + +func (c Counts) String() string { + var flags strings.Builder + if c.LocalFlags&protocol.FlagLocalNeeded != 0 { + flags.WriteString("Need") + } + if c.LocalFlags&protocol.FlagLocalIgnored != 0 { + flags.WriteString("Ignored") + } + if c.LocalFlags&protocol.FlagLocalMustRescan != 0 { + flags.WriteString("Rescan") + } + if c.LocalFlags&protocol.FlagLocalReceiveOnly != 0 { + flags.WriteString("Recvonly") + } + if c.LocalFlags&protocol.FlagLocalUnsupported != 0 { + flags.WriteString("Unsupported") + } + if c.LocalFlags != 0 { + flags.WriteString(fmt.Sprintf("(%x)", c.LocalFlags)) + } + if flags.Len() == 0 { + flags.WriteString("---") + } + return fmt.Sprintf("{Device:%v, Files:%d, Dirs:%d, Symlinks:%d, Del:%d, Bytes:%d, Seq:%d, Flags:%s}", c.DeviceID, c.Files, c.Directories, c.Symlinks, c.Deleted, c.Bytes, c.Sequence, flags.String()) +} + +// Equal compares the numbers only, not sequence/dev/flags. +func (c Counts) Equal(o Counts) bool { + return c.Files == o.Files && c.Directories == o.Directories && c.Symlinks == o.Symlinks && c.Deleted == o.Deleted && c.Bytes == o.Bytes +} diff --git a/internal/db/interface.go b/internal/db/interface.go new file mode 100644 index 000000000..550143d73 --- /dev/null +++ b/internal/db/interface.go @@ -0,0 +1,123 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package db // import "github.com/syncthing/syncthing/internal/db/sqlite" + +import ( + "iter" + "time" + + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/thejerf/suture/v4" +) + +type DB interface { + Service(maintenanceInterval time.Duration) suture.Service + + // Basics + Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error + Close() error + + // Single files + GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) + GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error) + GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) + + // File iterators + // + // n.b. there is a slight inconsistency in the return types where some + // return a FileInfo iterator and some a FileMetadata iterator. The + // latter is more lightweight, and the discrepancy depends on how the + // functions tend to be used. We can introduce more variations as + // required. + AllGlobalFiles(folder string) (iter.Seq[FileMetadata], func() error) + AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[FileMetadata], func() error) + AllLocalBlocksWithHash(hash []byte) (iter.Seq[BlockMapEntry], func() error) + AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) + AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) + AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) + AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[FileMetadata], func() error) + AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, FileMetadata], func() error) + AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) + + // Cleanup + DropAllFiles(folder string, device protocol.DeviceID) error + DropDevice(device protocol.DeviceID) error + DropFilesNamed(folder string, device protocol.DeviceID, names []string) error + DropFolder(folder string) error + + // Various metadata + GetDeviceSequence(folder string, device protocol.DeviceID) (int64, error) + ListFolders() ([]string, error) + ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) + RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) + + // Counts + CountGlobal(folder string) (Counts, error) + CountLocal(folder string, device protocol.DeviceID) (Counts, error) + CountNeed(folder string, device protocol.DeviceID) (Counts, error) + CountReceiveOnlyChanged(folder string) (Counts, error) + + // Index IDs + DropAllIndexIDs() error + GetIndexID(folder string, device protocol.DeviceID) (protocol.IndexID, error) + SetIndexID(folder string, device protocol.DeviceID, id protocol.IndexID) error + + // MtimeFS + DeleteMtime(folder, name string) error + GetMtime(folder, name string) (ondisk, virtual time.Time) + PutMtime(folder, name string, ondisk, virtual time.Time) error + + KV +} + +// Generic KV store +type KV interface { + GetKV(key string) ([]byte, error) + PutKV(key string, val []byte) error + DeleteKV(key string) error + PrefixKV(prefix string) (iter.Seq[KeyValue], func() error) +} + +type BlockMapEntry struct { + BlocklistHash []byte + Offset int64 + BlockIndex int + Size int +} + +type KeyValue struct { + Key string + Value []byte +} + +type FileMetadata struct { + Name string + Sequence int64 + ModNanos int64 + Size int64 + LocalFlags int64 + Type protocol.FileInfoType + Deleted bool + Invalid bool +} + +func (f *FileMetadata) ModTime() time.Time { + return time.Unix(0, f.ModNanos) +} + +func (f *FileMetadata) IsReceiveOnlyChanged() bool { + return f.LocalFlags&protocol.FlagLocalReceiveOnly != 0 +} + +func (f *FileMetadata) IsDirectory() bool { + return f.Type == protocol.FileInfoTypeDirectory +} + +func (f *FileMetadata) ShouldConflict() bool { + return f.LocalFlags&protocol.LocalConflictFlags != 0 +} diff --git a/internal/db/metrics.go b/internal/db/metrics.go new file mode 100644 index 000000000..d59408dc4 --- /dev/null +++ b/internal/db/metrics.go @@ -0,0 +1,229 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package db + +import ( + "iter" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" +) + +var ( + metricCurrentOperations = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "syncthing", + Subsystem: "db", + Name: "operations_current", + }, []string{"folder", "operation"}) + metricTotalOperationSeconds = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "syncthing", + Subsystem: "db", + Name: "operation_seconds_total", + Help: "Total time spent in database operations, per folder and operation", + }, []string{"folder", "operation"}) + metricTotalOperationsCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "syncthing", + Subsystem: "db", + Name: "operations_total", + Help: "Total number of database operations, per folder and operation", + }, []string{"folder", "operation"}) + metricTotalFilesUpdatedCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "syncthing", + Subsystem: "db", + Name: "files_updated_total", + Help: "Total number of files updated", + }, []string{"folder"}) +) + +func MetricsWrap(db DB) DB { + return metricsDB{db} +} + +type metricsDB struct { + DB +} + +func (m metricsDB) account(folder, op string) func() { + t0 := time.Now() + metricCurrentOperations.WithLabelValues(folder, op).Inc() + return func() { + if dur := time.Since(t0).Seconds(); dur > 0 { + metricTotalOperationSeconds.WithLabelValues(folder, op).Add(dur) + } + metricTotalOperationsCount.WithLabelValues(folder, op).Inc() + metricCurrentOperations.WithLabelValues(folder, op).Dec() + } +} + +func (m metricsDB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[FileMetadata], func() error) { + defer m.account(folder, "AllLocalFilesWithBlocksHash")() + return m.DB.AllLocalFilesWithBlocksHash(folder, h) +} + +func (m metricsDB) AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, FileMetadata], func() error) { + defer m.account("-", "AllLocalFilesWithBlocksHashAnyFolder")() + return m.DB.AllLocalFilesWithBlocksHashAnyFolder(h) +} + +func (m metricsDB) AllGlobalFiles(folder string) (iter.Seq[FileMetadata], func() error) { + defer m.account(folder, "AllGlobalFiles")() + return m.DB.AllGlobalFiles(folder) +} + +func (m metricsDB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[FileMetadata], func() error) { + defer m.account(folder, "AllGlobalFilesPrefix")() + return m.DB.AllGlobalFilesPrefix(folder, prefix) +} + +func (m metricsDB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { + defer m.account(folder, "AllLocalFiles")() + return m.DB.AllLocalFiles(folder, device) +} + +func (m metricsDB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) { + defer m.account(folder, "AllLocalFilesPrefix")() + return m.DB.AllLocalFilesWithPrefix(folder, device, prefix) +} + +func (m metricsDB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) { + defer m.account(folder, "AllLocalFilesBySequence")() + return m.DB.AllLocalFilesBySequence(folder, device, startSeq, limit) +} + +func (m metricsDB) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) { + defer m.account(folder, "AllNeededGlobalFiles")() + return m.DB.AllNeededGlobalFiles(folder, device, order, limit, offset) +} + +func (m metricsDB) GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error) { + defer m.account(folder, "GetGlobalAvailability")() + return m.DB.GetGlobalAvailability(folder, file) +} + +func (m metricsDB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[BlockMapEntry], func() error) { + defer m.account("-", "AllLocalBlocksWithHash")() + return m.DB.AllLocalBlocksWithHash(hash) +} + +func (m metricsDB) Close() error { + defer m.account("-", "Close")() + return m.DB.Close() +} + +func (m metricsDB) ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) { + defer m.account(folder, "ListDevicesForFolder")() + return m.DB.ListDevicesForFolder(folder) +} + +func (m metricsDB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) { + defer m.account(folder, "RemoteSequences")() + return m.DB.RemoteSequences(folder) +} + +func (m metricsDB) DropAllFiles(folder string, device protocol.DeviceID) error { + defer m.account(folder, "DropAllFiles")() + return m.DB.DropAllFiles(folder, device) +} + +func (m metricsDB) DropDevice(device protocol.DeviceID) error { + defer m.account("-", "DropDevice")() + return m.DB.DropDevice(device) +} + +func (m metricsDB) DropFilesNamed(folder string, device protocol.DeviceID, names []string) error { + defer m.account(folder, "DropFilesNamed")() + return m.DB.DropFilesNamed(folder, device, names) +} + +func (m metricsDB) DropFolder(folder string) error { + defer m.account(folder, "DropFolder")() + return m.DB.DropFolder(folder) +} + +func (m metricsDB) DropAllIndexIDs() error { + defer m.account("-", "IndexIDDropAll")() + return m.DB.DropAllIndexIDs() +} + +func (m metricsDB) ListFolders() ([]string, error) { + defer m.account("-", "ListFolders")() + return m.DB.ListFolders() +} + +func (m metricsDB) GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) { + defer m.account(folder, "GetGlobalFile")() + return m.DB.GetGlobalFile(folder, file) +} + +func (m metricsDB) CountGlobal(folder string) (Counts, error) { + defer m.account(folder, "CountGlobal")() + return m.DB.CountGlobal(folder) +} + +func (m metricsDB) GetIndexID(folder string, device protocol.DeviceID) (protocol.IndexID, error) { + defer m.account(folder, "IndexIDGet")() + return m.DB.GetIndexID(folder, device) +} + +func (m metricsDB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) { + defer m.account(folder, "GetDeviceFile")() + return m.DB.GetDeviceFile(folder, device, file) +} + +func (m metricsDB) CountLocal(folder string, device protocol.DeviceID) (Counts, error) { + defer m.account(folder, "CountLocal")() + return m.DB.CountLocal(folder, device) +} + +func (m metricsDB) CountNeed(folder string, device protocol.DeviceID) (Counts, error) { + defer m.account(folder, "CountNeed")() + return m.DB.CountNeed(folder, device) +} + +func (m metricsDB) CountReceiveOnlyChanged(folder string) (Counts, error) { + defer m.account(folder, "CountReceiveOnlyChanged")() + return m.DB.CountReceiveOnlyChanged(folder) +} + +func (m metricsDB) GetDeviceSequence(folder string, device protocol.DeviceID) (int64, error) { + defer m.account(folder, "GetDeviceSequence")() + return m.DB.GetDeviceSequence(folder, device) +} + +func (m metricsDB) SetIndexID(folder string, device protocol.DeviceID, id protocol.IndexID) error { + defer m.account(folder, "IndexIDSet")() + return m.DB.SetIndexID(folder, device, id) +} + +func (m metricsDB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error { + defer m.account(folder, "Update")() + defer metricTotalFilesUpdatedCount.WithLabelValues(folder).Add(float64(len(fs))) + return m.DB.Update(folder, device, fs) +} + +func (m metricsDB) GetKV(key string) ([]byte, error) { + defer m.account("-", "GetKV")() + return m.DB.GetKV(key) +} + +func (m metricsDB) PutKV(key string, val []byte) error { + defer m.account("-", "PutKV")() + return m.DB.PutKV(key, val) +} + +func (m metricsDB) DeleteKV(key string) error { + defer m.account("-", "DeleteKV")() + return m.DB.DeleteKV(key) +} + +func (m metricsDB) PrefixKV(prefix string) (iter.Seq[KeyValue], func() error) { + defer m.account("-", "PrefixKV")() + return m.DB.PrefixKV(prefix) +} diff --git a/lib/db/observed.go b/internal/db/observed.go similarity index 55% rename from lib/db/observed.go rename to internal/db/observed.go index 47705b613..04aa64f4a 100644 --- a/lib/db/observed.go +++ b/internal/db/observed.go @@ -8,6 +8,7 @@ package db import ( "fmt" + "strings" "time" "google.golang.org/protobuf/proto" @@ -17,6 +18,14 @@ import ( "github.com/syncthing/syncthing/lib/protocol" ) +type ObservedDB struct { + kv KV +} + +func NewObservedDB(kv KV) *ObservedDB { + return &ObservedDB{kv: kv} +} + type ObservedFolder struct { Time time.Time `json:"time"` Label string `json:"label"` @@ -52,39 +61,42 @@ func (o *ObservedDevice) fromWire(w *dbproto.ObservedDevice) { o.Address = w.GetAddress() } -func (db *Lowlevel) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) error { - key := db.keyer.GeneratePendingDeviceKey(nil, device[:]) +func (db *ObservedDB) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) error { + key := "device/" + device.String() od := &dbproto.ObservedDevice{ Time: timestamppb.New(time.Now().Truncate(time.Second)), Name: name, Address: address, } - return db.Put(key, mustMarshal(od)) + return db.kv.PutKV(key, mustMarshal(od)) } -func (db *Lowlevel) RemovePendingDevice(device protocol.DeviceID) error { - key := db.keyer.GeneratePendingDeviceKey(nil, device[:]) - return db.Delete(key) +func (db *ObservedDB) RemovePendingDevice(device protocol.DeviceID) error { + key := "device/" + device.String() + return db.kv.DeleteKV(key) } // PendingDevices enumerates all entries. Invalid ones are dropped from the database // after a warning log message, as a side-effect. -func (db *Lowlevel) PendingDevices() (map[protocol.DeviceID]ObservedDevice, error) { - iter, err := db.NewPrefixIterator([]byte{KeyTypePendingDevice}) - if err != nil { - return nil, err - } - defer iter.Release() +func (db *ObservedDB) PendingDevices() (map[protocol.DeviceID]ObservedDevice, error) { res := make(map[protocol.DeviceID]ObservedDevice) - for iter.Next() { - keyDev := db.keyer.DeviceFromPendingDeviceKey(iter.Key()) - deviceID, err := protocol.DeviceIDFromBytes(keyDev) + it, errFn := db.kv.PrefixKV("device/") + for kv := range it { + _, keyDev, ok := strings.Cut(kv.Key, "/") + if !ok { + if err := db.kv.DeleteKV(kv.Key); err != nil { + return nil, fmt.Errorf("delete invalid pending device: %w", err) + } + continue + } + + deviceID, err := protocol.DeviceIDFromString(keyDev) var protoD dbproto.ObservedDevice var od ObservedDevice if err != nil { goto deleteKey } - if err = proto.Unmarshal(iter.Value(), &protoD); err != nil { + if err = proto.Unmarshal(kv.Value, &protoD); err != nil { goto deleteKey } od.fromWire(&protoD) @@ -94,52 +106,37 @@ func (db *Lowlevel) PendingDevices() (map[protocol.DeviceID]ObservedDevice, erro // Deleting invalid entries is the only possible "repair" measure and // appropriate for the importance of pending entries. They will come back // soon if still relevant. - l.Infof("Invalid pending device entry, deleting from database: %x", iter.Key()) - if err := db.Delete(iter.Key()); err != nil { - return nil, err + if err := db.kv.DeleteKV(kv.Key); err != nil { + return nil, fmt.Errorf("delete invalid pending device: %w", err) } } - return res, nil + return res, errFn() } -func (db *Lowlevel) AddOrUpdatePendingFolder(id string, of ObservedFolder, device protocol.DeviceID) error { - key, err := db.keyer.GeneratePendingFolderKey(nil, device[:], []byte(id)) - if err != nil { - return err - } - return db.Put(key, mustMarshal(of.toWire())) +func (db *ObservedDB) AddOrUpdatePendingFolder(id string, of ObservedFolder, device protocol.DeviceID) error { + key := "folder/" + device.String() + "/" + id + return db.kv.PutKV(key, mustMarshal(of.toWire())) } // RemovePendingFolderForDevice removes entries for specific folder / device combinations. -func (db *Lowlevel) RemovePendingFolderForDevice(id string, device protocol.DeviceID) error { - key, err := db.keyer.GeneratePendingFolderKey(nil, device[:], []byte(id)) - if err != nil { - return err - } - return db.Delete(key) +func (db *ObservedDB) RemovePendingFolderForDevice(id string, device protocol.DeviceID) error { + key := "folder/" + device.String() + "/" + id + return db.kv.DeleteKV(key) } // RemovePendingFolder removes all entries matching a specific folder ID. -func (db *Lowlevel) RemovePendingFolder(id string) error { - iter, err := db.NewPrefixIterator([]byte{KeyTypePendingFolder}) - if err != nil { - return fmt.Errorf("creating iterator: %w", err) - } - defer iter.Release() - var iterErr error - for iter.Next() { - if id != string(db.keyer.FolderFromPendingFolderKey(iter.Key())) { +func (db *ObservedDB) RemovePendingFolder(id string) error { + it, errFn := db.kv.PrefixKV("folder/") + for kv := range it { + parts := strings.Split(kv.Key, "/") + if len(parts) != 3 || parts[2] != id { continue } - if err = db.Delete(iter.Key()); err != nil { - if iterErr != nil { - l.Debugf("Repeat error removing pending folder: %v", err) - } else { - iterErr = err - } + if err := db.kv.DeleteKV(kv.Key); err != nil { + return fmt.Errorf("delete pending folder: %w", err) } } - return iterErr + return errFn() } // Consolidated information about a pending folder @@ -147,41 +144,37 @@ type PendingFolder struct { OfferedBy map[protocol.DeviceID]ObservedFolder `json:"offeredBy"` } -func (db *Lowlevel) PendingFolders() (map[string]PendingFolder, error) { +func (db *ObservedDB) PendingFolders() (map[string]PendingFolder, error) { return db.PendingFoldersForDevice(protocol.EmptyDeviceID) } // PendingFoldersForDevice enumerates only entries matching the given device ID, unless it // is EmptyDeviceID. Invalid ones are dropped from the database after a info log // message, as a side-effect. -func (db *Lowlevel) PendingFoldersForDevice(device protocol.DeviceID) (map[string]PendingFolder, error) { - var err error - prefixKey := []byte{KeyTypePendingFolder} +func (db *ObservedDB) PendingFoldersForDevice(device protocol.DeviceID) (map[string]PendingFolder, error) { + prefix := "folder/" if device != protocol.EmptyDeviceID { - prefixKey, err = db.keyer.GeneratePendingFolderKey(nil, device[:], nil) - if err != nil { - return nil, err - } + prefix += device.String() + "/" } - iter, err := db.NewPrefixIterator(prefixKey) - if err != nil { - return nil, err - } - defer iter.Release() res := make(map[string]PendingFolder) - for iter.Next() { - keyDev, ok := db.keyer.DeviceFromPendingFolderKey(iter.Key()) - deviceID, err := protocol.DeviceIDFromBytes(keyDev) + it, errFn := db.kv.PrefixKV(prefix) + for kv := range it { + parts := strings.Split(kv.Key, "/") + if len(parts) != 3 { + continue + } + keyDev := parts[1] + deviceID, err := protocol.DeviceIDFromString(keyDev) var protoF dbproto.ObservedFolder var of ObservedFolder var folderID string - if !ok || err != nil { + if err != nil { goto deleteKey } - if folderID = string(db.keyer.FolderFromPendingFolderKey(iter.Key())); len(folderID) < 1 { + if folderID = parts[2]; len(folderID) < 1 { goto deleteKey } - if err = proto.Unmarshal(iter.Value(), &protoF); err != nil { + if err = proto.Unmarshal(kv.Value, &protoF); err != nil { goto deleteKey } if _, ok := res[folderID]; !ok { @@ -196,10 +189,17 @@ func (db *Lowlevel) PendingFoldersForDevice(device protocol.DeviceID) (map[strin // Deleting invalid entries is the only possible "repair" measure and // appropriate for the importance of pending entries. They will come back // soon if still relevant. - l.Infof("Invalid pending folder entry, deleting from database: %x", iter.Key()) - if err := db.Delete(iter.Key()); err != nil { - return nil, err + if err := db.kv.DeleteKV(kv.Key); err != nil { + return nil, fmt.Errorf("delete invalid pending folder: %w", err) } } - return res, nil + return res, errFn() +} + +func mustMarshal(m proto.Message) []byte { + bs, err := proto.Marshal(m) + if err != nil { + panic(err) + } + return bs } diff --git a/lib/db/backend/backend.go b/internal/db/olddb/backend/backend.go similarity index 93% rename from lib/db/backend/backend.go rename to internal/db/olddb/backend/backend.go index 0ab1795eb..789684d91 100644 --- a/lib/db/backend/backend.go +++ b/internal/db/olddb/backend/backend.go @@ -108,29 +108,8 @@ type Iterator interface { // is empty for a db in memory. type Backend interface { Reader - Writer NewReadTransaction() (ReadTransaction, error) - NewWriteTransaction(hooks ...CommitHook) (WriteTransaction, error) Close() error - Compact() error - Location() string -} - -type Tuning int - -const ( - // N.b. these constants must match those in lib/config.Tuning! - TuningAuto Tuning = iota - TuningSmall - TuningLarge -) - -func Open(path string, tuning Tuning) (Backend, error) { - return OpenLevelDB(path, tuning) -} - -func OpenMemory() Backend { - return OpenLevelDBMemory() } var ( diff --git a/internal/db/olddb/backend/leveldb_backend.go b/internal/db/olddb/backend/leveldb_backend.go new file mode 100644 index 000000000..c44eb285c --- /dev/null +++ b/internal/db/olddb/backend/leveldb_backend.go @@ -0,0 +1,113 @@ +// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. + +package backend + +import ( + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/iterator" + "github.com/syndtr/goleveldb/leveldb/util" +) + +// leveldbBackend implements Backend on top of a leveldb +type leveldbBackend struct { + ldb *leveldb.DB + closeWG *closeWaitGroup + location string +} + +func newLeveldbBackend(ldb *leveldb.DB, location string) *leveldbBackend { + return &leveldbBackend{ + ldb: ldb, + closeWG: &closeWaitGroup{}, + location: location, + } +} + +func (b *leveldbBackend) NewReadTransaction() (ReadTransaction, error) { + return b.newSnapshot() +} + +func (b *leveldbBackend) newSnapshot() (leveldbSnapshot, error) { + rel, err := newReleaser(b.closeWG) + if err != nil { + return leveldbSnapshot{}, err + } + snap, err := b.ldb.GetSnapshot() + if err != nil { + rel.Release() + return leveldbSnapshot{}, wrapLeveldbErr(err) + } + return leveldbSnapshot{ + snap: snap, + rel: rel, + }, nil +} + +func (b *leveldbBackend) Close() error { + b.closeWG.CloseWait() + return wrapLeveldbErr(b.ldb.Close()) +} + +func (b *leveldbBackend) Get(key []byte) ([]byte, error) { + val, err := b.ldb.Get(key, nil) + return val, wrapLeveldbErr(err) +} + +func (b *leveldbBackend) NewPrefixIterator(prefix []byte) (Iterator, error) { + return &leveldbIterator{b.ldb.NewIterator(util.BytesPrefix(prefix), nil)}, nil +} + +func (b *leveldbBackend) NewRangeIterator(first, last []byte) (Iterator, error) { + return &leveldbIterator{b.ldb.NewIterator(&util.Range{Start: first, Limit: last}, nil)}, nil +} + +func (b *leveldbBackend) Location() string { + return b.location +} + +// leveldbSnapshot implements backend.ReadTransaction +type leveldbSnapshot struct { + snap *leveldb.Snapshot + rel *releaser +} + +func (l leveldbSnapshot) Get(key []byte) ([]byte, error) { + val, err := l.snap.Get(key, nil) + return val, wrapLeveldbErr(err) +} + +func (l leveldbSnapshot) NewPrefixIterator(prefix []byte) (Iterator, error) { + return l.snap.NewIterator(util.BytesPrefix(prefix), nil), nil +} + +func (l leveldbSnapshot) NewRangeIterator(first, last []byte) (Iterator, error) { + return l.snap.NewIterator(&util.Range{Start: first, Limit: last}, nil), nil +} + +func (l leveldbSnapshot) Release() { + l.snap.Release() + l.rel.Release() +} + +type leveldbIterator struct { + iterator.Iterator +} + +func (it *leveldbIterator) Error() error { + return wrapLeveldbErr(it.Iterator.Error()) +} + +// wrapLeveldbErr wraps errors so that the backend package can recognize them +func wrapLeveldbErr(err error) error { + switch err { + case leveldb.ErrClosed: + return errClosed + case leveldb.ErrNotFound: + return errNotFound + } + return err +} diff --git a/internal/db/olddb/backend/leveldb_open.go b/internal/db/olddb/backend/leveldb_open.go new file mode 100644 index 000000000..344426ae0 --- /dev/null +++ b/internal/db/olddb/backend/leveldb_open.go @@ -0,0 +1,32 @@ +// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. + +package backend + +import ( + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" +) + +const dbMaxOpenFiles = 100 + +// OpenLevelDBRO attempts to open the database at the given location, read +// only. +func OpenLevelDBRO(location string) (Backend, error) { + opts := &opt.Options{ + OpenFilesCacheCapacity: dbMaxOpenFiles, + ReadOnly: true, + } + ldb, err := open(location, opts) + if err != nil { + return nil, err + } + return newLeveldbBackend(ldb, location), nil +} + +func open(location string, opts *opt.Options) (*leveldb.DB, error) { + return leveldb.OpenFile(location, opts) +} diff --git a/lib/db/keyer.go b/internal/db/olddb/keyer.go similarity index 99% rename from lib/db/keyer.go rename to internal/db/olddb/keyer.go index 137b5cc29..3c335fd52 100644 --- a/lib/db/keyer.go +++ b/internal/db/olddb/keyer.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package db +package olddb import ( "encoding/binary" diff --git a/internal/db/olddb/lowlevel.go b/internal/db/olddb/lowlevel.go new file mode 100644 index 000000000..3afad4c76 --- /dev/null +++ b/internal/db/olddb/lowlevel.go @@ -0,0 +1,70 @@ +// 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 https://mozilla.org/MPL/2.0/. + +package olddb + +import ( + "encoding/binary" + "time" + + "github.com/syncthing/syncthing/internal/db/olddb/backend" +) + +// deprecatedLowlevel is the lowest level database interface. It has a very simple +// purpose: hold the actual backend database, and the in-memory state +// that belong to that database. In the same way that a single on disk +// database can only be opened once, there should be only one deprecatedLowlevel for +// any given backend. +type deprecatedLowlevel struct { + backend.Backend + folderIdx *smallIndex + deviceIdx *smallIndex + keyer keyer +} + +func NewLowlevel(backend backend.Backend) (*deprecatedLowlevel, error) { + // Only log restarts in debug mode. + db := &deprecatedLowlevel{ + Backend: backend, + folderIdx: newSmallIndex(backend, []byte{KeyTypeFolderIdx}), + deviceIdx: newSmallIndex(backend, []byte{KeyTypeDeviceIdx}), + } + db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx) + return db, nil +} + +// ListFolders returns the list of folders currently in the database +func (db *deprecatedLowlevel) ListFolders() []string { + return db.folderIdx.Values() +} + +func (db *deprecatedLowlevel) IterateMtimes(fn func(folder, name string, ondisk, virtual time.Time) error) error { + it, err := db.NewPrefixIterator([]byte{KeyTypeVirtualMtime}) + if err != nil { + return err + } + defer it.Release() + for it.Next() { + key := it.Key()[1:] + folderID, ok := db.folderIdx.Val(binary.BigEndian.Uint32(key)) + if !ok { + continue + } + name := key[4:] + val := it.Value() + var ondisk, virtual time.Time + if err := ondisk.UnmarshalBinary(val[:len(val)/2]); err != nil { + continue + } + if err := virtual.UnmarshalBinary(val[len(val)/2:]); err != nil { + continue + } + if err := fn(string(folderID), string(name), ondisk, virtual); err != nil { + return err + } + } + return it.Error() +} diff --git a/internal/db/olddb/set.go b/internal/db/olddb/set.go new file mode 100644 index 000000000..be16e23cc --- /dev/null +++ b/internal/db/olddb/set.go @@ -0,0 +1,67 @@ +// 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 https://mozilla.org/MPL/2.0/. + +// Package db provides a set type to track local/remote files with newness +// checks. We must do a certain amount of normalization in here. We will get +// fed paths with either native or wire-format separators and encodings +// depending on who calls us. We transform paths to wire-format (NFC and +// slashes) on the way to the database, and transform to native format +// (varying separator and encoding) on the way back out. +package olddb + +import ( + "github.com/syncthing/syncthing/lib/osutil" + "github.com/syncthing/syncthing/lib/protocol" +) + +type deprecatedFileSet struct { + folder string + db *deprecatedLowlevel +} + +// The Iterator is called with either a protocol.FileInfo or a +// FileInfoTruncated (depending on the method) and returns true to +// continue iteration, false to stop. +type Iterator func(f protocol.FileInfo) bool + +func NewFileSet(folder string, db *deprecatedLowlevel) (*deprecatedFileSet, error) { + s := &deprecatedFileSet{ + folder: folder, + db: db, + } + return s, nil +} + +type Snapshot struct { + folder string + t readOnlyTransaction +} + +func (s *deprecatedFileSet) Snapshot() (*Snapshot, error) { + t, err := s.db.newReadOnlyTransaction() + if err != nil { + return nil, err + } + return &Snapshot{ + folder: s.folder, + t: t, + }, nil +} + +func (s *Snapshot) Release() { + s.t.close() +} + +func (s *Snapshot) WithHaveSequence(startSeq int64, fn Iterator) error { + return s.t.withHaveSequence([]byte(s.folder), startSeq, nativeFileIterator(fn)) +} + +func nativeFileIterator(fn Iterator) Iterator { + return func(fi protocol.FileInfo) bool { + fi.Name = osutil.NativeFilename(fi.Name) + return fn(fi) + } +} diff --git a/lib/db/smallindex.go b/internal/db/olddb/smallindex.go similarity index 71% rename from lib/db/smallindex.go rename to internal/db/olddb/smallindex.go index b426d906b..4182996da 100644 --- a/lib/db/smallindex.go +++ b/internal/db/olddb/smallindex.go @@ -4,13 +4,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package db +package olddb import ( "encoding/binary" "sort" - "github.com/syncthing/syncthing/lib/db/backend" + "github.com/syncthing/syncthing/internal/db/olddb/backend" "github.com/syncthing/syncthing/lib/sync" ) @@ -74,23 +74,7 @@ func (i *smallIndex) ID(val []byte) (uint32, error) { return id, nil } - id := i.nextID - i.nextID++ - - valStr := string(val) - i.val2id[valStr] = id - i.id2val[id] = valStr - - key := make([]byte, len(i.prefix)+8) // prefix plus uint32 id - copy(key, i.prefix) - binary.BigEndian.PutUint32(key[len(i.prefix):], id) - if err := i.db.Put(key, val); err != nil { - i.mut.Unlock() - return 0, err - } - - i.mut.Unlock() - return id, nil + panic("missing ID") } // Val returns the value for the given index number, or (nil, false) if there @@ -106,33 +90,6 @@ func (i *smallIndex) Val(id uint32) ([]byte, bool) { return []byte(val), true } -func (i *smallIndex) Delete(val []byte) error { - i.mut.Lock() - defer i.mut.Unlock() - - // Check the reverse mapping to get the ID for the value. - if id, ok := i.val2id[string(val)]; ok { - // Generate the corresponding database key. - key := make([]byte, len(i.prefix)+8) // prefix plus uint32 id - copy(key, i.prefix) - binary.BigEndian.PutUint32(key[len(i.prefix):], id) - - // Put an empty value into the database. This indicates that the - // entry does not exist any more and prevents the ID from being - // reused in the future. - if err := i.db.Put(key, []byte{}); err != nil { - return err - } - - // Delete reverse mapping. - delete(i.id2val, id) - } - - // Delete forward mapping. - delete(i.val2id, string(val)) - return nil -} - // Values returns the set of values in the index func (i *smallIndex) Values() []string { // In principle this method should return [][]byte because all the other diff --git a/internal/db/olddb/transactions.go b/internal/db/olddb/transactions.go new file mode 100644 index 000000000..6f6006b06 --- /dev/null +++ b/internal/db/olddb/transactions.go @@ -0,0 +1,193 @@ +// 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 https://mozilla.org/MPL/2.0/. + +package olddb + +import ( + "fmt" + + "google.golang.org/protobuf/proto" + + "github.com/syncthing/syncthing/internal/db/olddb/backend" + "github.com/syncthing/syncthing/internal/gen/bep" + "github.com/syncthing/syncthing/internal/gen/dbproto" + "github.com/syncthing/syncthing/lib/protocol" +) + +// A readOnlyTransaction represents a database snapshot. +type readOnlyTransaction struct { + backend.ReadTransaction + keyer keyer +} + +func (db *deprecatedLowlevel) newReadOnlyTransaction() (readOnlyTransaction, error) { + tran, err := db.NewReadTransaction() + if err != nil { + return readOnlyTransaction{}, err + } + return db.readOnlyTransactionFromBackendTransaction(tran), nil +} + +func (db *deprecatedLowlevel) readOnlyTransactionFromBackendTransaction(tran backend.ReadTransaction) readOnlyTransaction { + return readOnlyTransaction{ + ReadTransaction: tran, + keyer: db.keyer, + } +} + +func (t readOnlyTransaction) close() { + t.Release() +} + +func (t readOnlyTransaction) getFileByKey(key []byte) (protocol.FileInfo, bool, error) { + f, ok, err := t.getFileTrunc(key, false) + if err != nil || !ok { + return protocol.FileInfo{}, false, err + } + return f, true, nil +} + +func (t readOnlyTransaction) getFileTrunc(key []byte, trunc bool) (protocol.FileInfo, bool, error) { + bs, err := t.Get(key) + if backend.IsNotFound(err) { + return protocol.FileInfo{}, false, nil + } + if err != nil { + return protocol.FileInfo{}, false, err + } + f, err := t.unmarshalTrunc(bs, trunc) + if backend.IsNotFound(err) { + return protocol.FileInfo{}, false, nil + } + if err != nil { + return protocol.FileInfo{}, false, err + } + return f, true, nil +} + +func (t readOnlyTransaction) unmarshalTrunc(bs []byte, trunc bool) (protocol.FileInfo, error) { + if trunc { + var bfi dbproto.FileInfoTruncated + err := proto.Unmarshal(bs, &bfi) + if err != nil { + return protocol.FileInfo{}, err + } + if err := t.fillTruncated(&bfi); err != nil { + return protocol.FileInfo{}, err + } + return protocol.FileInfoFromDBTruncated(&bfi), nil + } + + var bfi bep.FileInfo + err := proto.Unmarshal(bs, &bfi) + if err != nil { + return protocol.FileInfo{}, err + } + if err := t.fillFileInfo(&bfi); err != nil { + return protocol.FileInfo{}, err + } + return protocol.FileInfoFromDB(&bfi), nil +} + +type blocksIndirectionError struct { + err error +} + +func (e *blocksIndirectionError) Error() string { + return fmt.Sprintf("filling Blocks: %v", e.err) +} + +func (e *blocksIndirectionError) Unwrap() error { + return e.err +} + +// fillFileInfo follows the (possible) indirection of blocks and version +// vector and fills it out. +func (t readOnlyTransaction) fillFileInfo(fi *bep.FileInfo) error { + var key []byte + + if len(fi.Blocks) == 0 && len(fi.BlocksHash) != 0 { + // The blocks list is indirected and we need to load it. + key = t.keyer.GenerateBlockListKey(key, fi.BlocksHash) + bs, err := t.Get(key) + if err != nil { + return &blocksIndirectionError{err} + } + var bl dbproto.BlockList + if err := proto.Unmarshal(bs, &bl); err != nil { + return err + } + fi.Blocks = bl.Blocks + } + + if len(fi.VersionHash) != 0 { + key = t.keyer.GenerateVersionKey(key, fi.VersionHash) + bs, err := t.Get(key) + if err != nil { + return fmt.Errorf("filling Version: %w", err) + } + var v bep.Vector + if err := proto.Unmarshal(bs, &v); err != nil { + return err + } + fi.Version = &v + } + + return nil +} + +// fillTruncated follows the (possible) indirection of version vector and +// fills it. +func (t readOnlyTransaction) fillTruncated(fi *dbproto.FileInfoTruncated) error { + var key []byte + + if len(fi.VersionHash) == 0 { + return nil + } + + key = t.keyer.GenerateVersionKey(key, fi.VersionHash) + bs, err := t.Get(key) + if err != nil { + return err + } + var v bep.Vector + if err := proto.Unmarshal(bs, &v); err != nil { + return err + } + fi.Version = &v + return nil +} + +func (t *readOnlyTransaction) withHaveSequence(folder []byte, startSeq int64, fn Iterator) error { + first, err := t.keyer.GenerateSequenceKey(nil, folder, startSeq) + if err != nil { + return err + } + last, err := t.keyer.GenerateSequenceKey(nil, folder, maxInt64) + if err != nil { + return err + } + dbi, err := t.NewRangeIterator(first, last) + if err != nil { + return err + } + defer dbi.Release() + + for dbi.Next() { + f, ok, err := t.getFileByKey(dbi.Value()) + if err != nil { + return err + } + if !ok { + continue + } + + if !fn(f) { + return nil + } + } + return dbi.Error() +} diff --git a/internal/db/sqlite/db.go b/internal/db/sqlite/db.go new file mode 100644 index 000000000..cb2da41d8 --- /dev/null +++ b/internal/db/sqlite/db.go @@ -0,0 +1,77 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "sync" + "time" + + "github.com/jmoiron/sqlx" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/thejerf/suture/v4" +) + +type DB struct { + sql *sqlx.DB + localDeviceIdx int64 + updateLock sync.Mutex + + statementsMut sync.RWMutex + statements map[string]*sqlx.Stmt + tplInput map[string]any +} + +var _ db.DB = (*DB)(nil) + +func (s *DB) Close() error { + s.updateLock.Lock() + s.statementsMut.Lock() + defer s.updateLock.Unlock() + defer s.statementsMut.Unlock() + for _, stmt := range s.statements { + stmt.Close() + } + return wrap(s.sql.Close()) +} + +func (s *DB) Service(maintenanceInterval time.Duration) suture.Service { + return newService(s, maintenanceInterval) +} + +func (s *DB) ListFolders() ([]string, error) { + var res []string + err := s.stmt(` + SELECT folder_id FROM folders + ORDER BY folder_id + `).Select(&res) + return res, wrap(err) +} + +func (s *DB) ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) { + var res []string + err := s.stmt(` + SELECT d.device_id FROM counts s + INNER JOIN folders o ON o.idx = s.folder_idx + INNER JOIN devices d ON d.idx = s.device_idx + WHERE o.folder_id = ? AND s.count > 0 AND s.device_idx != {{.LocalDeviceIdx}} + GROUP BY d.device_id + ORDER BY d.device_id + `).Select(&res, folder) + if err != nil { + return nil, wrap(err) + } + + devs := make([]protocol.DeviceID, len(res)) + for i, s := range res { + devs[i], err = protocol.DeviceIDFromString(s) + if err != nil { + return nil, wrap(err) + } + } + return devs, nil +} diff --git a/internal/db/sqlite/db_bench_test.go b/internal/db/sqlite/db_bench_test.go new file mode 100644 index 000000000..5e0f79574 --- /dev/null +++ b/internal/db/sqlite/db_bench_test.go @@ -0,0 +1,243 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/syncthing/syncthing/internal/timeutil" + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/rand" +) + +var globalFi protocol.FileInfo + +func BenchmarkUpdate(b *testing.B) { + db, err := OpenTemp() + if err != nil { + b.Fatal(err) + } + b.Cleanup(func() { + if err := db.Close(); err != nil { + b.Fatal(err) + } + }) + svc := db.Service(time.Hour).(*Service) + + fs := make([]protocol.FileInfo, 100) + seed := 0 + + size := 10000 + for size < 200_000 { + t0 := time.Now() + if err := svc.periodic(context.Background()); err != nil { + b.Fatal(err) + } + b.Log("garbage collect in", time.Since(t0)) + + for { + local, err := db.CountLocal(folderID, protocol.LocalDeviceID) + if err != nil { + b.Fatal(err) + } + if local.Files >= size { + break + } + fs := make([]protocol.FileInfo, 1000) + for i := range fs { + fs[i] = genFile(rand.String(24), 64, 0) + } + if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil { + b.Fatal(err) + } + } + + b.Run(fmt.Sprintf("Insert100Loc@%d", size), func(b *testing.B) { + for range b.N { + for i := range fs { + fs[i] = genFile(rand.String(24), 64, 0) + } + if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s") + }) + + b.Run(fmt.Sprintf("RepBlocks100@%d", size), func(b *testing.B) { + for range b.N { + for i := range fs { + fs[i].Blocks = genBlocks(fs[i].Name, seed, 64) + fs[i].Version = fs[i].Version.Update(42) + } + seed++ + if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s") + }) + + b.Run(fmt.Sprintf("RepSame100@%d", size), func(b *testing.B) { + for range b.N { + for i := range fs { + fs[i].Version = fs[i].Version.Update(42) + } + if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s") + }) + + b.Run(fmt.Sprintf("Insert100Rem@%d", size), func(b *testing.B) { + for range b.N { + for i := range fs { + fs[i].Blocks = genBlocks(fs[i].Name, seed, 64) + fs[i].Version = fs[i].Version.Update(42) + fs[i].Sequence = timeutil.StrictlyMonotonicNanos() + } + if err := db.Update(folderID, protocol.DeviceID{42}, fs); err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s") + }) + + b.Run(fmt.Sprintf("GetGlobal100@%d", size), func(b *testing.B) { + for range b.N { + for i := range fs { + _, ok, err := db.GetGlobalFile(folderID, fs[i].Name) + if err != nil { + b.Fatal(err) + } + if !ok { + b.Fatal("should exist") + } + } + } + b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s") + }) + + b.Run(fmt.Sprintf("LocalSequenced@%d", size), func(b *testing.B) { + count := 0 + for range b.N { + cur, err := db.GetDeviceSequence(folderID, protocol.LocalDeviceID) + if err != nil { + b.Fatal(err) + } + it, errFn := db.AllLocalFilesBySequence(folderID, protocol.LocalDeviceID, cur-100, 0) + for f := range it { + count++ + globalFi = f + } + if err := errFn(); err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(count)/b.Elapsed().Seconds(), "files/s") + }) + + b.Run(fmt.Sprintf("GetDeviceSequenceLoc@%d", size), func(b *testing.B) { + for range b.N { + _, err := db.GetDeviceSequence(folderID, protocol.LocalDeviceID) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run(fmt.Sprintf("GetDeviceSequenceRem@%d", size), func(b *testing.B) { + for range b.N { + _, err := db.GetDeviceSequence(folderID, protocol.DeviceID{42}) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run(fmt.Sprintf("RemoteNeed@%d", size), func(b *testing.B) { + count := 0 + for range b.N { + it, errFn := db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0) + for f := range it { + count++ + globalFi = f + } + if err := errFn(); err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(count)/b.Elapsed().Seconds(), "files/s") + }) + + b.Run(fmt.Sprintf("LocalNeed100Largest@%d", size), func(b *testing.B) { + count := 0 + for range b.N { + it, errFn := db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderLargestFirst, 100, 0) + for f := range it { + globalFi = f + count++ + } + if err := errFn(); err != nil { + b.Fatal(err) + } + } + b.ReportMetric(float64(count)/b.Elapsed().Seconds(), "files/s") + }) + + size <<= 1 + } +} + +func TestBenchmarkDropAllRemote(t *testing.T) { + if testing.Short() { + t.Skip("slow test") + } + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + fs := make([]protocol.FileInfo, 1000) + seq := 0 + for { + local, err := db.CountLocal(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if local.Files >= 15_000 { + break + } + for i := range fs { + seq++ + fs[i] = genFile(rand.String(24), 64, seq) + } + if err := db.Update(folderID, protocol.DeviceID{42}, fs); err != nil { + t.Fatal(err) + } + if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil { + t.Fatal(err) + } + } + + t0 := time.Now() + if err := db.DropAllFiles(folderID, protocol.DeviceID{42}); err != nil { + t.Fatal(err) + } + d := time.Since(t0) + t.Log("drop all took", d) +} diff --git a/internal/db/sqlite/db_counts.go b/internal/db/sqlite/db_counts.go new file mode 100644 index 000000000..24700dd3f --- /dev/null +++ b/internal/db/sqlite/db_counts.go @@ -0,0 +1,137 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/lib/protocol" +) + +type countsRow struct { + Type protocol.FileInfoType + Count int + Size int64 + Deleted bool + LocalFlags int64 `db:"local_flags"` +} + +func (s *DB) CountLocal(folder string, device protocol.DeviceID) (db.Counts, error) { + var res []countsRow + if err := s.stmt(` + SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s + INNER JOIN folders o ON o.idx = s.folder_idx + INNER JOIN devices d ON d.idx = s.device_idx + WHERE o.folder_id = ? AND d.device_id = ? AND s.local_flags & {{.FlagLocalIgnored}} = 0 + `).Select(&res, folder, device.String()); err != nil { + return db.Counts{}, wrap(err) + } + return summarizeCounts(res), nil +} + +func (s *DB) CountNeed(folder string, device protocol.DeviceID) (db.Counts, error) { + if device == protocol.LocalDeviceID { + return s.needSizeLocal(folder) + } + return s.needSizeRemote(folder, device) +} + +func (s *DB) CountGlobal(folder string) (db.Counts, error) { + // Exclude ignored and receive-only changed files from the global count + // (legacy expectation? it's a bit weird since those files can in fact + // be global and you can get them with GetGlobal etc.) + var res []countsRow + err := s.stmt(` + SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s + INNER JOIN folders o ON o.idx = s.folder_idx + WHERE o.folder_id = ? AND s.local_flags & {{.FlagLocalGlobal}} != 0 AND s.local_flags & {{or .FlagLocalReceiveOnly .FlagLocalIgnored}} = 0 + `).Select(&res, folder) + if err != nil { + return db.Counts{}, wrap(err) + } + return summarizeCounts(res), nil +} + +func (s *DB) CountReceiveOnlyChanged(folder string) (db.Counts, error) { + var res []countsRow + err := s.stmt(` + SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s + INNER JOIN folders o ON o.idx = s.folder_idx + WHERE o.folder_id = ? AND local_flags & {{.FlagLocalReceiveOnly}} != 0 + `).Select(&res, folder) + if err != nil { + return db.Counts{}, wrap(err) + } + return summarizeCounts(res), nil +} + +func (s *DB) needSizeLocal(folder string) (db.Counts, error) { + // The need size for the local device is the sum of entries with the + // need bit set. + var res []countsRow + err := s.stmt(` + SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s + INNER JOIN folders o ON o.idx = s.folder_idx + WHERE o.folder_id = ? AND s.local_flags & {{.FlagLocalNeeded}} != 0 + `).Select(&res, folder) + if err != nil { + return db.Counts{}, wrap(err) + } + return summarizeCounts(res), nil +} + +func (s *DB) needSizeRemote(folder string, device protocol.DeviceID) (db.Counts, error) { + var res []countsRow + // See neededGlobalFilesRemote for commentary as that is the same query without summing + if err := s.stmt(` + SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g + INNER JOIN folders o ON o.idx = g.folder_idx + WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( + SELECT 1 FROM FILES f + INNER JOIN devices d ON d.idx = f.device_idx + WHERE f.name = g.name AND f.version = g.version AND f.folder_idx = g.folder_idx AND d.device_id = ? + ) + GROUP BY g.type, g.local_flags, g.deleted + + UNION ALL + + SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g + INNER JOIN folders o ON o.idx = g.folder_idx + WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( + SELECT 1 FROM FILES f + INNER JOIN devices d ON d.idx = f.device_idx + WHERE f.name = g.name AND f.folder_idx = g.folder_idx AND d.device_id = ? AND NOT f.deleted + ) + GROUP BY g.type, g.local_flags, g.deleted + `).Select(&res, folder, device.String(), + folder, device.String()); err != nil { + return db.Counts{}, wrap(err) + } + + return summarizeCounts(res), nil +} + +func summarizeCounts(res []countsRow) db.Counts { + c := db.Counts{ + DeviceID: protocol.LocalDeviceID, + } + for _, r := range res { + switch { + case r.Deleted: + c.Deleted += r.Count + case r.Type == protocol.FileInfoTypeFile: + c.Files += r.Count + c.Bytes += r.Size + case r.Type == protocol.FileInfoTypeDirectory: + c.Directories += r.Count + c.Bytes += r.Size + case r.Type == protocol.FileInfoTypeSymlink: + c.Symlinks += r.Count + c.Bytes += r.Size + } + } + return c +} diff --git a/internal/db/sqlite/db_global.go b/internal/db/sqlite/db_global.go new file mode 100644 index 000000000..2ce7a0c93 --- /dev/null +++ b/internal/db/sqlite/db_global.go @@ -0,0 +1,189 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "database/sql" + "errors" + "fmt" + "iter" + + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/osutil" + "github.com/syncthing/syncthing/lib/protocol" +) + +func (s *DB) GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) { + file = osutil.NormalizedFilename(file) + + var ind indirectFI + err := s.stmt(` + SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi + INNER JOIN files f on fi.sequence = f.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash + INNER JOIN folders o ON o.idx = f.folder_idx + WHERE o.folder_id = ? AND f.name = ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 + `).Get(&ind, folder, file) + if errors.Is(err, sql.ErrNoRows) { + return protocol.FileInfo{}, false, nil + } + if err != nil { + return protocol.FileInfo{}, false, wrap(err) + } + fi, err := ind.FileInfo() + if err != nil { + return protocol.FileInfo{}, false, wrap(err) + } + return fi, true, nil +} + +func (s *DB) GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error) { + file = osutil.NormalizedFilename(file) + + var devStrs []string + err := s.stmt(` + SELECT d.device_id FROM files f + INNER JOIN devices d ON d.idx = f.device_idx + INNER JOIN folders o ON o.idx = f.folder_idx + INNER JOIN files g ON f.folder_idx = g.folder_idx AND g.version = f.version AND g.name = f.name + WHERE o.folder_id = ? AND g.name = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND f.device_idx != {{.LocalDeviceIdx}} + ORDER BY d.device_id + `).Select(&devStrs, folder, file) + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + if err != nil { + return nil, wrap(err) + } + + devs := make([]protocol.DeviceID, 0, len(devStrs)) + for _, s := range devStrs { + d, err := protocol.DeviceIDFromString(s) + if err != nil { + return nil, wrap(err) + } + devs = append(devs, d) + } + + return devs, nil +} + +func (s *DB) AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) { + it, errFn := iterStructs[db.FileMetadata](s.stmt(` + SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f + INNER JOIN folders o ON o.idx = f.folder_idx + WHERE o.folder_id = ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 + ORDER BY f.name + `).Queryx(folder)) + return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) { + m.Name = osutil.NativeFilename(m.Name) + return m, nil + }) +} + +func (s *DB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[db.FileMetadata], func() error) { + if prefix == "" { + return s.AllGlobalFiles(folder) + } + + prefix = osutil.NormalizedFilename(prefix) + end := prefixEnd(prefix) + + it, errFn := iterStructs[db.FileMetadata](s.stmt(` + SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f + INNER JOIN folders o ON o.idx = f.folder_idx + WHERE o.folder_id = ? AND f.name >= ? AND f.name < ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 + ORDER BY f.name + `).Queryx(folder, prefix, end)) + return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) { + m.Name = osutil.NativeFilename(m.Name) + return m, nil + }) +} + +func (s *DB) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) { + var selectOpts string + switch order { + case config.PullOrderRandom: + selectOpts = "ORDER BY RANDOM()" + case config.PullOrderAlphabetic: + selectOpts = "ORDER BY g.name ASC" + case config.PullOrderSmallestFirst: + selectOpts = "ORDER BY g.size ASC" + case config.PullOrderLargestFirst: + selectOpts = "ORDER BY g.size DESC" + case config.PullOrderOldestFirst: + selectOpts = "ORDER BY g.modified ASC" + case config.PullOrderNewestFirst: + selectOpts = "ORDER BY g.modified DESC" + } + + if limit > 0 { + selectOpts += fmt.Sprintf(" LIMIT %d", limit) + } + if offset > 0 { + selectOpts += fmt.Sprintf(" OFFSET %d", offset) + } + + if device == protocol.LocalDeviceID { + return s.neededGlobalFilesLocal(folder, selectOpts) + } + + return s.neededGlobalFilesRemote(folder, device, selectOpts) +} + +func (s *DB) neededGlobalFilesLocal(folder, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) { + // Select all the non-ignored files with the need bit set. + it, errFn := iterStructs[indirectFI](s.stmt(` + SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi + INNER JOIN files g on fi.sequence = g.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash + INNER JOIN folders o ON o.idx = g.folder_idx + WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalIgnored}} = 0 AND g.local_flags & {{.FlagLocalNeeded}} != 0 + ` + selectOpts).Queryx(folder)) + return itererr.Map(it, errFn, indirectFI.FileInfo) +} + +func (s *DB) neededGlobalFilesRemote(folder string, device protocol.DeviceID, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) { + // Select: + // + // - all the valid, non-deleted global files that don't have a corresponding + // remote file with the same version. + // + // - all the valid, deleted global files that have a corresponding non-deleted + // remote file (of any version) + + it, errFn := iterStructs[indirectFI](s.stmt(` + SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi + INNER JOIN files g on fi.sequence = g.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash + INNER JOIN folders o ON o.idx = g.folder_idx + WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( + SELECT 1 FROM FILES f + INNER JOIN devices d ON d.idx = f.device_idx + WHERE f.name = g.name AND f.version = g.version AND f.folder_idx = g.folder_idx AND d.device_id = ? + ) + + UNION ALL + + SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi + INNER JOIN files g on fi.sequence = g.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash + INNER JOIN folders o ON o.idx = g.folder_idx + WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( + SELECT 1 FROM FILES f + INNER JOIN devices d ON d.idx = f.device_idx + WHERE f.name = g.name AND f.folder_idx = g.folder_idx AND d.device_id = ? AND NOT f.deleted + ) + `+selectOpts).Queryx( + folder, device.String(), + folder, device.String(), + )) + return itererr.Map(it, errFn, indirectFI.FileInfo) +} diff --git a/internal/db/sqlite/db_global_test.go b/internal/db/sqlite/db_global_test.go new file mode 100644 index 000000000..44130ac77 --- /dev/null +++ b/internal/db/sqlite/db_global_test.go @@ -0,0 +1,493 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "slices" + "testing" + + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" +) + +func TestNeed(t *testing.T) { + t.Helper() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + var v protocol.Vector + baseV := v.Update(1) + newerV := baseV.Update(42) + files := []protocol.FileInfo{ + genFile("test1", 1, 0), // remote need + genFile("test2", 2, 0), // local need + genFile("test3", 3, 0), // global + } + files[0].Version = baseV + files[1].Version = baseV + files[2].Version = newerV + err = db.Update(folderID, protocol.LocalDeviceID, files) + if err != nil { + t.Fatal(err) + } + + // Some remote files + remote := []protocol.FileInfo{ + genFile("test2", 2, 100), // global + genFile("test3", 3, 101), // remote need + genFile("test4", 4, 102), // local need + } + remote[0].Version = newerV + remote[1].Version = baseV + remote[2].Version = newerV + err = db.Update(folderID, protocol.DeviceID{42}, remote) + if err != nil { + t.Fatal(err) + } + + // A couple are needed locally + localNeed := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))) + if !slices.Equal(localNeed, []string{"test2", "test4"}) { + t.Log(localNeed) + t.Fatal("bad local need") + } + + // Another couple are needed remotely + remoteNeed := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0))) + if !slices.Equal(remoteNeed, []string{"test1", "test3"}) { + t.Log(remoteNeed) + t.Fatal("bad remote need") + } +} + +func TestDropRecalcsGlobal(t *testing.T) { + // When we drop a device we may get a new global + + t.Parallel() + + t.Run("DropAllFiles", func(t *testing.T) { + t.Parallel() + + testDropWithDropper(t, func(t *testing.T, db *DB) { + t.Helper() + if err := db.DropAllFiles(folderID, protocol.DeviceID{42}); err != nil { + t.Fatal(err) + } + }) + }) + + t.Run("DropDevice", func(t *testing.T) { + t.Parallel() + + testDropWithDropper(t, func(t *testing.T, db *DB) { + t.Helper() + if err := db.DropDevice(protocol.DeviceID{42}); err != nil { + t.Fatal(err) + } + }) + }) + + t.Run("DropFilesNamed", func(t *testing.T) { + t.Parallel() + + testDropWithDropper(t, func(t *testing.T, db *DB) { + t.Helper() + if err := db.DropFilesNamed(folderID, protocol.DeviceID{42}, []string{"test1", "test42"}); err != nil { + t.Fatal(err) + } + }) + }) +} + +func testDropWithDropper(t *testing.T, dropper func(t *testing.T, db *DB)) { + t.Helper() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{ + genFile("test1", 1, 0), + genFile("test2", 2, 0), + }) + if err != nil { + t.Fatal(err) + } + + // Some remote files + remote := []protocol.FileInfo{ + genFile("test1", 3, 0), + } + remote[0].Version = remote[0].Version.Update(42) + err = db.Update(folderID, protocol.DeviceID{42}, remote) + if err != nil { + t.Fatal(err) + } + + // Remote test1 wins as the global, verify. + count, err := db.CountGlobal(folderID) + if err != nil { + t.Fatal(err) + } + if count.Bytes != (2+3)*128<<10 { + t.Log(count) + t.Fatal("bad global size to begin with") + } + if g, ok, err := db.GetGlobalFile(folderID, "test1"); err != nil || !ok { + t.Fatal("missing global to begin with") + } else if g.Size != 3*128<<10 { + t.Fatal("remote test1 should be the global") + } + + // Now remove that remote device + dropper(t, db) + + // Our test1 should now be the global + count, err = db.CountGlobal(folderID) + if err != nil { + t.Fatal(err) + } + if count.Bytes != (1+2)*128<<10 { + t.Log(count) + t.Fatal("bad global size after drop") + } + if g, ok, err := db.GetGlobalFile(folderID, "test1"); err != nil || !ok { + t.Fatal("missing global after drop") + } else if g.Size != 1*128<<10 { + t.Fatal("local test1 should be the global") + } +} + +func TestNeedDeleted(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{ + genFile("test1", 1, 0), + genFile("test2", 2, 0), + }) + if err != nil { + t.Fatal(err) + } + + // A remote deleted file + remote := []protocol.FileInfo{ + genFile("test1", 1, 101), + } + remote[0].SetDeleted(42) + err = db.Update(folderID, protocol.DeviceID{42}, remote) + if err != nil { + t.Fatal(err) + } + + // We need the one deleted file + s, err := db.CountNeed(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Deleted != 1 { + t.Log(s) + t.Error("bad need") + } +} + +func TestDontNeedIgnored(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // A remote file + files := []protocol.FileInfo{ + genFile("test1", 1, 103), + } + err = db.Update(folderID, protocol.DeviceID{42}, files) + if err != nil { + t.Fatal(err) + } + + // Which we've ignored locally + files[0].SetIgnored() + err = db.Update(folderID, protocol.LocalDeviceID, files) + if err != nil { + t.Fatal(err) + } + + // We don't need it + s, err := db.CountNeed(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Files != 0 { + t.Log(s) + t.Error("bad need") + } + + // It shouldn't show up in the need list + names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) + if len(names) != 0 { + t.Log(names) + t.Error("need no files") + } +} + +func TestRemoveDontNeedLocalIgnored(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // A local ignored file + file := genFile("test1", 1, 103) + file.SetIgnored() + files := []protocol.FileInfo{file} + err = db.Update(folderID, protocol.LocalDeviceID, files) + if err != nil { + t.Fatal(err) + } + + // Which the remote doesn't have (no update) + + // They don't need it + s, err := db.CountNeed(folderID, protocol.DeviceID{42}) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Files != 0 { + t.Log(s) + t.Error("bad need") + } + + // It shouldn't show up in their need list + names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0)) + if len(names) != 0 { + t.Log(names) + t.Error("need no files") + } +} + +func TestLocalDontNeedDeletedMissing(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // A remote deleted file + file := genFile("test1", 1, 103) + file.SetDeleted(42) + files := []protocol.FileInfo{file} + err = db.Update(folderID, protocol.DeviceID{42}, files) + if err != nil { + t.Fatal(err) + } + + // Which we don't have (no local update) + + // We don't need it + s, err := db.CountNeed(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 { + t.Log(s) + t.Error("bad need") + } + + // It shouldn't show up in the need list + names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) + if len(names) != 0 { + t.Log(names) + t.Error("need no files") + } +} + +func TestRemoteDontNeedDeletedMissing(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // A local deleted file + file := genFile("test1", 1, 103) + file.SetDeleted(42) + files := []protocol.FileInfo{file} + err = db.Update(folderID, protocol.LocalDeviceID, files) + if err != nil { + t.Fatal(err) + } + + // Which the remote doesn't have (no local update) + + // They don't need it + s, err := db.CountNeed(folderID, protocol.DeviceID{42}) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 { + t.Log(s) + t.Error("bad need") + } + + // It shouldn't show up in their need list + names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0)) + if len(names) != 0 { + t.Log(names) + t.Error("need no files") + } +} + +func TestNeedRemoteSymlinkAndDir(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Two remote "specials", a symlink and a directory + var v protocol.Vector + v.Update(1) + files := []protocol.FileInfo{ + {Name: "sym", Type: protocol.FileInfoTypeSymlink, Sequence: 100, Version: v, Blocks: genBlocks("symlink", 0, 1)}, + {Name: "dir", Type: protocol.FileInfoTypeDirectory, Sequence: 101, Version: v}, + } + err = db.Update(folderID, protocol.DeviceID{42}, files) + if err != nil { + t.Fatal(err) + } + + // We need them + s, err := db.CountNeed(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if s.Directories != 1 || s.Symlinks != 1 { + t.Log(s) + t.Error("bad need") + } + + // They should be in the need list + names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) + if len(names) != 2 { + t.Log(names) + t.Error("bad need") + } +} + +func TestNeedPagination(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Several remote files + var v protocol.Vector + v.Update(1) + files := []protocol.FileInfo{ + genFile("test0", 1, 100), + genFile("test1", 1, 101), + genFile("test2", 1, 102), + genFile("test3", 1, 103), + genFile("test4", 1, 104), + genFile("test5", 1, 105), + genFile("test6", 1, 106), + genFile("test7", 1, 107), + genFile("test8", 1, 108), + genFile("test9", 1, 109), + } + err = db.Update(folderID, protocol.DeviceID{42}, files) + if err != nil { + t.Fatal(err) + } + + // We should get the first two + names := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 2, 0))) + if !slices.Equal(names, []string{"test0", "test1"}) { + t.Log(names) + t.Error("bad need") + } + + // We should get the next three + names = fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 3, 2))) + if !slices.Equal(names, []string{"test2", "test3", "test4"}) { + t.Log(names) + t.Error("bad need") + } + + // We should get the last five + names = fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 5, 5))) + if !slices.Equal(names, []string{"test5", "test6", "test7", "test8", "test9"}) { + t.Log(names) + t.Error("bad need") + } +} diff --git a/internal/db/sqlite/db_indexid.go b/internal/db/sqlite/db_indexid.go new file mode 100644 index 000000000..e8f3fa1ab --- /dev/null +++ b/internal/db/sqlite/db_indexid.go @@ -0,0 +1,163 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "database/sql" + "encoding/hex" + "errors" + "fmt" + + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/lib/protocol" +) + +func (s *DB) GetIndexID(folder string, device protocol.DeviceID) (protocol.IndexID, error) { + // Try a fast read-only query to begin with. If it does not find the ID + // we'll do the full thing under a lock. + var indexID string + if err := s.stmt(` + SELECT i.index_id FROM indexids i + INNER JOIN folders o ON o.idx = i.folder_idx + INNER JOIN devices d ON d.idx = i.device_idx + WHERE o.folder_id = ? AND d.device_id = ? + `).Get(&indexID, folder, device.String()); err == nil && indexID != "" { + idx, err := indexIDFromHex(indexID) + return idx, wrap(err, "select") + } + if device != protocol.LocalDeviceID { + // For non-local devices we do not create the index ID, so return + // zero anyway if we don't have one. + return 0, nil + } + + s.updateLock.Lock() + defer s.updateLock.Unlock() + + // We are now operating only for the local device ID + + folderIdx, err := s.folderIdxLocked(folder) + if err != nil { + return 0, wrap(err) + } + + if err := s.stmt(` + SELECT index_id FROM indexids WHERE folder_idx = ? AND device_idx = {{.LocalDeviceIdx}} + `).Get(&indexID, folderIdx); err != nil && !errors.Is(err, sql.ErrNoRows) { + return 0, wrap(err, "select local") + } + + if indexID == "" { + // Generate a new index ID. Some trickiness in the query as we need + // to find the max sequence of local files if there already exist + // any. + id := protocol.NewIndexID() + if _, err := s.stmt(` + INSERT INTO indexids (folder_idx, device_idx, index_id, sequence) + SELECT ?, {{.LocalDeviceIdx}}, ?, COALESCE(MAX(sequence), 0) FROM files + WHERE folder_idx = ? AND device_idx = {{.LocalDeviceIdx}} + ON CONFLICT DO UPDATE SET index_id = ? + `).Exec(folderIdx, indexIDToHex(id), folderIdx, indexIDToHex(id)); err != nil { + return 0, wrap(err, "insert") + } + return id, nil + } + + return indexIDFromHex(indexID) +} + +func (s *DB) SetIndexID(folder string, device protocol.DeviceID, id protocol.IndexID) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + + folderIdx, err := s.folderIdxLocked(folder) + if err != nil { + return wrap(err, "folder idx") + } + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err, "device idx") + } + + if _, err := s.stmt(` + INSERT OR REPLACE INTO indexids (folder_idx, device_idx, index_id, sequence) values (?, ?, ?, 0) + `).Exec(folderIdx, deviceIdx, indexIDToHex(id)); err != nil { + return wrap(err, "insert") + } + return nil +} + +func (s *DB) DropAllIndexIDs() error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + _, err := s.stmt(`DELETE FROM indexids`).Exec() + return wrap(err) +} + +func (s *DB) GetDeviceSequence(folder string, device protocol.DeviceID) (int64, error) { + var res sql.NullInt64 + err := s.stmt(` + SELECT sequence FROM indexids i + INNER JOIN folders o ON o.idx = i.folder_idx + INNER JOIN devices d ON d.idx = i.device_idx + WHERE o.folder_id = ? AND d.device_id = ? + `).Get(&res, folder, device.String()) + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + if err != nil { + return 0, wrap(err) + } + if !res.Valid { + return 0, nil + } + return res.Int64, nil +} + +func (s *DB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) { + type row struct { + Device string + Seq int64 + } + + it, errFn := iterStructs[row](s.stmt(` + SELECT d.device_id AS device, i.sequence AS seq FROM indexids i + INNER JOIN folders o ON o.idx = i.folder_idx + INNER JOIN devices d ON d.idx = i.device_idx + WHERE o.folder_id = ? AND i.device_idx != {{.LocalDeviceIdx}} + `).Queryx(folder)) + + res := make(map[protocol.DeviceID]int64) + for row, err := range itererr.Zip(it, errFn) { + if err != nil { + return nil, wrap(err) + } + dev, err := protocol.DeviceIDFromString(row.Device) + if err != nil { + return nil, wrap(err, "device ID") + } + res[dev] = row.Seq + } + return res, nil +} + +func indexIDFromHex(s string) (protocol.IndexID, error) { + bs, err := hex.DecodeString(s) + if err != nil { + return 0, fmt.Errorf("indexIDFromHex: %q: %w", s, err) + } + var id protocol.IndexID + if err := id.Unmarshal(bs); err != nil { + return 0, fmt.Errorf("indexIDFromHex: %q: %w", s, err) + } + return id, nil +} + +func indexIDToHex(i protocol.IndexID) string { + bs, _ := i.Marshal() + return hex.EncodeToString(bs) +} diff --git a/internal/db/sqlite/db_indexid_test.go b/internal/db/sqlite/db_indexid_test.go new file mode 100644 index 000000000..82116734a --- /dev/null +++ b/internal/db/sqlite/db_indexid_test.go @@ -0,0 +1,81 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "testing" + + "github.com/syncthing/syncthing/lib/protocol" +) + +func TestIndexIDs(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal() + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + t.Run("LocalDeviceID", func(t *testing.T) { + t.Parallel() + + localID, err := db.GetIndexID("foo", protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if localID == 0 { + t.Fatal("should have been generated") + } + + again, err := db.GetIndexID("foo", protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if again != localID { + t.Fatal("should get same again") + } + + other, err := db.GetIndexID("bar", protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if other == localID { + t.Fatal("should not get same for other folder") + } + }) + + t.Run("OtherDeviceID", func(t *testing.T) { + t.Parallel() + + localID, err := db.GetIndexID("foo", protocol.DeviceID{42}) + if err != nil { + t.Fatal(err) + } + if localID != 0 { + t.Fatal("should have been zero") + } + + newID := protocol.NewIndexID() + if err := db.SetIndexID("foo", protocol.DeviceID{42}, newID); err != nil { + t.Fatal(err) + } + + again, err := db.GetIndexID("foo", protocol.DeviceID{42}) + if err != nil { + t.Fatal(err) + } + if again != newID { + t.Log(again, newID) + t.Fatal("should get the ID we set") + } + }) +} diff --git a/internal/db/sqlite/db_kv.go b/internal/db/sqlite/db_kv.go new file mode 100644 index 000000000..077ad3609 --- /dev/null +++ b/internal/db/sqlite/db_kv.go @@ -0,0 +1,78 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "iter" + + "github.com/jmoiron/sqlx" + "github.com/syncthing/syncthing/internal/db" +) + +func (s *DB) GetKV(key string) ([]byte, error) { + var val []byte + if err := s.stmt(` + SELECT value FROM kv + WHERE key = ? + `).Get(&val, key); err != nil { + return nil, wrap(err) + } + return val, nil +} + +func (s *DB) PutKV(key string, val []byte) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + _, err := s.stmt(` + INSERT OR REPLACE INTO kv (key, value) + VALUES (?, ?) + `).Exec(key, val) + return wrap(err) +} + +func (s *DB) DeleteKV(key string) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + _, err := s.stmt(` + DELETE FROM kv WHERE key = ? + `).Exec(key) + return wrap(err) +} + +func (s *DB) PrefixKV(prefix string) (iter.Seq[db.KeyValue], func() error) { + var rows *sqlx.Rows + var err error + if prefix == "" { + rows, err = s.stmt(`SELECT key, value FROM kv`).Queryx() + } else { + end := prefixEnd(prefix) + rows, err = s.stmt(` + SELECT key, value FROM kv + WHERE key >= ? AND key < ? + `).Queryx(prefix, end) + } + if err != nil { + return func(_ func(db.KeyValue) bool) {}, func() error { return err } + } + + return func(yield func(db.KeyValue) bool) { + defer rows.Close() + for rows.Next() { + var key string + var val []byte + if err = rows.Scan(&key, &val); err != nil { + return + } + if !yield(db.KeyValue{Key: key, Value: val}) { + return + } + } + err = rows.Err() + }, func() error { + return err + } +} diff --git a/internal/db/sqlite/db_local.go b/internal/db/sqlite/db_local.go new file mode 100644 index 000000000..4b4064757 --- /dev/null +++ b/internal/db/sqlite/db_local.go @@ -0,0 +1,126 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "database/sql" + "errors" + "fmt" + "iter" + + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/lib/osutil" + "github.com/syncthing/syncthing/lib/protocol" +) + +func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) { + file = osutil.NormalizedFilename(file) + + var ind indirectFI + err := s.stmt(` + SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi + INNER JOIN files f on fi.sequence = f.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash + INNER JOIN devices d ON f.device_idx = d.idx + INNER JOIN folders o ON f.folder_idx = o.idx + WHERE o.folder_id = ? AND d.device_id = ? AND f.name = ? + `).Get(&ind, folder, device.String(), file) + if errors.Is(err, sql.ErrNoRows) { + return protocol.FileInfo{}, false, nil + } + if err != nil { + return protocol.FileInfo{}, false, wrap(err) + } + fi, err := ind.FileInfo() + if err != nil { + return protocol.FileInfo{}, false, wrap(err, "indirect") + } + return fi, true, nil +} + +func (s *DB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { + it, errFn := iterStructs[indirectFI](s.stmt(` + SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi + INNER JOIN files f on fi.sequence = f.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash + INNER JOIN folders o ON o.idx = f.folder_idx + INNER JOIN devices d ON d.idx = f.device_idx + WHERE o.folder_id = ? AND d.device_id = ? + `).Queryx(folder, device.String())) + return itererr.Map(it, errFn, indirectFI.FileInfo) +} + +func (s *DB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) { + var limitStr string + if limit > 0 { + limitStr = fmt.Sprintf(" LIMIT %d", limit) + } + it, errFn := iterStructs[indirectFI](s.stmt(` + SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi + INNER JOIN files f on fi.sequence = f.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash + INNER JOIN folders o ON o.idx = f.folder_idx + INNER JOIN devices d ON d.idx = f.device_idx + WHERE o.folder_id = ? AND d.device_id = ? AND f.sequence >= ? + ORDER BY f.sequence`+limitStr).Queryx( + folder, device.String(), startSeq)) + return itererr.Map(it, errFn, indirectFI.FileInfo) +} + +func (s *DB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) { + if prefix == "" { + return s.AllLocalFiles(folder, device) + } + + prefix = osutil.NormalizedFilename(prefix) + end := prefixEnd(prefix) + + it, errFn := iterStructs[indirectFI](s.sql.Queryx(` + SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi + INNER JOIN files f on fi.sequence = f.sequence + LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash + INNER JOIN folders o ON o.idx = f.folder_idx + INNER JOIN devices d ON d.idx = f.device_idx + WHERE o.folder_id = ? AND d.device_id = ? AND f.name >= ? AND f.name < ? + `, folder, device.String(), prefix, end)) + return itererr.Map(it, errFn, indirectFI.FileInfo) +} + +func (s *DB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[db.FileMetadata], func() error) { + return iterStructs[db.FileMetadata](s.stmt(` + SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f + INNER JOIN folders o ON o.idx = f.folder_idx + WHERE o.folder_id = ? AND f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ? + `).Queryx(folder, h)) +} + +func (s *DB) AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, db.FileMetadata], func() error) { + type row struct { + FolderID string `db:"folder_id"` + db.FileMetadata + } + it, errFn := iterStructs[row](s.stmt(` + SELECT o.folder_id, f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f + INNER JOIN folders o ON o.idx = f.folder_idx + WHERE f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ? + `).Queryx(h)) + return itererr.Map2(it, errFn, func(r row) (string, db.FileMetadata, error) { + return r.FolderID, r.FileMetadata, nil + }) +} + +func (s *DB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntry], func() error) { + // We involve the files table in this select because deletion of blocks + // & blocklists is deferred (garbage collected) while the files list is + // not. This filters out blocks that are in fact deleted. + return iterStructs[db.BlockMapEntry](s.stmt(` + SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size FROM files f + LEFT JOIN blocks b ON f.blocklist_hash = b.blocklist_hash + WHERE f.device_idx = {{.LocalDeviceIdx}} AND b.hash = ? + `).Queryx(hash)) +} diff --git a/internal/db/sqlite/db_local_test.go b/internal/db/sqlite/db_local_test.go new file mode 100644 index 000000000..8444a07d8 --- /dev/null +++ b/internal/db/sqlite/db_local_test.go @@ -0,0 +1,202 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "testing" + + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/lib/protocol" +) + +func TestBlocks(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal() + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + files := []protocol.FileInfo{ + { + Name: "file1", + Blocks: []protocol.BlockInfo{ + {Hash: []byte{1, 2, 3}, Offset: 0, Size: 42}, + {Hash: []byte{2, 3, 4}, Offset: 42, Size: 42}, + {Hash: []byte{3, 4, 5}, Offset: 84, Size: 42}, + }, + }, + { + Name: "file2", + Blocks: []protocol.BlockInfo{ + {Hash: []byte{2, 3, 4}, Offset: 0, Size: 42}, + {Hash: []byte{3, 4, 5}, Offset: 42, Size: 42}, + {Hash: []byte{4, 5, 6}, Offset: 84, Size: 42}, + }, + }, + } + + if err := db.Update("test", protocol.LocalDeviceID, files); err != nil { + t.Fatal(err) + } + + // Search for blocks + + vals, err := itererr.Collect(db.AllLocalBlocksWithHash([]byte{1, 2, 3})) + if err != nil { + t.Fatal(err) + } + if len(vals) != 1 { + t.Log(vals) + t.Fatal("expected one hit") + } else if vals[0].BlockIndex != 0 || vals[0].Offset != 0 || vals[0].Size != 42 { + t.Log(vals[0]) + t.Fatal("bad entry") + } + + // Get FileInfos for those blocks + + found := 0 + it, errFn := db.AllLocalFilesWithBlocksHashAnyFolder(vals[0].BlocklistHash) + for folder, fileInfo := range it { + if folder != folderID { + t.Fatal("should be same folder") + } + if fileInfo.Name != "file1" { + t.Fatal("should be file1") + } + found++ + } + if err := errFn(); err != nil { + t.Fatal(err) + } + if found != 1 { + t.Fatal("should find one file") + } + + // Get the other blocks + + vals, err = itererr.Collect(db.AllLocalBlocksWithHash([]byte{3, 4, 5})) + if err != nil { + t.Fatal(err) + } + if len(vals) != 2 { + t.Log(vals) + t.Fatal("expected two hits") + } + // if vals[0].Index != 2 || vals[0].Offset != 84 || vals[0].Size != 42 { + // t.Log(vals[0]) + // t.Fatal("bad entry 1") + // } + // if vals[1].Index != 1 || vals[1].Offset != 42 || vals[1].Size != 42 { + // t.Log(vals[1]) + // t.Fatal("bad entry 2") + // } +} + +func TestBlocksDeleted(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal() + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // Insert a file + file := genFile("foo", 1, 0) + if err := sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + + // We should find one entry for the block hash + search := file.Blocks[0].Hash + es := mustCollect[db.BlockMapEntry](t)(sdb.AllLocalBlocksWithHash(search)) + if len(es) != 1 { + t.Fatal("expected one hit") + } + + // Update the file with a new block hash + file.Blocks = genBlocks("foo", 42, 1) + if err := sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + + // Searching for the old hash should yield no hits + if hits := mustCollect[db.BlockMapEntry](t)(sdb.AllLocalBlocksWithHash(search)); len(hits) != 0 { + t.Log(hits) + t.Error("expected no hits") + } + + // Searching for the new hash should yield one hits + if hits := mustCollect[db.BlockMapEntry](t)(sdb.AllLocalBlocksWithHash(file.Blocks[0].Hash)); len(hits) != 1 { + t.Log(hits) + t.Error("expected one hit") + } +} + +func TestRemoteSequence(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal() + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // Insert a local file + file := genFile("foo", 1, 0) + if err := sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + + // Insert several remote files + file = genFile("foo1", 1, 42) + if err := sdb.Update(folderID, protocol.DeviceID{42}, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + if err := sdb.Update(folderID, protocol.DeviceID{43}, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + file = genFile("foo2", 1, 43) + if err := sdb.Update(folderID, protocol.DeviceID{43}, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + if err := sdb.Update(folderID, protocol.DeviceID{44}, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + file = genFile("foo3", 1, 44) + if err := sdb.Update(folderID, protocol.DeviceID{44}, []protocol.FileInfo{file}); err != nil { + t.Fatal() + } + + // Verify remote sequences + seqs, err := sdb.RemoteSequences(folderID) + if err != nil { + t.Fatal(err) + } + if len(seqs) != 3 || seqs[protocol.DeviceID{42}] != 42 || + seqs[protocol.DeviceID{43}] != 43 || + seqs[protocol.DeviceID{44}] != 44 { + t.Log(seqs) + t.Error("bad seqs") + } +} diff --git a/internal/db/sqlite/db_mtimes.go b/internal/db/sqlite/db_mtimes.go new file mode 100644 index 000000000..1c3dc7765 --- /dev/null +++ b/internal/db/sqlite/db_mtimes.go @@ -0,0 +1,54 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "time" +) + +func (s *DB) GetMtime(folder, name string) (ondisk, virtual time.Time) { + var res struct { + Ondisk int64 + Virtual int64 + } + if err := s.stmt(` + SELECT m.ondisk, m.virtual FROM mtimes m + INNER JOIN folders o ON o.idx = m.folder_idx + WHERE o.folder_id = ? AND m.name = ? + `).Get(&res, folder, name); err != nil { + return time.Time{}, time.Time{} + } + return time.Unix(0, res.Ondisk), time.Unix(0, res.Virtual) +} + +func (s *DB) PutMtime(folder, name string, ondisk, virtual time.Time) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + folderIdx, err := s.folderIdxLocked(folder) + if err != nil { + return wrap(err) + } + _, err = s.stmt(` + INSERT OR REPLACE INTO mtimes (folder_idx, name, ondisk, virtual) + VALUES (?, ?, ?, ?) + `).Exec(folderIdx, name, ondisk.UnixNano(), virtual.UnixNano()) + return wrap(err) +} + +func (s *DB) DeleteMtime(folder, name string) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + folderIdx, err := s.folderIdxLocked(folder) + if err != nil { + return wrap(err) + } + _, err = s.stmt(` + DELETE FROM mtimes + WHERE folder_idx = ? AND name = ? + `).Exec(folderIdx, name) + return wrap(err) +} diff --git a/internal/db/sqlite/db_mtimes_test.go b/internal/db/sqlite/db_mtimes_test.go new file mode 100644 index 000000000..71972eb5b --- /dev/null +++ b/internal/db/sqlite/db_mtimes_test.go @@ -0,0 +1,54 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "testing" + "time" +) + +func TestMtimePairs(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal() + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + t0 := time.Now().Truncate(time.Second) + t1 := t0.Add(1234567890) + + // Set a pair + if err := db.PutMtime("foo", "bar", t0, t1); err != nil { + t.Fatal(err) + } + + // Check it + gt0, gt1 := db.GetMtime("foo", "bar") + if !gt0.Equal(t0) || !gt1.Equal(t1) { + t.Log(t0, gt0) + t.Log(t1, gt1) + t.Log("bad times") + } + + // Delete it + if err := db.DeleteMtime("foo", "bar"); err != nil { + t.Fatal(err) + } + + // Check it + gt0, gt1 = db.GetMtime("foo", "bar") + if !gt0.IsZero() || !gt1.IsZero() { + t.Log(gt0, gt1) + t.Log("bad times") + } +} diff --git a/internal/db/sqlite/db_open.go b/internal/db/sqlite/db_open.go new file mode 100644 index 000000000..9de8e4d1b --- /dev/null +++ b/internal/db/sqlite/db_open.go @@ -0,0 +1,203 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "database/sql" + "os" + "path/filepath" + "strconv" + "strings" + "text/template" + + "github.com/jmoiron/sqlx" + "github.com/syncthing/syncthing/lib/build" + "github.com/syncthing/syncthing/lib/protocol" +) + +const maxDBConns = 128 + +func Open(path string) (*DB, error) { + // Open the database with options to enable foreign keys and recursive + // triggers (needed for the delete+insert triggers on row replace). + sqlDB, err := sqlx.Open(dbDriver, "file:"+path+"?"+commonOptions) + if err != nil { + return nil, wrap(err) + } + sqlDB.SetMaxOpenConns(maxDBConns) + if _, err := sqlDB.Exec(`PRAGMA journal_mode = WAL`); err != nil { + return nil, wrap(err, "PRAGMA journal_mode") + } + if _, err := sqlDB.Exec(`PRAGMA optimize = 0x10002`); err != nil { + // https://www.sqlite.org/pragma.html#pragma_optimize + return nil, wrap(err, "PRAGMA optimize") + } + if _, err := sqlDB.Exec(`PRAGMA journal_size_limit = 6144000`); err != nil { + // https://www.powersync.com/blog/sqlite-optimizations-for-ultra-high-performance + return nil, wrap(err, "PRAGMA journal_size_limit") + } + return openCommon(sqlDB) +} + +// Open the database with options suitable for the migration inserts. This +// is not a safe mode of operation for normal processing, use only for bulk +// inserts with a close afterwards. +func OpenForMigration(path string) (*DB, error) { + sqlDB, err := sqlx.Open(dbDriver, "file:"+path+"?"+commonOptions) + if err != nil { + return nil, wrap(err, "open") + } + sqlDB.SetMaxOpenConns(1) + if _, err := sqlDB.Exec(`PRAGMA foreign_keys = 0`); err != nil { + return nil, wrap(err, "PRAGMA foreign_keys") + } + if _, err := sqlDB.Exec(`PRAGMA journal_mode = OFF`); err != nil { + return nil, wrap(err, "PRAGMA journal_mode") + } + if _, err := sqlDB.Exec(`PRAGMA synchronous = 0`); err != nil { + return nil, wrap(err, "PRAGMA synchronous") + } + return openCommon(sqlDB) +} + +func OpenTemp() (*DB, error) { + // SQLite has a memory mode, but it works differently with concurrency + // compared to what we need with the WAL mode. So, no memory databases + // for now. + dir, err := os.MkdirTemp("", "syncthing-db") + if err != nil { + return nil, wrap(err) + } + path := filepath.Join(dir, "db") + l.Debugln("Test DB in", path) + return Open(path) +} + +func openCommon(sqlDB *sqlx.DB) (*DB, error) { + if _, err := sqlDB.Exec(`PRAGMA auto_vacuum = INCREMENTAL`); err != nil { + return nil, wrap(err, "PRAGMA auto_vacuum") + } + if _, err := sqlDB.Exec(`PRAGMA default_temp_store = MEMORY`); err != nil { + return nil, wrap(err, "PRAGMA default_temp_store") + } + if _, err := sqlDB.Exec(`PRAGMA temp_store = MEMORY`); err != nil { + return nil, wrap(err, "PRAGMA temp_store") + } + + db := &DB{ + sql: sqlDB, + statements: make(map[string]*sqlx.Stmt), + } + + if err := db.runScripts("sql/schema/*"); err != nil { + return nil, wrap(err) + } + + ver, _ := db.getAppliedSchemaVersion() + if ver.SchemaVersion > 0 { + filter := func(scr string) bool { + scr = filepath.Base(scr) + nstr, _, ok := strings.Cut(scr, "-") + if !ok { + return false + } + n, err := strconv.ParseInt(nstr, 10, 32) + if err != nil { + return false + } + return int(n) > ver.SchemaVersion + } + if err := db.runScripts("sql/migrations/*", filter); err != nil { + return nil, wrap(err) + } + } + + // Touch device IDs that should always exist and have a low index + // numbers, and will never change + db.localDeviceIdx, _ = db.deviceIdxLocked(protocol.LocalDeviceID) + + // Set the current schema version, if not already set + if err := db.setAppliedSchemaVersion(currentSchemaVersion); err != nil { + return nil, wrap(err) + } + + db.tplInput = map[string]any{ + "FlagLocalUnsupported": protocol.FlagLocalUnsupported, + "FlagLocalIgnored": protocol.FlagLocalIgnored, + "FlagLocalMustRescan": protocol.FlagLocalMustRescan, + "FlagLocalReceiveOnly": protocol.FlagLocalReceiveOnly, + "FlagLocalGlobal": protocol.FlagLocalGlobal, + "FlagLocalNeeded": protocol.FlagLocalNeeded, + "LocalDeviceIdx": db.localDeviceIdx, + "SyncthingVersion": build.LongVersion, + } + + return db, nil +} + +var tplFuncs = template.FuncMap{ + "or": func(vs ...int) int { + v := vs[0] + for _, ov := range vs[1:] { + v |= ov + } + return v + }, +} + +// stmt returns a prepared statement for the given SQL string, after +// applying local template expansions. The statement is cached. +func (s *DB) stmt(tpl string) stmt { + tpl = strings.TrimSpace(tpl) + + // Fast concurrent lookup of cached statement + s.statementsMut.RLock() + stmt, ok := s.statements[tpl] + s.statementsMut.RUnlock() + if ok { + return stmt + } + + // On miss, take the full lock, check again + s.statementsMut.Lock() + defer s.statementsMut.Unlock() + stmt, ok = s.statements[tpl] + if ok { + return stmt + } + + // Apply template expansions + var sb strings.Builder + compTpl := template.Must(template.New("tpl").Funcs(tplFuncs).Parse(tpl)) + if err := compTpl.Execute(&sb, s.tplInput); err != nil { + panic("bug: bad template: " + err.Error()) + } + + // Prepare and cache + stmt, err := s.sql.Preparex(sb.String()) + if err != nil { + return failedStmt{err} + } + s.statements[tpl] = stmt + return stmt +} + +type stmt interface { + Exec(args ...any) (sql.Result, error) + Get(dest any, args ...any) error + Queryx(args ...any) (*sqlx.Rows, error) + Select(dest any, args ...any) error +} + +type failedStmt struct { + err error +} + +func (f failedStmt) Exec(_ ...any) (sql.Result, error) { return nil, f.err } +func (f failedStmt) Get(_ any, _ ...any) error { return f.err } +func (f failedStmt) Queryx(_ ...any) (*sqlx.Rows, error) { return nil, f.err } +func (f failedStmt) Select(_ any, _ ...any) error { return f.err } diff --git a/internal/db/sqlite/db_open_cgo.go b/internal/db/sqlite/db_open_cgo.go new file mode 100644 index 000000000..db86c6b4c --- /dev/null +++ b/internal/db/sqlite/db_open_cgo.go @@ -0,0 +1,18 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +//go:build cgo + +package sqlite + +import ( + _ "github.com/mattn/go-sqlite3" // register sqlite3 database driver +) + +const ( + dbDriver = "sqlite3" + commonOptions = "_fk=true&_rt=true&_cache_size=-65536&_sync=1&_txlock=immediate" +) diff --git a/internal/db/sqlite/db_open_nocgo.go b/internal/db/sqlite/db_open_nocgo.go new file mode 100644 index 000000000..ea33222e4 --- /dev/null +++ b/internal/db/sqlite/db_open_nocgo.go @@ -0,0 +1,23 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +//go:build !cgo && !wazero + +package sqlite + +import ( + "github.com/syncthing/syncthing/lib/build" + _ "modernc.org/sqlite" // register sqlite database driver +) + +const ( + dbDriver = "sqlite" + commonOptions = "_pragma=foreign_keys(1)&_pragma=recursive_triggers(1)&_pragma=cache_size(-65536)&_pragma=synchronous(1)" +) + +func init() { + build.AddTag("modernc-sqlite") +} diff --git a/internal/db/sqlite/db_prepared.go b/internal/db/sqlite/db_prepared.go new file mode 100644 index 000000000..b06281ea7 --- /dev/null +++ b/internal/db/sqlite/db_prepared.go @@ -0,0 +1,44 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import "github.com/jmoiron/sqlx" + +type txPreparedStmts struct { + *sqlx.Tx + stmts map[string]*sqlx.Stmt +} + +func (p *txPreparedStmts) Preparex(query string) (*sqlx.Stmt, error) { + if p.stmts == nil { + p.stmts = make(map[string]*sqlx.Stmt) + } + stmt, ok := p.stmts[query] + if ok { + return stmt, nil + } + stmt, err := p.Tx.Preparex(query) + if err != nil { + return nil, wrap(err) + } + p.stmts[query] = stmt + return stmt, nil +} + +func (p *txPreparedStmts) Commit() error { + for _, s := range p.stmts { + s.Close() + } + return p.Tx.Commit() +} + +func (p *txPreparedStmts) Rollback() error { + for _, s := range p.stmts { + s.Close() + } + return p.Tx.Rollback() +} diff --git a/internal/db/sqlite/db_schema.go b/internal/db/sqlite/db_schema.go new file mode 100644 index 000000000..89cebd7ac --- /dev/null +++ b/internal/db/sqlite/db_schema.go @@ -0,0 +1,88 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "embed" + "io/fs" + "strings" + "time" + + "github.com/syncthing/syncthing/lib/build" +) + +const currentSchemaVersion = 1 + +//go:embed sql/** +var embedded embed.FS + +func (s *DB) runScripts(glob string, filter ...func(s string) bool) error { + scripts, err := fs.Glob(embedded, glob) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.Begin() + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + +nextScript: + for _, scr := range scripts { + for _, fn := range filter { + if !fn(scr) { + l.Debugln("Skipping script", scr) + continue nextScript + } + } + l.Debugln("Executing script", scr) + bs, err := fs.ReadFile(embedded, scr) + if err != nil { + return wrap(err, scr) + } + // SQLite requires one statement per exec, so we split the init + // files on lines containing only a semicolon and execute them + // separately. We require it on a separate line because there are + // also statement-internal semicolons in the triggers. + for _, stmt := range strings.Split(string(bs), "\n;") { + if _, err := tx.Exec(stmt); err != nil { + return wrap(err, stmt) + } + } + } + + return wrap(tx.Commit()) +} + +type schemaVersion struct { + SchemaVersion int + AppliedAt int64 + SyncthingVersion string +} + +func (s *schemaVersion) AppliedTime() time.Time { + return time.Unix(0, s.AppliedAt) +} + +func (s *DB) setAppliedSchemaVersion(ver int) error { + _, err := s.stmt(` + INSERT OR IGNORE INTO schemamigrations (schema_version, applied_at, syncthing_version) + VALUES (?, ?, ?) + `).Exec(ver, time.Now().UnixNano(), build.LongVersion) + return wrap(err) +} + +func (s *DB) getAppliedSchemaVersion() (schemaVersion, error) { + var v schemaVersion + err := s.stmt(` + SELECT schema_version as schemaversion, applied_at as appliedat, syncthing_version as syncthingversion FROM schemamigrations + ORDER BY schema_version DESC + LIMIT 1 + `).Get(&v) + return v, wrap(err) +} diff --git a/internal/db/sqlite/db_service.go b/internal/db/sqlite/db_service.go new file mode 100644 index 000000000..3786fbc10 --- /dev/null +++ b/internal/db/sqlite/db_service.go @@ -0,0 +1,141 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "context" + "time" + + "github.com/syncthing/syncthing/internal/db" +) + +const ( + internalMetaPrefix = "dbsvc" + lastMaintKey = "lastMaint" +) + +type Service struct { + sdb *DB + maintenanceInterval time.Duration + internalMeta *db.Typed +} + +func newService(sdb *DB, maintenanceInterval time.Duration) *Service { + return &Service{ + sdb: sdb, + maintenanceInterval: maintenanceInterval, + internalMeta: db.NewTyped(sdb, internalMetaPrefix), + } +} + +func (s *Service) Serve(ctx context.Context) error { + // Run periodic maintenance + + // Figure out when we last ran maintenance and schedule accordingly. If + // it was never, do it now. + lastMaint, _, _ := s.internalMeta.Time(lastMaintKey) + nextMaint := lastMaint.Add(s.maintenanceInterval) + wait := time.Until(nextMaint) + if wait < 0 { + wait = time.Minute + } + l.Debugln("Next periodic run in", wait) + + timer := time.NewTimer(wait) + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + } + + if err := s.periodic(ctx); err != nil { + return wrap(err) + } + + timer.Reset(s.maintenanceInterval) + l.Debugln("Next periodic run in", s.maintenanceInterval) + _ = s.internalMeta.PutTime(lastMaintKey, time.Now()) + } +} + +func (s *Service) periodic(ctx context.Context) error { + t0 := time.Now() + l.Debugln("Periodic start") + + s.sdb.updateLock.Lock() + defer s.sdb.updateLock.Unlock() + + t1 := time.Now() + defer func() { l.Debugln("Periodic done in", time.Since(t1), "+", t1.Sub(t0)) }() + + if err := s.garbageCollectBlocklistsAndBlocksLocked(ctx); err != nil { + return wrap(err) + } + + _, _ = s.sdb.sql.ExecContext(ctx, `ANALYZE`) + _, _ = s.sdb.sql.ExecContext(ctx, `PRAGMA optimize`) + _, _ = s.sdb.sql.ExecContext(ctx, `PRAGMA incremental_vacuum`) + _, _ = s.sdb.sql.ExecContext(ctx, `PRAGMA wal_checkpoint(TRUNCATE)`) + + return nil +} + +func (s *Service) garbageCollectBlocklistsAndBlocksLocked(ctx context.Context) error { + // Remove all blocklists not referred to by any files and, by extension, + // any blocks not referred to by a blocklist. This is an expensive + // operation when run normally, especially if there are a lot of blocks + // to collect. + // + // We make this orders of magnitude faster by disabling foreign keys for + // the transaction and doing the cleanup manually. This requires using + // an explicit connection and disabling foreign keys before starting the + // transaction. We make sure to clean up on the way out. + + conn, err := s.sdb.sql.Connx(ctx) + if err != nil { + return wrap(err) + } + defer conn.Close() + + if _, err := conn.ExecContext(ctx, `PRAGMA foreign_keys = 0`); err != nil { + return wrap(err) + } + defer func() { //nolint:contextcheck + _, _ = conn.ExecContext(context.Background(), `PRAGMA foreign_keys = 1`) + }() + + tx, err := conn.BeginTxx(ctx, nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + + if res, err := tx.ExecContext(ctx, ` + DELETE FROM blocklists + WHERE NOT EXISTS ( + SELECT 1 FROM files WHERE files.blocklist_hash = blocklists.blocklist_hash + )`); err != nil { + return wrap(err, "delete blocklists") + } else if shouldDebug() { + rows, err := res.RowsAffected() + l.Debugln("Blocklist GC:", rows, err) + } + + if res, err := tx.ExecContext(ctx, ` + DELETE FROM blocks + WHERE NOT EXISTS ( + SELECT 1 FROM blocklists WHERE blocklists.blocklist_hash = blocks.blocklist_hash + )`); err != nil { + return wrap(err, "delete blocks") + } else if shouldDebug() { + rows, err := res.RowsAffected() + l.Debugln("Blocks GC:", rows, err) + } + + return wrap(tx.Commit()) +} diff --git a/internal/db/sqlite/db_test.go b/internal/db/sqlite/db_test.go new file mode 100644 index 000000000..82749d4a9 --- /dev/null +++ b/internal/db/sqlite/db_test.go @@ -0,0 +1,1145 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "errors" + "iter" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/internal/timeutil" + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" +) + +const ( + folderID = "test" + blockSize = 128 << 10 + dirSize = 128 +) + +func TestBasics(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + local := []protocol.FileInfo{ + genFile("test1", 1, 0), + genDir("test2", 0), + genFile("test2/a", 2, 0), + genFile("test2/b", 3, 0), + } + err = sdb.Update(folderID, protocol.LocalDeviceID, local) + if err != nil { + t.Fatal(err) + } + + // Some remote files + remote := []protocol.FileInfo{ + genFile("test3", 3, 101), + genFile("test4", 4, 102), + genFile("test1", 5, 103), + } + // All newer than the local ones + for i := range remote { + remote[i].Version = remote[i].Version.Update(42) + } + err = sdb.Update(folderID, protocol.DeviceID{42}, remote) + if err != nil { + t.Fatal(err) + } + const ( + localSize = (1+2+3)*blockSize + dirSize + remoteSize = (3 + 4 + 5) * blockSize + globalSize = (2+3+3+4+5)*blockSize + dirSize + needSizeLocal = remoteSize + needSizeRemote = (2+3)*blockSize + dirSize + ) + + t.Run("SchemaVersion", func(t *testing.T) { + ver, err := sdb.getAppliedSchemaVersion() + if err != nil { + t.Fatal(err) + } + if ver.SchemaVersion != currentSchemaVersion { + t.Log(ver) + t.Error("should be version 1") + } + if d := time.Since(ver.AppliedTime()); d > time.Minute || d < 0 { + t.Log(ver) + t.Error("suspicious applied tim") + } + }) + + t.Run("Local", func(t *testing.T) { + t.Parallel() + + fi, ok, err := sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test2/a") // exists + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatal("not found") + } + if fi.Name != filepath.FromSlash("test2/a") { + t.Fatal("should have got test2/a") + } + if len(fi.Blocks) != 2 { + t.Fatal("expected two blocks") + } + + _, ok, err = sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test3") // does not exist + if err != nil { + t.Fatal(err) + } + if ok { + t.Fatal("should be not found") + } + }) + + t.Run("Global", func(t *testing.T) { + t.Parallel() + + fi, ok, err := sdb.GetGlobalFile(folderID, "test1") + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatal("not found") + } + if fi.Size != 5*blockSize { + t.Fatal("should be the remote file") + } + }) + + t.Run("AllLocal", func(t *testing.T) { + t.Parallel() + + have := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFiles(folderID, protocol.LocalDeviceID)) + if len(have) != 4 { + t.Log(have) + t.Error("expected four files") + } + have = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFiles(folderID, protocol.DeviceID{42})) + if len(have) != 3 { + t.Log(have) + t.Error("expected three files") + } + }) + + t.Run("AllNeededNamesLocal", func(t *testing.T) { + t.Parallel() + + need := fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))) + if len(need) != 3 || need[0] != "test1" { + t.Log(need) + t.Error("expected three files, ordered alphabetically") + } + + need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 1, 0))) + if len(need) != 1 || need[0] != "test1" { + t.Log(need) + t.Error("expected one file, limited, ordered alphabetically") + } + need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderLargestFirst, 0, 0))) + if len(need) != 3 || need[0] != "test1" { // largest + t.Log(need) + t.Error("expected three files, ordered largest to smallest") + } + need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderSmallestFirst, 0, 0))) + if len(need) != 3 || need[0] != "test3" { // smallest + t.Log(need) + t.Error("expected three files, ordered smallest to largest") + } + + need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderNewestFirst, 0, 0))) + if len(need) != 3 || need[0] != "test1" { // newest + t.Log(need) + t.Error("expected three files, ordered newest to oldest") + } + need = fiNames(mustCollect[protocol.FileInfo](t)(sdb.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderOldestFirst, 0, 0))) + if len(need) != 3 || need[0] != "test3" { // oldest + t.Log(need) + t.Error("expected three files, ordered oldest to newest") + } + }) + + t.Run("LocalSize", func(t *testing.T) { + t.Parallel() + + // Local device + + c, err := sdb.CountLocal(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if c.Files != 3 { + t.Log(c) + t.Error("one file expected") + } + if c.Directories != 1 { + t.Log(c) + t.Error("one directory expected") + } + if c.Bytes != localSize { + t.Log(c) + t.Error("size unexpected") + } + + // Other device + + c, err = sdb.CountLocal(folderID, protocol.DeviceID{42}) + if err != nil { + t.Fatal(err) + } + if c.Files != 3 { + t.Log(c) + t.Error("three files expected") + } + if c.Directories != 0 { + t.Log(c) + t.Error("no directories expected") + } + if c.Bytes != remoteSize { + t.Log(c) + t.Error("size unexpected") + } + }) + + t.Run("GlobalSize", func(t *testing.T) { + t.Parallel() + + c, err := sdb.CountGlobal(folderID) + if err != nil { + t.Fatal(err) + } + if c.Files != 5 { + t.Log(c) + t.Error("five files expected") + } + if c.Directories != 1 { + t.Log(c) + t.Error("one directory expected") + } + if c.Bytes != int64(globalSize) { + t.Log(c) + t.Error("size unexpected") + } + }) + + t.Run("NeedSizeLocal", func(t *testing.T) { + t.Parallel() + + c, err := sdb.CountNeed(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if c.Files != 3 { + t.Log(c) + t.Error("three files expected") + } + if c.Directories != 0 { + t.Log(c) + t.Error("no directories expected") + } + if c.Bytes != needSizeLocal { + t.Log(c) + t.Error("size unexpected") + } + }) + + t.Run("NeedSizeRemote", func(t *testing.T) { + t.Parallel() + + c, err := sdb.CountNeed(folderID, protocol.DeviceID{42}) + if err != nil { + t.Fatal(err) + } + if c.Files != 2 { + t.Log(c) + t.Error("two files expected") + } + if c.Directories != 1 { + t.Log(c) + t.Error("one directory expected") + } + if c.Bytes != needSizeRemote { + t.Log(c) + t.Error("size unexpected") + } + }) + + t.Run("Folders", func(t *testing.T) { + t.Parallel() + + folders, err := sdb.ListFolders() + if err != nil { + t.Fatal(err) + } + if len(folders) != 1 || folders[0] != folderID { + t.Error("expected one folder") + } + }) + + t.Run("DevicesForFolder", func(t *testing.T) { + t.Parallel() + + devs, err := sdb.ListDevicesForFolder("test") + if err != nil { + t.Fatal(err) + } + if len(devs) != 1 || devs[0] != (protocol.DeviceID{42}) { + t.Log(devs) + t.Error("expected one device") + } + }) + + t.Run("Sequence", func(t *testing.T) { + t.Parallel() + + iid, err := sdb.GetIndexID(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if iid == 0 { + t.Log(iid) + t.Fatal("expected index ID") + } + + if seq, err := sdb.GetDeviceSequence(folderID, protocol.LocalDeviceID); err != nil { + t.Fatal(err) + } else if seq != 4 { + t.Log(seq) + t.Error("expected local sequence to match number of files inserted") + } + + if seq, err := sdb.GetDeviceSequence(folderID, protocol.DeviceID{42}); err != nil { + t.Fatal(err) + } else if seq != 103 { + t.Log(seq) + t.Error("expected remote sequence to match highest sent") + } + + // Non-existent should be zero and no error + if seq, err := sdb.GetDeviceSequence("trolol", protocol.LocalDeviceID); err != nil { + t.Fatal(err) + } else if seq != 0 { + t.Log(seq) + t.Error("expected zero sequence") + } + if seq, err := sdb.GetDeviceSequence("trolol", protocol.DeviceID{42}); err != nil { + t.Fatal(err) + } else if seq != 0 { + t.Log(seq) + t.Error("expected zero sequence") + } + if seq, err := sdb.GetDeviceSequence(folderID, protocol.DeviceID{99}); err != nil { + t.Fatal(err) + } else if seq != 0 { + t.Log(seq) + t.Error("expected zero sequence") + } + }) + + t.Run("AllGlobalPrefix", func(t *testing.T) { + t.Parallel() + + vals := mustCollect[db.FileMetadata](t)(sdb.AllGlobalFilesPrefix(folderID, "test2")) + + // Vals should be test2, test2/a, test2/b + if len(vals) != 3 { + t.Log(vals) + t.Error("expected three items") + } else if vals[0].Name != "test2" { + t.Error(vals) + } + + // Empty prefix should be all the files + vals = mustCollect[db.FileMetadata](t)(sdb.AllGlobalFilesPrefix(folderID, "")) + if len(vals) != 6 { + t.Log(vals) + t.Error("expected six items") + } + }) + + t.Run("AllLocalPrefix", func(t *testing.T) { + t.Parallel() + + vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test2")) + + // Vals should be test2, test2/a, test2/b + if len(vals) != 3 { + t.Log(vals) + t.Error("expected three items") + } else if vals[0].Name != "test2" { + t.Error(vals) + } + + // Empty prefix should be all the files + vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "")) + + if len(vals) != 4 { + t.Log(vals) + t.Error("expected four items") + } + }) + + t.Run("AllLocalSequenced", func(t *testing.T) { + t.Parallel() + + vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesBySequence(folderID, protocol.LocalDeviceID, 3, 0)) + + // Vals should be test2/a, test2/b + if len(vals) != 2 { + t.Log(vals) + t.Error("expected three items") + } else if vals[0].Name != filepath.FromSlash("test2/a") || vals[0].Sequence != 3 { + t.Error(vals) + } + }) +} + +func TestPrefixGlobbing(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + local := []protocol.FileInfo{ + genFile("test1", 1, 0), + genDir("test2", 0), + genFile("test2/a", 2, 0), + genDir("test2/b", 0), + genFile("test2/b/c", 3, 0), + } + err = sdb.Update(folderID, protocol.LocalDeviceID, local) + if err != nil { + t.Fatal(err) + } + + vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test2")) + + // Vals should be test2, test2/a, test2/b, test2/b/c + if len(vals) != 4 { + t.Log(vals) + t.Error("expected four items") + } else if vals[0].Name != "test2" || vals[3].Name != filepath.FromSlash("test2/b/c") { + t.Error(vals) + } + + // Empty prefix should be all the files + vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "")) + + if len(vals) != 5 { + t.Log(vals) + t.Error("expected five items") + } + + // Same as partial prefix + vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "tes")) + + if len(vals) != 5 { + t.Log(vals) + t.Error("expected five items") + } + + // Prefix should be case sensitive, so no match here + vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "tEsT2")) + + if len(vals) != 0 { + t.Log(vals) + t.Error("expected no items") + } + + // Subdir should match + vals = mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test2/b")) + + if len(vals) != 2 { + t.Log(vals) + t.Error("expected two items") + } +} + +func TestPrefixGlobbingStar(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + local := []protocol.FileInfo{ + genFile("test1a", 1, 0), + genFile("test*a", 2, 0), + genFile("test2a", 3, 0), + } + err = sdb.Update(folderID, protocol.LocalDeviceID, local) + if err != nil { + t.Fatal(err) + } + + vals := mustCollect[protocol.FileInfo](t)(sdb.AllLocalFilesWithPrefix(folderID, protocol.LocalDeviceID, "test*a")) + + // Vals should be test*a + if len(vals) != 1 { + t.Log(vals) + t.Error("expected one item") + } else if vals[0].Name != "test*a" { + t.Error(vals) + } +} + +func TestAvailability(t *testing.T) { + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + + const folderID = "test" + + // Some local files + err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{ + genFile("test1", 1, 0), + genFile("test2", 2, 0), + }) + if err != nil { + t.Fatal(err) + } + + // Some remote files + err = db.Update(folderID, protocol.DeviceID{42}, []protocol.FileInfo{ + genFile("test2", 2, 1), + genFile("test3", 3, 2), + }) + if err != nil { + t.Fatal(err) + } + + // Further remote files + err = db.Update(folderID, protocol.DeviceID{45}, []protocol.FileInfo{ + genFile("test3", 3, 1), + genFile("test4", 4, 2), + }) + if err != nil { + t.Fatal(err) + } + + a, err := db.GetGlobalAvailability(folderID, "test1") + if err != nil { + t.Fatal(err) + } + if len(a) != 0 { + t.Log(a) + t.Error("expected no availability (only local)") + } + + a, err = db.GetGlobalAvailability(folderID, "test2") + if err != nil { + t.Fatal(err) + } + if len(a) != 1 || a[0] != (protocol.DeviceID{42}) { + t.Log(a) + t.Error("expected one availability (only 42)") + } + + a, err = db.GetGlobalAvailability(folderID, "test3") + if err != nil { + t.Fatal(err) + } + if len(a) != 2 || a[0] != (protocol.DeviceID{42}) || a[1] != (protocol.DeviceID{45}) { + t.Log(a) + t.Error("expected two availabilities (both remotes)") + } + + if err := db.Close(); err != nil { + t.Fatal(err) + } +} + +func TestDropFilesNamed(t *testing.T) { + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + const folderID = "test" + + // Some local files + err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{ + genFile("test1", 1, 0), + genFile("test2", 2, 0), + }) + if err != nil { + t.Fatal(err) + } + + // Drop test1 + if err := db.DropFilesNamed(folderID, protocol.LocalDeviceID, []string{"test1"}); err != nil { + t.Fatal(err) + } + + // Check + if _, ok, err := db.GetDeviceFile(folderID, protocol.LocalDeviceID, "test1"); err != nil || ok { + t.Log(err, ok) + t.Error("expected to not exist") + } + if c, err := db.CountLocal(folderID, protocol.LocalDeviceID); err != nil { + t.Fatal(err) + } else if c.Files != 1 { + t.Log(c) + t.Error("expected count to be one") + } + if _, ok, err := db.GetDeviceFile(folderID, protocol.LocalDeviceID, "test2"); err != nil || !ok { + t.Log(err, ok) + t.Error("expected to exist") + } +} + +func TestDropFolder(t *testing.T) { + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + + // Folder A + err = db.Update("a", protocol.LocalDeviceID, []protocol.FileInfo{ + genFile("test1", 1, 0), + genFile("test2", 2, 0), + }) + if err != nil { + t.Fatal(err) + } + + // Folder B + err = db.Update("b", protocol.LocalDeviceID, []protocol.FileInfo{ + genFile("test1", 1, 0), + genFile("test2", 2, 0), + }) + if err != nil { + t.Fatal(err) + } + + // Drop A + if err := db.DropFolder("a"); err != nil { + t.Fatal(err) + } + + // Check + if _, ok, err := db.GetDeviceFile("a", protocol.LocalDeviceID, "test1"); err != nil || ok { + t.Log(err, ok) + t.Error("expected to not exist") + } + if c, err := db.CountLocal("a", protocol.LocalDeviceID); err != nil { + t.Fatal(err) + } else if c.Files != 0 { + t.Log(c) + t.Error("expected count to be zero") + } + + if _, ok, err := db.GetDeviceFile("b", protocol.LocalDeviceID, "test1"); err != nil || !ok { + t.Log(err, ok) + t.Error("expected to exist") + } + if c, err := db.CountLocal("b", protocol.LocalDeviceID); err != nil { + t.Fatal(err) + } else if c.Files != 2 { + t.Log(c) + t.Error("expected count to be two") + } +} + +func TestDropDevice(t *testing.T) { + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + + // Device 1 + err = db.Update("a", protocol.DeviceID{1}, []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + }) + if err != nil { + t.Fatal(err) + } + + // Device 2 + err = db.Update("a", protocol.DeviceID{2}, []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + }) + if err != nil { + t.Fatal(err) + } + + // Drop 1 + if err := db.DropDevice(protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } + + // Check + if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{1}, "test1"); err != nil || ok { + t.Log(err, ok) + t.Error("expected to not exist") + } + if c, err := db.CountLocal("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if c.Files != 0 { + t.Log(c) + t.Error("expected count to be zero") + } + if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{2}, "test1"); err != nil || !ok { + t.Log(err, ok) + t.Error("expected to exist") + } + if c, err := db.CountLocal("a", protocol.DeviceID{2}); err != nil { + t.Fatal(err) + } else if c.Files != 2 { + t.Log(c) + t.Error("expected count to be two") + } + + // Drop something that doesn't exist + if err := db.DropDevice(protocol.DeviceID{99}); err != nil { + t.Fatal(err) + } +} + +func TestDropAllFiles(t *testing.T) { + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Some local files + + // Device 1 folder A + err = db.Update("a", protocol.DeviceID{1}, []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + }) + if err != nil { + t.Fatal(err) + } + + // Device 1 folder B + err = db.Update("b", protocol.DeviceID{1}, []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + }) + if err != nil { + t.Fatal(err) + } + + // Drop folder A + if err := db.DropAllFiles("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } + + // Check + if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{1}, "test1"); err != nil || ok { + t.Log(err, ok) + t.Error("expected to not exist") + } + if c, err := db.CountLocal("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if c.Files != 0 { + t.Log(c) + t.Error("expected count to be zero") + } + if _, ok, err := db.GetDeviceFile("b", protocol.DeviceID{1}, "test1"); err != nil || !ok { + t.Log(err, ok) + t.Error("expected to exist") + } + if c, err := db.CountLocal("b", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if c.Files != 2 { + t.Log(c) + t.Error("expected count to be two") + } + + // Drop things that don't exist + if err := db.DropAllFiles("a", protocol.DeviceID{99}); err != nil { + t.Fatal(err) + } + if err := db.DropAllFiles("trolol", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } + if err := db.DropAllFiles("trolol", protocol.DeviceID{99}); err != nil { + t.Fatal(err) + } +} + +func TestConcurrentUpdate(t *testing.T) { + t.Parallel() + + db, err := Open(filepath.Join(t.TempDir(), "db")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + const folderID = "test" + + files := []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + genFile("test3", 3, 3), + genFile("test4", 4, 4), + } + + const n = 32 + res := make([]error, n) + var wg sync.WaitGroup + wg.Add(n) + for i := range n { + go func() { + res[i] = db.Update(folderID, protocol.DeviceID{byte(i), byte(i), byte(i)}, files) + wg.Done() + }() + } + wg.Wait() + for i, err := range res { + if err != nil { + t.Errorf("%d: %v", i, err) + } + } +} + +func TestConcurrentUpdateSelect(t *testing.T) { + t.Parallel() + + db, err := Open(filepath.Join(t.TempDir(), "db")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + const folderID = "test" + + // Some local files + files := []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + genFile("test3", 3, 3), + genFile("test4", 4, 4), + } + + // Insert the files for a remote device + if err := db.Update(folderID, protocol.DeviceID{42}, files); err != nil { + t.Fatal() + } + + // Iterate over handled files and insert them for the local device. + // This is similar to a pattern we have in other places and should + // work. + handled := 0 + it, errFn := db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0) + for glob := range it { + glob.Version = glob.Version.Update(1) + if err := db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{glob}); err != nil { + t.Fatal(err) + } + handled++ + } + if err := errFn(); err != nil { + t.Fatal(err) + } + + if handled != len(files) { + t.Log(handled) + t.Error("should have handled all the files") + } +} + +func TestAllForBlocksHash(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // test1 is unique, while test2 and test3 have the same blocks and hence + // the same blocks hash + + files := []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + genFile("test3", 3, 3), + } + files[2].Blocks = files[1].Blocks + + if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil { + t.Fatal(err) + } + + // Check test1 + + test1, ok, err := sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test1") + if err != nil || !ok { + t.Fatal("expected to exist") + } + + vals := mustCollect[db.FileMetadata](t)(sdb.AllLocalFilesWithBlocksHash(folderID, test1.BlocksHash)) + if len(vals) != 1 { + t.Log(vals) + t.Fatal("expected one file to match") + } + + // Check test2 which also matches test3 + + test2, ok, err := sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "test2") + if err != nil || !ok { + t.Fatal("expected to exist") + } + + vals = mustCollect[db.FileMetadata](t)(sdb.AllLocalFilesWithBlocksHash(folderID, test2.BlocksHash)) + if len(vals) != 2 { + t.Log(vals) + t.Fatal("expected two files to match") + } + if vals[0].Name != "test2" { + t.Log(vals[0]) + t.Error("expected test2") + } + if vals[1].Name != "test3" { + t.Log(vals[1]) + t.Error("expected test3") + } +} + +func TestBlocklistGarbageCollection(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + svc := sdb.Service(time.Hour).(*Service) + + // Add three files + + files := []protocol.FileInfo{ + genFile("test1", 1, 1), + genFile("test2", 2, 2), + genFile("test3", 3, 3), + } + + if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil { + t.Fatal(err) + } + + // There should exist three blockslists and six blocks + + var count int + if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil { + t.Fatal(err) + } + if count != 3 { + t.Log(count) + t.Fatal("expected 3 blocklists") + } + if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil { + t.Fatal(err) + } + if count != 6 { + t.Log(count) + t.Fatal("expected 6 blocks") + } + + // Mark test3 as deleted, it's blocks and blocklist are now eligible for collection + files = files[2:] + files[0].SetDeleted(42) + if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil { + t.Fatal(err) + } + + // Run garbage collection + if err := svc.periodic(context.Background()); err != nil { + t.Fatal(err) + } + + // There should exist two blockslists and four blocks + + if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil { + t.Fatal(err) + } + if count != 2 { + t.Log(count) + t.Error("expected 2 blocklists") + } + if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil { + t.Fatal(err) + } + if count != 3 { + t.Log(count) + t.Error("expected 3 blocks") + } +} + +func TestErrorWrap(t *testing.T) { + if wrap(nil, "foo") != nil { + t.Fatal("nil should wrap to nil") + } + + fooErr := errors.New("foo") + if err := wrap(fooErr); err.Error() != "testerrorwrap: foo" { + t.Fatalf("%q", err) + } + + if err := wrap(fooErr, "bar", "baz"); err.Error() != "testerrorwrap (bar, baz): foo" { + t.Fatalf("%q", err) + } +} + +func mustCollect[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) []T { + t.Helper() + return func(it iter.Seq[T], errFn func() error) []T { + t.Helper() + vals, err := itererr.Collect(it, errFn) + if err != nil { + t.Fatal(err) + } + return vals + } +} + +func fiNames(fs []protocol.FileInfo) []string { + names := make([]string, len(fs)) + for i, fi := range fs { + names[i] = fi.Name + } + return names +} + +func genDir(name string, seq int) protocol.FileInfo { + return protocol.FileInfo{ + Name: name, + Type: protocol.FileInfoTypeDirectory, + ModifiedS: time.Now().Unix(), + ModifiedBy: 1, + Sequence: int64(seq), + Version: protocol.Vector{}.Update(1), + Permissions: 0o755, + ModifiedNs: 12345678, + } +} + +func genFile(name string, numBlocks int, seq int) protocol.FileInfo { + ts := timeutil.StrictlyMonotonicNanos() + s := ts / 1e9 + ns := int32(ts % 1e9) + return protocol.FileInfo{ + Name: name, + Size: int64(numBlocks) * blockSize, + ModifiedS: s, + ModifiedBy: 1, + Version: protocol.Vector{}.Update(1), + Sequence: int64(seq), + Blocks: genBlocks(name, 0, numBlocks), + Permissions: 0o644, + ModifiedNs: ns, + RawBlockSize: blockSize, + } +} + +func genBlocks(name string, seed, count int) []protocol.BlockInfo { + b := make([]protocol.BlockInfo, count) + for i := range b { + b[i].Hash = genBlockHash(name, seed, i) + b[i].Size = blockSize + b[i].Offset = (blockSize) * int64(i) + } + return b +} + +func genBlockHash(name string, seed, index int) []byte { + bs := sha256.Sum256([]byte(name)) + ebs := binary.LittleEndian.AppendUint64(nil, uint64(seed)) + for i := range ebs { + bs[i] ^= ebs[i] + } + ebs = binary.LittleEndian.AppendUint64(nil, uint64(index)) + for i := range ebs { + bs[i] ^= ebs[i] + } + return bs[:] +} diff --git a/internal/db/sqlite/db_update.go b/internal/db/sqlite/db_update.go new file mode 100644 index 000000000..1cfa652c5 --- /dev/null +++ b/internal/db/sqlite/db_update.go @@ -0,0 +1,549 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "cmp" + "context" + "fmt" + "runtime" + "slices" + "strings" + + "github.com/jmoiron/sqlx" + "github.com/syncthing/syncthing/internal/gen/dbproto" + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/lib/osutil" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/sliceutil" + "google.golang.org/protobuf/proto" +) + +func (s *DB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + + folderIdx, err := s.folderIdxLocked(folder) + if err != nil { + return wrap(err) + } + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + //nolint:sqlclosecheck + insertFileStmt, err := txp.Preparex(` + INSERT OR REPLACE INTO files (folder_idx, device_idx, remote_sequence, name, type, modified, size, version, deleted, invalid, local_flags, blocklist_hash) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + RETURNING sequence + `) + if err != nil { + return wrap(err, "prepare insert file") + } + + //nolint:sqlclosecheck + insertFileInfoStmt, err := txp.Preparex(` + INSERT INTO fileinfos (sequence, fiprotobuf) + VALUES (?, ?) + `) + if err != nil { + return wrap(err, "prepare insert fileinfo") + } + + //nolint:sqlclosecheck + insertBlockListStmt, err := txp.Preparex(` + INSERT OR IGNORE INTO blocklists (blocklist_hash, blprotobuf) + VALUES (?, ?) + `) + if err != nil { + return wrap(err, "prepare insert blocklist") + } + + var prevRemoteSeq int64 + for i, f := range fs { + f.Name = osutil.NormalizedFilename(f.Name) + + var blockshash *[]byte + if len(f.Blocks) > 0 { + f.BlocksHash = protocol.BlocksHash(f.Blocks) + blockshash = &f.BlocksHash + } else { + f.BlocksHash = nil + } + + if f.Type == protocol.FileInfoTypeDirectory { + f.Size = 128 // synthetic directory size + } + + // Insert the file. + // + // If it is a remote file, set remote_sequence otherwise leave it at + // null. Returns the new local sequence. + var remoteSeq *int64 + if device != protocol.LocalDeviceID { + if i > 0 && f.Sequence == prevRemoteSeq { + return fmt.Errorf("duplicate remote sequence number %d", prevRemoteSeq) + } + prevRemoteSeq = f.Sequence + remoteSeq = &f.Sequence + } + var localSeq int64 + if err := insertFileStmt.Get(&localSeq, folderIdx, deviceIdx, remoteSeq, f.Name, f.Type, f.ModTime().UnixNano(), f.Size, f.Version.String(), f.IsDeleted(), f.IsInvalid(), f.LocalFlags, blockshash); err != nil { + return wrap(err, "insert file") + } + + if len(f.Blocks) > 0 { + // Indirect the block list + blocks := sliceutil.Map(f.Blocks, protocol.BlockInfo.ToWire) + bs, err := proto.Marshal(&dbproto.BlockList{Blocks: blocks}) + if err != nil { + return wrap(err, "marshal blocklist") + } + if _, err := insertBlockListStmt.Exec(f.BlocksHash, bs); err != nil { + return wrap(err, "insert blocklist") + } + + if device == protocol.LocalDeviceID { + // Insert all blocks + if err := s.insertBlocksLocked(txp, f.BlocksHash, f.Blocks); err != nil { + return wrap(err, "insert blocks") + } + } + + f.Blocks = nil + } + + // Insert the fileinfo + if device == protocol.LocalDeviceID { + f.Sequence = localSeq + } + bs, err := proto.Marshal(f.ToWire(true)) + if err != nil { + return wrap(err, "marshal fileinfo") + } + if _, err := insertFileInfoStmt.Exec(localSeq, bs); err != nil { + return wrap(err, "insert fileinfo") + } + + // Update global and need + if err := s.recalcGlobalForFile(txp, folderIdx, f.Name); err != nil { + return wrap(err) + } + } + + return wrap(tx.Commit()) +} + +func (s *DB) DropFolder(folder string) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + _, err := s.stmt(` + DELETE FROM folders + WHERE folder_id = ? + `).Exec(folder) + return wrap(err) +} + +func (s *DB) DropDevice(device protocol.DeviceID) error { + if device == protocol.LocalDeviceID { + panic("bug: cannot drop local device") + } + + s.updateLock.Lock() + defer s.updateLock.Unlock() + + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + // Find all folders where the device is involved + var folderIdxs []int64 + if err := tx.Select(&folderIdxs, ` + SELECT folder_idx + FROM counts + WHERE device_idx = ? AND count > 0 + GROUP BY folder_idx + `, deviceIdx); err != nil { + return wrap(err) + } + + // Drop the device, which cascades to delete all files etc for it + if _, err := tx.Exec(`DELETE FROM devices WHERE device_id = ?`, device.String()); err != nil { + return wrap(err) + } + + // Recalc the globals for all affected folders + for _, idx := range folderIdxs { + if err := s.recalcGlobalForFolder(txp, idx); err != nil { + return wrap(err) + } + } + + return wrap(tx.Commit()) +} + +func (s *DB) DropAllFiles(folder string, device protocol.DeviceID) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + + // This is a two part operation, first dropping all the files and then + // recalculating the global state for the entire folder. + + folderIdx, err := s.folderIdxLocked(folder) + if err != nil { + return wrap(err) + } + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + // Drop all the file entries + + result, err := tx.Exec(` + DELETE FROM files + WHERE folder_idx = ? AND device_idx = ? + `, folderIdx, deviceIdx) + if err != nil { + return wrap(err) + } + if n, err := result.RowsAffected(); err == nil && n == 0 { + // The delete affected no rows, so we don't need to redo the entire + // global/need calculation. + return wrap(tx.Commit()) + } + + // Recalc global for the entire folder + + if err := s.recalcGlobalForFolder(txp, folderIdx); err != nil { + return wrap(err) + } + return wrap(tx.Commit()) +} + +func (s *DB) DropFilesNamed(folder string, device protocol.DeviceID, names []string) error { + for i := range names { + names[i] = osutil.NormalizedFilename(names[i]) + } + + s.updateLock.Lock() + defer s.updateLock.Unlock() + + folderIdx, err := s.folderIdxLocked(folder) + if err != nil { + return wrap(err) + } + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + // Drop the named files + + query, args, err := sqlx.In(` + DELETE FROM files + WHERE folder_idx = ? AND device_idx = ? AND name IN (?) + `, folderIdx, deviceIdx, names) + if err != nil { + return wrap(err) + } + if _, err := tx.Exec(query, args...); err != nil { + return wrap(err) + } + + // Recalc globals for the named files + + for _, name := range names { + if err := s.recalcGlobalForFile(txp, folderIdx, name); err != nil { + return wrap(err) + } + } + + return wrap(tx.Commit()) +} + +func (*DB) insertBlocksLocked(tx *txPreparedStmts, blocklistHash []byte, blocks []protocol.BlockInfo) error { + if len(blocks) == 0 { + return nil + } + bs := make([]map[string]any, len(blocks)) + for i, b := range blocks { + bs[i] = map[string]any{ + "hash": b.Hash, + "blocklist_hash": blocklistHash, + "idx": i, + "offset": b.Offset, + "size": b.Size, + } + } + _, err := tx.NamedExec(` + INSERT OR IGNORE INTO blocks (hash, blocklist_hash, idx, offset, size) + VALUES (:hash, :blocklist_hash, :idx, :offset, :size) + `, bs) + return wrap(err) +} + +func (s *DB) recalcGlobalForFolder(txp *txPreparedStmts, folderIdx int64) error { + // Select files where there is no global, those are the ones we need to + // recalculate. + //nolint:sqlclosecheck + namesStmt, err := txp.Preparex(` + SELECT f.name FROM files f + WHERE f.folder_idx = ? AND NOT EXISTS ( + SELECT 1 FROM files g + WHERE g.folder_idx = ? AND g.name = f.name AND g.local_flags & ? != 0 + ) + GROUP BY name + `) + if err != nil { + return wrap(err) + } + rows, err := namesStmt.Queryx(folderIdx, folderIdx, protocol.FlagLocalGlobal) + if err != nil { + return wrap(err) + } + defer rows.Close() + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return wrap(err) + } + if err := s.recalcGlobalForFile(txp, folderIdx, name); err != nil { + return wrap(err) + } + } + return wrap(rows.Err()) +} + +func (s *DB) recalcGlobalForFile(txp *txPreparedStmts, folderIdx int64, file string) error { + //nolint:sqlclosecheck + selStmt, err := txp.Preparex(` + SELECT name, folder_idx, device_idx, sequence, modified, version, deleted, invalid, local_flags FROM files + WHERE folder_idx = ? AND name = ? + `) + if err != nil { + return wrap(err) + } + es, err := itererr.Collect(iterStructs[fileRow](selStmt.Queryx(folderIdx, file))) + if err != nil { + return wrap(err) + } + if len(es) == 0 { + // shouldn't happen + return nil + } + + // Sort the entries; the global entry is at the head of the list + slices.SortFunc(es, fileRow.Compare) + + // The global version is the first one in the list that is not invalid, + // or just the first one in the list if all are invalid. + var global fileRow + globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.Invalid }) + if globIdx < 0 { + globIdx = 0 + } + global = es[globIdx] + + // We "have" the file if the position in the list of versions is at the + // global version or better, or if the version is the same as the global + // file (we might be further down the list due to invalid flags), or if + // the global is deleted and we don't have it at all... + localIdx := slices.IndexFunc(es, func(e fileRow) bool { return e.DeviceIdx == s.localDeviceIdx }) + hasLocal := localIdx >= 0 && localIdx <= globIdx || // have a better or equal version + localIdx >= 0 && es[localIdx].Version.Equal(global.Version.Vector) || // have an equal version but invalid/ignored + localIdx < 0 && global.Deleted // missing it, but the global is also deleted + + // Set the global flag on the global entry. Set the need flag if the + // local device needs this file, unless it's invalid. + global.LocalFlags |= protocol.FlagLocalGlobal + if hasLocal || global.Invalid { + global.LocalFlags &= ^protocol.FlagLocalNeeded + } else { + global.LocalFlags |= protocol.FlagLocalNeeded + } + //nolint:sqlclosecheck + upStmt, err := txp.Prepare(` + UPDATE files SET local_flags = ? + WHERE folder_idx = ? AND device_idx = ? AND sequence = ? + `) + if err != nil { + return wrap(err) + } + if _, err := upStmt.Exec(global.LocalFlags, global.FolderIdx, global.DeviceIdx, global.Sequence); err != nil { + return wrap(err) + } + + // Clear the need and global flags on all other entries + //nolint:sqlclosecheck + upStmt, err = txp.Prepare(` + UPDATE files SET local_flags = local_flags & ? + WHERE folder_idx = ? AND name = ? AND sequence != ? AND local_flags & ? != 0 + `) + if err != nil { + return wrap(err) + } + if _, err := upStmt.Exec(^(protocol.FlagLocalNeeded | protocol.FlagLocalGlobal), folderIdx, global.Name, global.Sequence, protocol.FlagLocalNeeded|protocol.FlagLocalGlobal); err != nil { + return wrap(err) + } + + return nil +} + +func (s *DB) folderIdxLocked(folderID string) (int64, error) { + if _, err := s.stmt(` + INSERT OR IGNORE INTO folders(folder_id) + VALUES (?) + `).Exec(folderID); err != nil { + return 0, wrap(err) + } + var idx int64 + if err := s.stmt(` + SELECT idx FROM folders + WHERE folder_id = ? + `).Get(&idx, folderID); err != nil { + return 0, wrap(err) + } + + return idx, nil +} + +func (s *DB) deviceIdxLocked(deviceID protocol.DeviceID) (int64, error) { + devStr := deviceID.String() + if _, err := s.stmt(` + INSERT OR IGNORE INTO devices(device_id) + VALUES (?) + `).Exec(devStr); err != nil { + return 0, wrap(err) + } + var idx int64 + if err := s.stmt(` + SELECT idx FROM devices + WHERE device_id = ? + `).Get(&idx, devStr); err != nil { + return 0, wrap(err) + } + + return idx, nil +} + +// wrap returns the error wrapped with the calling function name and +// optional extra context strings as prefix. A nil error wraps to nil. +func wrap(err error, context ...string) error { + if err == nil { + return nil + } + + prefix := "error" + pc, _, _, ok := runtime.Caller(1) + details := runtime.FuncForPC(pc) + if ok && details != nil { + prefix = strings.ToLower(details.Name()) + if dotIdx := strings.LastIndex(prefix, "."); dotIdx > 0 { + prefix = prefix[dotIdx+1:] + } + } + + if len(context) > 0 { + for i := range context { + context[i] = strings.TrimSpace(context[i]) + } + extra := strings.Join(context, ", ") + return fmt.Errorf("%s (%s): %w", prefix, extra, err) + } + + return fmt.Errorf("%s: %w", prefix, err) +} + +type fileRow struct { + Name string + Version dbVector + FolderIdx int64 `db:"folder_idx"` + DeviceIdx int64 `db:"device_idx"` + Sequence int64 + Modified int64 + Size int64 + LocalFlags int64 `db:"local_flags"` + Deleted bool + Invalid bool +} + +func (e fileRow) Compare(other fileRow) int { + // From FileInfo.WinsConflict + vc := e.Version.Vector.Compare(other.Version.Vector) + switch vc { + case protocol.Equal: + if e.Invalid != other.Invalid { + if e.Invalid { + return 1 + } + return -1 + } + + // Compare the device ID index, lower is better. This is only + // deterministic to the extent that LocalDeviceID will always be the + // lowest one, order between remote devices is random (and + // irrelevant). + return cmp.Compare(e.DeviceIdx, other.DeviceIdx) + case protocol.Greater: // we are newer + return -1 + case protocol.Lesser: // we are older + return 1 + case protocol.ConcurrentGreater, protocol.ConcurrentLesser: // there is a conflict + if e.Invalid != other.Invalid { + if e.Invalid { // we are invalid, we lose + return 1 + } + return -1 // they are invalid, we win + } + if e.Deleted != other.Deleted { + if e.Deleted { // we are deleted, we lose + return 1 + } + return -1 // they are deleted, we win + } + if d := cmp.Compare(e.Modified, other.Modified); d != 0 { + return -d // positive d means we were newer, so we win (negative return) + } + if vc == protocol.ConcurrentGreater { + return -1 // we have a better device ID, we win + } + return 1 // they win + default: + return 0 + } +} diff --git a/lib/db/debug.go b/internal/db/sqlite/debug.go similarity index 58% rename from lib/db/debug.go rename to internal/db/sqlite/debug.go index 26abbaf01..acc711a2e 100644 --- a/lib/db/debug.go +++ b/internal/db/sqlite/debug.go @@ -1,17 +1,15 @@ -// Copyright (C) 2014 The Syncthing Authors. +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. -package db +package sqlite import ( "github.com/syncthing/syncthing/lib/logger" ) -var l = logger.DefaultLogger.NewFacility("db", "The database layer") +var l = logger.DefaultLogger.NewFacility("sqlite", "SQLite database") -func shouldDebug() bool { - return l.ShouldDebug("db") -} +func shouldDebug() bool { return l.ShouldDebug("sqlite") } diff --git a/internal/db/sqlite/sql/README.md b/internal/db/sqlite/sql/README.md new file mode 100644 index 000000000..42929fd51 --- /dev/null +++ b/internal/db/sqlite/sql/README.md @@ -0,0 +1,8 @@ +These SQL scripts are embedded in the binary. + +Scripts in `schema/` are run at every startup, in alphanumerical order. + +Scripts in `migrations/` are run when a migration is needed; the must begin +with a number that equals the schema version that results from that +migration. Migrations are not run on initial database creation, so the +scripts in `schema/` should create the latest version. diff --git a/internal/db/sqlite/sql/migrations/01-placeholder.sql b/internal/db/sqlite/sql/migrations/01-placeholder.sql new file mode 100644 index 000000000..30ec3e8c3 --- /dev/null +++ b/internal/db/sqlite/sql/migrations/01-placeholder.sql @@ -0,0 +1,7 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- The next migration should be number two. \ No newline at end of file diff --git a/internal/db/sqlite/sql/schema/00-indexes.sql b/internal/db/sqlite/sql/schema/00-indexes.sql new file mode 100644 index 000000000..1152d3b36 --- /dev/null +++ b/internal/db/sqlite/sql/schema/00-indexes.sql @@ -0,0 +1,19 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- folders map folder IDs as used by Syncthing to database folder indexes +CREATE TABLE IF NOT EXISTS folders ( + idx INTEGER NOT NULL PRIMARY KEY, + folder_id TEXT NOT NULL UNIQUE COLLATE BINARY +) STRICT +; + +-- devices map device IDs as used by Syncthing to database device indexes +CREATE TABLE IF NOT EXISTS devices ( + idx INTEGER NOT NULL PRIMARY KEY, + device_id TEXT NOT NULL UNIQUE COLLATE BINARY +) STRICT +; diff --git a/internal/db/sqlite/sql/schema/10-schema.sql b/internal/db/sqlite/sql/schema/10-schema.sql new file mode 100644 index 000000000..ce636dbd4 --- /dev/null +++ b/internal/db/sqlite/sql/schema/10-schema.sql @@ -0,0 +1,14 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- Schema migrations hold the list of historical migrations applied +CREATE TABLE IF NOT EXISTS schemamigrations ( + schema_version INTEGER NOT NULL, + applied_at INTEGER NOT NULL, -- unix nanos + syncthing_version TEXT NOT NULL COLLATE BINARY, + PRIMARY KEY(schema_version) +) STRICT +; diff --git a/internal/db/sqlite/sql/schema/20-files.sql b/internal/db/sqlite/sql/schema/20-files.sql new file mode 100644 index 000000000..ca16be9c3 --- /dev/null +++ b/internal/db/sqlite/sql/schema/20-files.sql @@ -0,0 +1,62 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- Files +-- +-- The files table contains all files announced by any device. Files present +-- on this device are filed under the LocalDeviceID, not the actual current +-- device ID, for simplicity, consistency and portability. One announced +-- version of each file is considered the "global" version - the latest one, +-- that all other devices strive to replicate. This instance gets the Global +-- flag bit set. There may be other identical instances of this file +-- announced by other devices, but only one onstance gets the Global flag; +-- this simplifies accounting. If the current device has the Global version, +-- the LocalDeviceID instance of the file is the one that has the Global +-- bit. +-- +-- If the current device does not have that version of the file it gets the +-- Need bit set. Only Global files announced by another device can have the +-- Need bit. This allows for very efficient lookup of files needing handling +-- on this device, which is a common query. +CREATE TABLE IF NOT EXISTS files ( + folder_idx INTEGER NOT NULL, + device_idx INTEGER NOT NULL, -- actual device ID or LocalDeviceID + sequence INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, -- our local database sequence, for each and every entry + remote_sequence INTEGER, -- remote device's sequence number, null for local or synthetic entries + name TEXT NOT NULL COLLATE BINARY, + type INTEGER NOT NULL, -- protocol.FileInfoType + modified INTEGER NOT NULL, -- Unix nanos + size INTEGER NOT NULL, + version TEXT NOT NULL COLLATE BINARY, + deleted INTEGER NOT NULL, -- boolean + invalid INTEGER NOT NULL, -- boolean + local_flags INTEGER NOT NULL, + blocklist_hash BLOB, -- null when there are no blocks + FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE, + FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE +) STRICT +; +-- FileInfos store the actual protobuf object. We do this separately to keep +-- the files rows smaller and more efficient. +CREATE TABLE IF NOT EXISTS fileinfos ( + sequence INTEGER NOT NULL PRIMARY KEY, -- our local database sequence from the files table + fiprotobuf BLOB NOT NULL, + FOREIGN KEY(sequence) REFERENCES files(sequence) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED +) STRICT +; +-- There can be only one file per folder, device, and remote sequence number +CREATE UNIQUE INDEX IF NOT EXISTS files_remote_sequence ON files (folder_idx, device_idx, remote_sequence) + WHERE remote_sequence IS NOT NULL +; +-- There can be only one file per folder, device, and name +CREATE UNIQUE INDEX IF NOT EXISTS files_device_name ON files (folder_idx, device_idx, name) +; +-- We want to be able to look up & iterate files based on just folder and name +CREATE INDEX IF NOT EXISTS files_name_only ON files (folder_idx, name) +; +-- We want to be able to look up & iterate files based on blocks hash +CREATE INDEX IF NOT EXISTS files_blocklist_hash_only ON files (blocklist_hash, device_idx, folder_idx) WHERE blocklist_hash IS NOT NULL +; diff --git a/internal/db/sqlite/sql/schema/30-indexids.sql b/internal/db/sqlite/sql/schema/30-indexids.sql new file mode 100644 index 000000000..dde9851bc --- /dev/null +++ b/internal/db/sqlite/sql/schema/30-indexids.sql @@ -0,0 +1,24 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- indexids holds the index ID and maximum sequence for a given device and folder +CREATE TABLE IF NOT EXISTS indexids ( + device_idx INTEGER NOT NULL, + folder_idx INTEGER NOT NULL, + index_id TEXT NOT NULL COLLATE BINARY, + sequence INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY(device_idx, folder_idx), + FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE, + FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE +) STRICT, WITHOUT ROWID +; +CREATE TRIGGER IF NOT EXISTS indexids_seq AFTER INSERT ON files +BEGIN + INSERT INTO indexids (folder_idx, device_idx, index_id, sequence) + VALUES (NEW.folder_idx, NEW.device_idx, "", COALESCE(NEW.remote_sequence, NEW.sequence)) + ON CONFLICT DO UPDATE SET sequence = COALESCE(NEW.remote_sequence, NEW.sequence); +END +; diff --git a/internal/db/sqlite/sql/schema/40-counts.sql b/internal/db/sqlite/sql/schema/40-counts.sql new file mode 100644 index 000000000..cac1851fb --- /dev/null +++ b/internal/db/sqlite/sql/schema/40-counts.sql @@ -0,0 +1,53 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- Counts +-- +-- Counts and sizes are maintained for each device, folder, type, flag bits +-- combination. +CREATE TABLE IF NOT EXISTS counts ( + folder_idx INTEGER NOT NULL, + device_idx INTEGER NOT NULL, + type INTEGER NOT NULL, + local_flags INTEGER NOT NULL, + count INTEGER NOT NULL, + size INTEGER NOT NULL, + deleted INTEGER NOT NULL, -- boolean + PRIMARY KEY(folder_idx, device_idx, type, local_flags, deleted), + FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE, + FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE +) STRICT, WITHOUT ROWID +; + +--- Maintain counts when files are added and removed using triggers + +CREATE TRIGGER IF NOT EXISTS counts_insert AFTER INSERT ON files +BEGIN + INSERT INTO counts (folder_idx, device_idx, type, local_flags, count, size, deleted) + VALUES (NEW.folder_idx, NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) + ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size; +END +; +CREATE TRIGGER IF NOT EXISTS counts_delete AFTER DELETE ON files +BEGIN + UPDATE counts SET count = count - 1, size = size - OLD.size + WHERE folder_idx = OLD.folder_idx AND device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted; +END +; +CREATE TRIGGER IF NOT EXISTS counts_update AFTER UPDATE OF local_flags ON files +WHEN NEW.local_flags != OLD.local_flags +BEGIN + INSERT INTO counts (folder_idx, device_idx, type, local_flags, count, size, deleted) + VALUES (NEW.folder_idx, NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) + ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size; + UPDATE counts SET count = count - 1, size = size - OLD.size + WHERE folder_idx = OLD.folder_idx AND device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted; +END +; +DROP TRIGGER IF EXISTS counts_update_add -- tmp migration +; +DROP TRIGGER IF EXISTS counts_update_del -- tmp migration +; diff --git a/internal/db/sqlite/sql/schema/50-blocks.sql b/internal/db/sqlite/sql/schema/50-blocks.sql new file mode 100644 index 000000000..17c8e40a0 --- /dev/null +++ b/internal/db/sqlite/sql/schema/50-blocks.sql @@ -0,0 +1,34 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- Block lists +-- +-- The block lists are extracted from FileInfos and stored separately. This +-- reduces the database size by reusing the same block list entry for all +-- devices announcing the same file. Doing it for all block lists instead of +-- using a size cutoff simplifies queries. Block lists are garbage collected +-- "manually", not using a trigger as that was too performance impacting. +CREATE TABLE IF NOT EXISTS blocklists ( + blocklist_hash BLOB NOT NULL PRIMARY KEY, + blprotobuf BLOB NOT NULL +) STRICT +; + +-- Blocks +-- +-- For all local files we store the blocks individually for quick lookup. A +-- given block can exist in multiple blocklists and at multiple offsets in a +-- blocklist. +CREATE TABLE IF NOT EXISTS blocks ( + hash BLOB NOT NULL, + blocklist_hash BLOB NOT NULL, + idx INTEGER NOT NULL, + offset INTEGER NOT NULL, + size INTEGER NOT NULL, + PRIMARY KEY (hash, blocklist_hash, idx), + FOREIGN KEY(blocklist_hash) REFERENCES blocklists(blocklist_hash) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED +) STRICT +; diff --git a/internal/db/sqlite/sql/schema/50-mtimes.sql b/internal/db/sqlite/sql/schema/50-mtimes.sql new file mode 100644 index 000000000..1d6f6b5ba --- /dev/null +++ b/internal/db/sqlite/sql/schema/50-mtimes.sql @@ -0,0 +1,16 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +--- Backing for the MtimeFS +CREATE TABLE IF NOT EXISTS mtimes ( + folder_idx INTEGER NOT NULL, + name TEXT NOT NULL, + ondisk INTEGER NOT NULL, -- unix nanos + virtual INTEGER NOT NULL, -- unix nanos + PRIMARY KEY(folder_idx, name), + FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE +) STRICT, WITHOUT ROWID +; diff --git a/internal/db/sqlite/sql/schema/70-kv.sql b/internal/db/sqlite/sql/schema/70-kv.sql new file mode 100644 index 000000000..e723ef467 --- /dev/null +++ b/internal/db/sqlite/sql/schema/70-kv.sql @@ -0,0 +1,13 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +--- Simple KV store. This backs the "miscDB" we use for certain minor pieces +-- of data. +CREATE TABLE IF NOT EXISTS kv ( + key TEXT NOT NULL PRIMARY KEY COLLATE BINARY, + value BLOB NOT NULL +) STRICT +; diff --git a/internal/db/sqlite/util.go b/internal/db/sqlite/util.go new file mode 100644 index 000000000..3d734d711 --- /dev/null +++ b/internal/db/sqlite/util.go @@ -0,0 +1,117 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "database/sql/driver" + "errors" + "iter" + + "github.com/jmoiron/sqlx" + "github.com/syncthing/syncthing/internal/gen/bep" + "github.com/syncthing/syncthing/internal/gen/dbproto" + "github.com/syncthing/syncthing/lib/osutil" + "github.com/syncthing/syncthing/lib/protocol" + "google.golang.org/protobuf/proto" +) + +// iterStructs returns an iterator over the given struct type by scanning +// the SQL rows. `rows` is closed when the iterator exits. +func iterStructs[T any](rows *sqlx.Rows, err error) (iter.Seq[T], func() error) { + if err != nil { + return func(_ func(T) bool) {}, func() error { return err } + } + + var retErr error + return func(yield func(T) bool) { + defer rows.Close() + for rows.Next() { + v := new(T) + if err := rows.StructScan(v); err != nil { + retErr = err + break + } + if cleanuper, ok := any(v).(interface{ cleanup() }); ok { + cleanuper.cleanup() + } + if !yield(*v) { + return + } + } + if err := rows.Err(); err != nil && retErr == nil { + retErr = err + } + }, func() error { return retErr } +} + +// dbVector is a wrapper that allows protocol.Vector values to be serialized +// to and from the database. +type dbVector struct { //nolint:recvcheck + protocol.Vector +} + +func (v dbVector) Value() (driver.Value, error) { + return v.String(), nil +} + +func (v *dbVector) Scan(value any) error { + str, ok := value.(string) + if !ok { + return errors.New("not a string") + } + if str == "" { + v.Vector = protocol.Vector{} + return nil + } + vec, err := protocol.VectorFromString(str) + if err != nil { + return wrap(err) + } + v.Vector = vec + + return nil +} + +// indirectFI constructs a FileInfo from separate marshalled FileInfo and +// BlockList bytes. +type indirectFI struct { + Name string // not used, must be present as dest for Need iterator + FiProtobuf []byte + BlProtobuf []byte + Size int64 // not used + Modified int64 // not used +} + +func (i indirectFI) FileInfo() (protocol.FileInfo, error) { + var fi bep.FileInfo + if err := proto.Unmarshal(i.FiProtobuf, &fi); err != nil { + return protocol.FileInfo{}, wrap(err, "unmarshal fileinfo") + } + if len(i.BlProtobuf) > 0 { + var bl dbproto.BlockList + if err := proto.Unmarshal(i.BlProtobuf, &bl); err != nil { + return protocol.FileInfo{}, wrap(err, "unmarshal blocklist") + } + fi.Blocks = bl.Blocks + } + fi.Name = osutil.NativeFilename(fi.Name) + return protocol.FileInfoFromDB(&fi), nil +} + +func prefixEnd(s string) string { + if s == "" { + panic("bug: cannot represent end prefix for empty string") + } + bs := []byte(s) + for i := len(bs) - 1; i >= 0; i-- { + if bs[i] < 0xff { + bs[i]++ + break + } + } + return string(bs) +} diff --git a/internal/db/typed.go b/internal/db/typed.go new file mode 100644 index 000000000..75ed88921 --- /dev/null +++ b/internal/db/typed.go @@ -0,0 +1,140 @@ +// 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 https://mozilla.org/MPL/2.0/. + +package db + +import ( + "database/sql" + "encoding/binary" + "errors" + "time" +) + +// Typed is a simple key-value store using a specific namespace within a +// lower level KV. +type Typed struct { + db KV + prefix string +} + +func NewMiscDB(db KV) *Typed { + return NewTyped(db, "misc") +} + +// NewTyped returns a new typed key-value store that lives in the namespace +// specified by the prefix. +func NewTyped(db KV, prefix string) *Typed { + return &Typed{ + db: db, + prefix: prefix, + } +} + +// PutInt64 stores a new int64. Any existing value (even if of another type) +// is overwritten. +func (n *Typed) PutInt64(key string, val int64) error { + var valBs [8]byte + binary.BigEndian.PutUint64(valBs[:], uint64(val)) + return n.db.PutKV(n.prefixedKey(key), valBs[:]) +} + +// Int64 returns the stored value interpreted as an int64 and a boolean that +// is false if no value was stored at the key. +func (n *Typed) Int64(key string) (int64, bool, error) { + valBs, err := n.db.GetKV(n.prefixedKey(key)) + if err != nil { + return 0, false, filterNotFound(err) + } + val := binary.BigEndian.Uint64(valBs) + return int64(val), true, nil +} + +// PutTime stores a new time.Time. Any existing value (even if of another +// type) is overwritten. +func (n *Typed) PutTime(key string, val time.Time) error { + valBs, _ := val.MarshalBinary() // never returns an error + return n.db.PutKV(n.prefixedKey(key), valBs) +} + +// Time returns the stored value interpreted as a time.Time and a boolean +// that is false if no value was stored at the key. +func (n *Typed) Time(key string) (time.Time, bool, error) { + var t time.Time + valBs, err := n.db.GetKV(n.prefixedKey(key)) + if err != nil { + return t, false, filterNotFound(err) + } + err = t.UnmarshalBinary(valBs) + return t, err == nil, err +} + +// PutString stores a new string. Any existing value (even if of another type) +// is overwritten. +func (n *Typed) PutString(key, val string) error { + return n.db.PutKV(n.prefixedKey(key), []byte(val)) +} + +// String returns the stored value interpreted as a string and a boolean that +// is false if no value was stored at the key. +func (n *Typed) String(key string) (string, bool, error) { + valBs, err := n.db.GetKV(n.prefixedKey(key)) + if err != nil { + return "", false, filterNotFound(err) + } + return string(valBs), true, nil +} + +// PutBytes stores a new byte slice. Any existing value (even if of another type) +// is overwritten. +func (n *Typed) PutBytes(key string, val []byte) error { + return n.db.PutKV(n.prefixedKey(key), val) +} + +// Bytes returns the stored value as a raw byte slice and a boolean that +// is false if no value was stored at the key. +func (n *Typed) Bytes(key string) ([]byte, bool, error) { + valBs, err := n.db.GetKV(n.prefixedKey(key)) + if err != nil { + return nil, false, filterNotFound(err) + } + return valBs, true, nil +} + +// PutBool stores a new boolean. Any existing value (even if of another type) +// is overwritten. +func (n *Typed) PutBool(key string, val bool) error { + if val { + return n.db.PutKV(n.prefixedKey(key), []byte{0x0}) + } + return n.db.PutKV(n.prefixedKey(key), []byte{0x1}) +} + +// Bool returns the stored value as a boolean and a boolean that +// is false if no value was stored at the key. +func (n *Typed) Bool(key string) (bool, bool, error) { + valBs, err := n.db.GetKV(n.prefixedKey(key)) + if err != nil { + return false, false, filterNotFound(err) + } + return valBs[0] == 0x0, true, nil +} + +// Delete deletes the specified key. It is allowed to delete a nonexistent +// key. +func (n *Typed) Delete(key string) error { + return n.db.DeleteKV(n.prefixedKey(key)) +} + +func (n *Typed) prefixedKey(key string) string { + return n.prefix + "/" + key +} + +func filterNotFound(err error) error { + if errors.Is(err, sql.ErrNoRows) { + return nil + } + return err +} diff --git a/internal/db/typed_test.go b/internal/db/typed_test.go new file mode 100644 index 000000000..9f01b4335 --- /dev/null +++ b/internal/db/typed_test.go @@ -0,0 +1,115 @@ +// 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 https://mozilla.org/MPL/2.0/. + +package db_test + +import ( + "testing" + "time" + + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/db/sqlite" +) + +func TestNamespacedInt(t *testing.T) { + t.Parallel() + + ldb, err := sqlite.OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + ldb.Close() + }) + + n1 := db.NewTyped(ldb, "foo") + n2 := db.NewTyped(ldb, "bar") + + t.Run("Int", func(t *testing.T) { + t.Parallel() + + // Key is missing to start with + + if v, ok, err := n1.Int64("testint"); err != nil { + t.Error("Unexpected error:", err) + } else if v != 0 || ok { + t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok) + } + + if err := n1.PutInt64("testint", 42); err != nil { + t.Fatal(err) + } + + // It should now exist in n1 + + if v, ok, err := n1.Int64("testint"); err != nil { + t.Error("Unexpected error:", err) + } else if v != 42 || !ok { + t.Errorf("Incorrect return v %v != 42 || ok %v != true", v, ok) + } + + // ... but not in n2, which is in a different namespace + + if v, ok, err := n2.Int64("testint"); err != nil { + t.Error("Unexpected error:", err) + } else if v != 0 || ok { + t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok) + } + + if err := n1.Delete("testint"); err != nil { + t.Fatal(err) + } + + // It should no longer exist + + if v, ok, err := n1.Int64("testint"); err != nil { + t.Error("Unexpected error:", err) + } else if v != 0 || ok { + t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok) + } + }) + + t.Run("Time", func(t *testing.T) { + t.Parallel() + + if v, ok, err := n1.Time("testtime"); err != nil { + t.Error("Unexpected error:", err) + } else if !v.IsZero() || ok { + t.Errorf("Incorrect return v %v != %v || ok %v != false", v, time.Time{}, ok) + } + + now := time.Now() + if err := n1.PutTime("testtime", now); err != nil { + t.Fatal(err) + } + + if v, ok, err := n1.Time("testtime"); err != nil { + t.Error("Unexpected error:", err) + } else if !v.Equal(now) || !ok { + t.Errorf("Incorrect return v %v != %v || ok %v != true", v, now, ok) + } + }) + + t.Run("String", func(t *testing.T) { + t.Parallel() + + if v, ok, err := n1.String("teststring"); err != nil { + t.Error("Unexpected error:", err) + } else if v != "" || ok { + t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok) + } + + if err := n1.PutString("teststring", "yo"); err != nil { + t.Fatal(err) + } + + if v, ok, err := n1.String("teststring"); err != nil { + t.Error("Unexpected error:", err) + } else if v != "yo" || !ok { + t.Errorf("Incorrect return v %q != \"yo\" || ok %v != true", v, ok) + } + }) +} diff --git a/internal/itererr/itererr.go b/internal/itererr/itererr.go new file mode 100644 index 000000000..86278a159 --- /dev/null +++ b/internal/itererr/itererr.go @@ -0,0 +1,83 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package itererr + +import "iter" + +// Collect returns a slice of the items from the iterator, plus the error if +// any. +func Collect[T any](it iter.Seq[T], errFn func() error) ([]T, error) { + var s []T + for v := range it { + s = append(s, v) + } + return s, errFn() +} + +// Zip interleaves the iterator value with the error. The iteration ends +// after a non-nil error. +func Zip[T any](it iter.Seq[T], errFn func() error) iter.Seq2[T, error] { + return func(yield func(T, error) bool) { + for v := range it { + if !yield(v, nil) { + break + } + } + if err := errFn(); err != nil { + var zero T + yield(zero, err) + } + } +} + +// Map returns a new iterator by applying the map function, while respecting +// the error function. Additionally, the map function can return an error if +// its own. +func Map[A, B any](i iter.Seq[A], errFn func() error, mapFn func(A) (B, error)) (iter.Seq[B], func() error) { + var retErr error + return func(yield func(B) bool) { + for v := range i { + mapped, err := mapFn(v) + if err != nil { + retErr = err + return + } + if !yield(mapped) { + return + } + } + }, func() error { + if prevErr := errFn(); prevErr != nil { + return prevErr + } + return retErr + } +} + +// Map returns a new iterator by applying the map function, while respecting +// the error function. Additionally, the map function can return an error if +// its own. +func Map2[A, B, C any](i iter.Seq[A], errFn func() error, mapFn func(A) (B, C, error)) (iter.Seq2[B, C], func() error) { + var retErr error + return func(yield func(B, C) bool) { + for v := range i { + ma, mb, err := mapFn(v) + if err != nil { + retErr = err + return + } + if !yield(ma, mb) { + return + } + } + }, func() error { + if prevErr := errFn(); prevErr != nil { + return prevErr + } + return retErr + } +} diff --git a/internal/protoutil/protoutil.go b/internal/protoutil/protoutil.go index 93bd76911..c43057b44 100644 --- a/internal/protoutil/protoutil.go +++ b/internal/protoutil/protoutil.go @@ -1,3 +1,9 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + package protoutil import ( diff --git a/internal/timeutil/timeutil.go b/internal/timeutil/timeutil.go new file mode 100644 index 000000000..103de37f3 --- /dev/null +++ b/internal/timeutil/timeutil.go @@ -0,0 +1,27 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package timeutil + +import ( + "sync/atomic" + "time" +) + +var prevNanos atomic.Int64 + +// StrictlyMonotonicNanos returns the current time in Unix nanoseconds. +// Guaranteed to strictly increase for each call, regardless of the +// underlying OS timer resolution or clock jumps. +func StrictlyMonotonicNanos() int64 { + for { + old := prevNanos.Load() + now := max(time.Now().UnixNano(), old+1) + if prevNanos.CompareAndSwap(old, now) { + return now + } + } +} diff --git a/lib/api/api.go b/lib/api/api.go index 8d07546b9..9a8f7104b 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -40,10 +40,10 @@ import ( "golang.org/x/text/transform" "golang.org/x/text/unicode/norm" + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/connections" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/discover" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" @@ -91,7 +91,7 @@ type service struct { startupErr error listenerAddr net.Addr exitChan chan *svcutil.FatalErr - miscDB *db.NamespacedKV + miscDB *db.Typed shutdownTimeout time.Duration guiErrors logger.Recorder @@ -106,7 +106,7 @@ type Service interface { WaitForStart() error } -func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, evLogger events.Logger, discoverer discover.Manager, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, noUpgrade bool, miscDB *db.NamespacedKV) Service { +func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonName string, m model.Model, defaultSub, diskSub events.BufferedSubscription, evLogger events.Logger, discoverer discover.Manager, connectionsService connections.Service, urService *ur.Service, fss model.FolderSummaryService, errors, systemLog logger.Recorder, noUpgrade bool, miscDB *db.Typed) Service { return &service{ id: id, cfg: cfg, @@ -984,16 +984,11 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - mtimeMapping, mtimeErr := s.model.GetMtimeMapping(folder, file) sendJSON(w, map[string]interface{}{ "global": jsonFileInfo(gf), "local": jsonFileInfo(lf), "availability": av, - "mtime": map[string]interface{}{ - "err": mtimeErr, - "value": mtimeMapping, - }, }) } @@ -1002,28 +997,14 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) { folder := qs.Get("folder") file := qs.Get("file") - snap, err := s.model.DBSnapshot(folder) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - mtimeMapping, mtimeErr := s.model.GetMtimeMapping(folder, file) - - lf, _ := snap.Get(protocol.LocalDeviceID, file) - gf, _ := snap.GetGlobal(file) - av := snap.Availability(file) - vl := snap.DebugGlobalVersions(file) + lf, _, _ := s.model.CurrentFolderFile(folder, file) + gf, _, _ := s.model.CurrentGlobalFile(folder, file) + av, _ := s.model.Availability(folder, protocol.FileInfo{Name: file}, protocol.BlockInfo{}) sendJSON(w, map[string]interface{}{ - "global": jsonFileInfo(gf), - "local": jsonFileInfo(lf), - "availability": av, - "globalVersions": vl.String(), - "mtime": map[string]interface{}{ - "err": mtimeErr, - "value": mtimeMapping, - }, + "global": jsonFileInfo(gf), + "local": jsonFileInfo(lf), + "availability": av, }) } diff --git a/lib/api/api_auth_test.go b/lib/api/api_auth_test.go index a2d7146d9..ee51031f1 100644 --- a/lib/api/api_auth_test.go +++ b/lib/api/api_auth_test.go @@ -10,10 +10,9 @@ import ( "testing" "time" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/db/sqlite" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/events" ) var guiCfg config.GUIConfiguration @@ -131,8 +130,14 @@ func (c *mockClock) wind(t time.Duration) { func TestTokenManager(t *testing.T) { t.Parallel() - mdb, _ := db.NewLowlevel(backend.OpenMemory(), events.NoopLogger) - kdb := db.NewNamespacedKV(mdb, "test") + mdb, err := sqlite.OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + mdb.Close() + }) + kdb := db.NewMiscDB(mdb) clock := &mockClock{now: time.Now()} // Token manager keeps up to three tokens with a validity time of 24 hours. diff --git a/lib/api/api_csrf.go b/lib/api/api_csrf.go index e8f03418d..d7392a345 100644 --- a/lib/api/api_csrf.go +++ b/lib/api/api_csrf.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/internal/db" ) const ( @@ -34,7 +34,7 @@ type apiKeyValidator interface { // Check for CSRF token on /rest/ URLs. If a correct one is not given, reject // the request with 403. For / and /index.html, set a new CSRF cookie if none // is currently set. -func newCsrfManager(unique string, prefix string, apiKeyValidator apiKeyValidator, next http.Handler, miscDB *db.NamespacedKV) *csrfManager { +func newCsrfManager(unique string, prefix string, apiKeyValidator apiKeyValidator, next http.Handler, miscDB *db.Typed) *csrfManager { m := &csrfManager{ unique: unique, prefix: prefix, diff --git a/lib/api/api_test.go b/lib/api/api_test.go index 96ea1d1ce..bad4b456c 100644 --- a/lib/api/api_test.go +++ b/lib/api/api_test.go @@ -27,12 +27,12 @@ import ( "github.com/d4l3k/messagediff" "github.com/thejerf/suture/v4" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/db/sqlite" "github.com/syncthing/syncthing/lib/assets" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" connmocks "github.com/syncthing/syncthing/lib/connections/mocks" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/db/backend" discovermocks "github.com/syncthing/syncthing/lib/discover/mocks" "github.com/syncthing/syncthing/lib/events" eventmocks "github.com/syncthing/syncthing/lib/events/mocks" @@ -84,8 +84,14 @@ func TestStopAfterBrokenConfig(t *testing.T) { } w := config.Wrap("/dev/null", cfg, protocol.LocalDeviceID, events.NoopLogger) - mdb, _ := db.NewLowlevel(backend.OpenMemory(), events.NoopLogger) - kdb := db.NewMiscDataNamespace(mdb) + mdb, err := sqlite.OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + mdb.Close() + }) + kdb := db.NewMiscDB(mdb) srv := New(protocol.LocalDeviceID, w, "", "syncthing", nil, nil, nil, events.NoopLogger, nil, nil, nil, nil, nil, nil, false, kdb).(*service) srv.started = make(chan string) @@ -217,11 +223,7 @@ type httpTestCase struct { func TestAPIServiceRequests(t *testing.T) { t.Parallel() - baseURL, cancel, err := startHTTP(apiCfg) - if err != nil { - t.Fatal(err) - } - t.Cleanup(cancel) + baseURL := startHTTP(t, apiCfg) cases := []httpTestCase{ // /rest/db @@ -598,11 +600,7 @@ func TestHTTPLogin(t *testing.T) { APIKey: testAPIKey, SendBasicAuthPrompt: sendBasicAuthPrompt, }) - baseURL, cancel, err := startHTTP(cfg) - if err != nil { - t.Fatal(err) - } - t.Cleanup(cancel) + baseURL := startHTTP(t, cfg) url := baseURL + path t.Run(fmt.Sprintf("%d path", expectedOkStatus), func(t *testing.T) { @@ -795,13 +793,9 @@ func TestHTTPLogin(t *testing.T) { w := initConfig(initialPassword, t) { - baseURL, cancel, err := startHTTPWithShutdownTimeout(w, shutdownTimeout) + baseURL := startHTTPWithShutdownTimeout(t, w, shutdownTimeout) cfgPath := baseURL + "/rest/config" path := baseURL + "/meta.js" - t.Cleanup(cancel) - if err != nil { - t.Fatal(err) - } resp := httpGetBasicAuth(path, "user", initialPassword) if resp.StatusCode != http.StatusOK { @@ -813,12 +807,8 @@ func TestHTTPLogin(t *testing.T) { httpRequest(http.MethodPut, cfgPath, cfg, "", "", testAPIKey, "", "", "", nil, t) } { - baseURL, cancel, err := startHTTP(w) + baseURL := startHTTP(t, w) path := baseURL + "/meta.js" - t.Cleanup(cancel) - if err != nil { - t.Fatal(err) - } resp := httpGetBasicAuth(path, "user", initialPassword) if resp.StatusCode != http.StatusForbidden { @@ -837,13 +827,9 @@ func TestHTTPLogin(t *testing.T) { w := initConfig(initialPassword, t) { - baseURL, cancel, err := startHTTPWithShutdownTimeout(w, shutdownTimeout) + baseURL := startHTTPWithShutdownTimeout(t, w, shutdownTimeout) cfgPath := baseURL + "/rest/config/gui" path := baseURL + "/meta.js" - t.Cleanup(cancel) - if err != nil { - t.Fatal(err) - } resp := httpGetBasicAuth(path, "user", initialPassword) if resp.StatusCode != http.StatusOK { @@ -855,12 +841,8 @@ func TestHTTPLogin(t *testing.T) { httpRequest(http.MethodPut, cfgPath, cfg.GUI, "", "", testAPIKey, "", "", "", nil, t) } { - baseURL, cancel, err := startHTTP(w) + baseURL := startHTTP(t, w) path := baseURL + "/meta.js" - t.Cleanup(cancel) - if err != nil { - t.Fatal(err) - } resp := httpGetBasicAuth(path, "user", initialPassword) if resp.StatusCode != http.StatusForbidden { @@ -885,11 +867,7 @@ func TestHtmlFormLogin(t *testing.T) { Password: "$2a$10$IdIZTxTg/dCNuNEGlmLynOjqg4B1FvDKuIV5e0BB3pnWVHNb8.GSq", // bcrypt of "räksmörgås" in UTF-8 SendBasicAuthPrompt: false, }) - baseURL, cancel, err := startHTTP(cfg) - if err != nil { - t.Fatal(err) - } - t.Cleanup(cancel) + baseURL := startHTTP(t, cfg) loginUrl := baseURL + "/rest/noauth/auth/password" resourceUrl := baseURL + "/meta.js" @@ -1030,11 +1008,7 @@ func TestApiCache(t *testing.T) { RawAddress: "127.0.0.1:0", APIKey: testAPIKey, }) - baseURL, cancel, err := startHTTP(cfg) - if err != nil { - t.Fatal(err) - } - t.Cleanup(cancel) + baseURL := startHTTP(t, cfg) httpGet := func(url string, bearer string) *http.Response { return httpGet(url, "", "", "", bearer, nil, t) @@ -1059,11 +1033,11 @@ func TestApiCache(t *testing.T) { }) } -func startHTTP(cfg config.Wrapper) (string, context.CancelFunc, error) { - return startHTTPWithShutdownTimeout(cfg, 0) +func startHTTP(t *testing.T, cfg config.Wrapper) string { + return startHTTPWithShutdownTimeout(t, cfg, 0) } -func startHTTPWithShutdownTimeout(cfg config.Wrapper, shutdownTimeout time.Duration) (string, context.CancelFunc, error) { +func startHTTPWithShutdownTimeout(t *testing.T, cfg config.Wrapper, shutdownTimeout time.Duration) string { m := new(modelmocks.Model) assetDir := "../../gui" eventSub := new(eventmocks.BufferedSubscription) @@ -1086,12 +1060,18 @@ func startHTTPWithShutdownTimeout(cfg config.Wrapper, shutdownTimeout time.Durat // Instantiate the API service urService := ur.New(cfg, m, connections, false) - mdb, _ := db.NewLowlevel(backend.OpenMemory(), events.NoopLogger) - kdb := db.NewMiscDataNamespace(mdb) + mdb, err := sqlite.OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + mdb.Close() + }) + kdb := db.NewMiscDB(mdb) svc := New(protocol.LocalDeviceID, cfg, assetDir, "syncthing", m, eventSub, diskEventSub, events.NoopLogger, discoverer, connections, urService, mockedSummary, errorLog, systemLog, false, kdb).(*service) svc.started = addrChan - if shutdownTimeout > 0*time.Millisecond { + if shutdownTimeout > 0 { svc.shutdownTimeout = shutdownTimeout } @@ -1101,14 +1081,14 @@ func startHTTPWithShutdownTimeout(cfg config.Wrapper, shutdownTimeout time.Durat }) supervisor.Add(svc) ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) supervisor.ServeBackground(ctx) // Make sure the API service is listening, and get the URL to use. addr := <-addrChan tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { - cancel() - return "", cancel, fmt.Errorf("weird address from API service: %w", err) + t.Fatal(fmt.Errorf("weird address from API service: %w", err)) } host, _, _ := net.SplitHostPort(cfg.GUI().RawAddress) @@ -1117,17 +1097,13 @@ func startHTTPWithShutdownTimeout(cfg config.Wrapper, shutdownTimeout time.Durat } baseURL := fmt.Sprintf("http://%s", net.JoinHostPort(host, strconv.Itoa(tcpAddr.Port))) - return baseURL, cancel, nil + return baseURL } func TestCSRFRequired(t *testing.T) { t.Parallel() - baseURL, cancel, err := startHTTP(apiCfg) - if err != nil { - t.Fatal("Unexpected error from getting base URL:", err) - } - t.Cleanup(cancel) + baseURL := startHTTP(t, apiCfg) cli := &http.Client{ Timeout: time.Minute, @@ -1245,11 +1221,7 @@ func TestCSRFRequired(t *testing.T) { func TestRandomString(t *testing.T) { t.Parallel() - baseURL, cancel, err := startHTTP(apiCfg) - if err != nil { - t.Fatal(err) - } - defer cancel() + baseURL := startHTTP(t, apiCfg) cli := &http.Client{ Timeout: time.Second, } @@ -1304,7 +1276,7 @@ func TestConfigPostOK(t *testing.T) { ] }`)) - resp, err := testConfigPost(cfg) + resp, err := testConfigPost(t, cfg) if err != nil { t.Fatal(err) } @@ -1325,7 +1297,7 @@ func TestConfigPostDupFolder(t *testing.T) { ] }`)) - resp, err := testConfigPost(cfg) + resp, err := testConfigPost(t, cfg) if err != nil { t.Fatal(err) } @@ -1334,12 +1306,10 @@ func TestConfigPostDupFolder(t *testing.T) { } } -func testConfigPost(data io.Reader) (*http.Response, error) { - baseURL, cancel, err := startHTTP(apiCfg) - if err != nil { - return nil, err - } - defer cancel() +func testConfigPost(t *testing.T, data io.Reader) (*http.Response, error) { + t.Helper() + + baseURL := startHTTP(t, apiCfg) cli := &http.Client{ Timeout: time.Second, } @@ -1356,11 +1326,7 @@ func TestHostCheck(t *testing.T) { cfg := newMockedConfig() cfg.GUIReturns(config.GUIConfiguration{RawAddress: "127.0.0.1:0"}) - baseURL, cancel, err := startHTTP(cfg) - if err != nil { - t.Fatal(err) - } - defer cancel() + baseURL := startHTTP(t, cfg) // A normal HTTP get to the localhost-bound service should succeed @@ -1419,11 +1385,7 @@ func TestHostCheck(t *testing.T) { RawAddress: "127.0.0.1:0", InsecureSkipHostCheck: true, }) - baseURL, cancel, err = startHTTP(cfg) - if err != nil { - t.Fatal(err) - } - defer cancel() + baseURL = startHTTP(t, cfg) // A request with a suspicious Host header should be allowed @@ -1445,11 +1407,7 @@ func TestHostCheck(t *testing.T) { cfg.GUIReturns(config.GUIConfiguration{ RawAddress: "0.0.0.0:0", }) - baseURL, cancel, err = startHTTP(cfg) - if err != nil { - t.Fatal(err) - } - defer cancel() + baseURL = startHTTP(t, cfg) // A request with a suspicious Host header should be allowed @@ -1476,11 +1434,7 @@ func TestHostCheck(t *testing.T) { cfg.GUIReturns(config.GUIConfiguration{ RawAddress: "[::1]:0", }) - baseURL, cancel, err = startHTTP(cfg) - if err != nil { - t.Fatal(err) - } - defer cancel() + baseURL = startHTTP(t, cfg) // A normal HTTP get to the localhost-bound service should succeed @@ -1568,11 +1522,7 @@ func TestAddressIsLocalhost(t *testing.T) { func TestAccessControlAllowOriginHeader(t *testing.T) { t.Parallel() - baseURL, cancel, err := startHTTP(apiCfg) - if err != nil { - t.Fatal(err) - } - defer cancel() + baseURL := startHTTP(t, apiCfg) cli := &http.Client{ Timeout: time.Second, } @@ -1596,11 +1546,7 @@ func TestAccessControlAllowOriginHeader(t *testing.T) { func TestOptionsRequest(t *testing.T) { t.Parallel() - baseURL, cancel, err := startHTTP(apiCfg) - if err != nil { - t.Fatal(err) - } - defer cancel() + baseURL := startHTTP(t, apiCfg) cli := &http.Client{ Timeout: time.Second, } @@ -1632,8 +1578,14 @@ func TestEventMasks(t *testing.T) { cfg := newMockedConfig() defSub := new(eventmocks.BufferedSubscription) diskSub := new(eventmocks.BufferedSubscription) - mdb, _ := db.NewLowlevel(backend.OpenMemory(), events.NoopLogger) - kdb := db.NewMiscDataNamespace(mdb) + mdb, err := sqlite.OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + mdb.Close() + }) + kdb := db.NewMiscDB(mdb) svc := New(protocol.LocalDeviceID, cfg, "", "syncthing", nil, defSub, diskSub, events.NoopLogger, nil, nil, nil, nil, nil, nil, false, kdb).(*service) if mask := svc.getEventMask(""); mask != DefaultEventMask { @@ -1780,11 +1732,7 @@ func TestConfigChanges(t *testing.T) { cfgCtx, cfgCancel := context.WithCancel(context.Background()) go w.Serve(cfgCtx) defer cfgCancel() - baseURL, cancel, err := startHTTP(w) - if err != nil { - t.Fatal("Unexpected error from getting base URL:", err) - } - defer cancel() + baseURL := startHTTP(t, w) cli := &http.Client{ Timeout: time.Minute, diff --git a/lib/api/tokenmanager.go b/lib/api/tokenmanager.go index 38cd24415..5bd674686 100644 --- a/lib/api/tokenmanager.go +++ b/lib/api/tokenmanager.go @@ -14,9 +14,9 @@ import ( "google.golang.org/protobuf/proto" + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/internal/gen/apiproto" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/sync" @@ -24,7 +24,7 @@ import ( type tokenManager struct { key string - miscDB *db.NamespacedKV + miscDB *db.Typed lifetime time.Duration maxItems int @@ -35,7 +35,7 @@ type tokenManager struct { saveTimer *time.Timer } -func newTokenManager(key string, miscDB *db.NamespacedKV, lifetime time.Duration, maxItems int) *tokenManager { +func newTokenManager(key string, miscDB *db.Typed, lifetime time.Duration, maxItems int) *tokenManager { var tokens apiproto.TokenSet if bs, ok, _ := miscDB.Bytes(key); ok { _ = proto.Unmarshal(bs, &tokens) // best effort @@ -152,7 +152,7 @@ type tokenCookieManager struct { tokens *tokenManager } -func newTokenCookieManager(shortID string, guiCfg config.GUIConfiguration, evLogger events.Logger, miscDB *db.NamespacedKV) *tokenCookieManager { +func newTokenCookieManager(shortID string, guiCfg config.GUIConfiguration, evLogger events.Logger, miscDB *db.Typed) *tokenCookieManager { return &tokenCookieManager{ cookieName: "sessionid-" + shortID, shortID: shortID, diff --git a/lib/build/build.go b/lib/build/build.go index 098dd7c7c..b13cb93b7 100644 --- a/lib/build/build.go +++ b/lib/build/build.go @@ -18,7 +18,7 @@ import ( "time" ) -const Codename = "Gold Grasshopper" +const Codename = "Hafnium Hornet" var ( // Injected by build script @@ -28,6 +28,9 @@ var ( Stamp = "0" Tags = "" + // Added to by other packages + extraTags []string + // Set by init() Date time.Time IsRelease bool @@ -43,6 +46,11 @@ var ( "STNORESTART", "STNOUPGRADE", } + replaceTags = map[string]string{ + "sqlite_omit_load_extension": "", + "osusergo": "", + "netgo": "", + } ) const versionExtraAllowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-. " @@ -108,8 +116,23 @@ func TagsList() []string { if Extra != "" { tags = append(tags, Extra) } + tags = append(tags, extraTags...) + + // Replace any tag values we want to have more user friendly versions, + // or be removed + for i, tag := range tags { + if repl, ok := replaceTags[tag]; ok { + tags[i] = repl + } + } sort.Strings(tags) + + // Remove any empty tags, which will be at the front of the list now + for len(tags) > 0 && tags[0] == "" { + tags = tags[1:] + } + return tags } @@ -124,3 +147,8 @@ func filterString(s, allowedChars string) string { } return res.String() } + +func AddTag(tag string) { + extraTags = append(extraTags, tag) + LongVersion = LongVersionFor("syncthing") +} diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 6359f42ab..63a817d65 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -484,7 +484,7 @@ func TestIssue1262(t *testing.T) { t.Fatal(err) } - actual := cfg.Folders()["test"].Filesystem(nil).URI() + actual := cfg.Folders()["test"].Filesystem().URI() expected := `e:\` if actual != expected { @@ -521,7 +521,7 @@ func TestFolderPath(t *testing.T) { Path: "~/tmp", } - realPath := folder.Filesystem(nil).URI() + realPath := folder.Filesystem().URI() if !filepath.IsAbs(realPath) { t.Error(realPath, "should be absolute") } diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index 76c3b3e39..edcb6a551 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -20,7 +20,6 @@ import ( "github.com/shirou/gopsutil/v4/disk" "github.com/syncthing/syncthing/lib/build" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/protocol" ) @@ -119,26 +118,24 @@ func (f FolderConfiguration) Copy() FolderConfiguration { // Filesystem creates a filesystem for the path and options of this folder. // The fset parameter may be nil, in which case no mtime handling on top of // the filesystem is provided. -func (f FolderConfiguration) Filesystem(fset *db.FileSet) fs.Filesystem { +func (f FolderConfiguration) Filesystem(extraOpts ...fs.Option) fs.Filesystem { // This is intentionally not a pointer method, because things like // cfg.Folders["default"].Filesystem(nil) should be valid. - opts := make([]fs.Option, 0, 3) + var opts []fs.Option if f.FilesystemType == FilesystemTypeBasic && f.JunctionsAsDirs { opts = append(opts, new(fs.OptionJunctionsAsDirs)) } if !f.CaseSensitiveFS { opts = append(opts, new(fs.OptionDetectCaseConflicts)) } - if fset != nil { - opts = append(opts, fset.MtimeOption()) - } + opts = append(opts, extraOpts...) return fs.NewFilesystem(f.FilesystemType.ToFS(), f.Path, opts...) } func (f FolderConfiguration) ModTimeWindow() time.Duration { dur := time.Duration(f.RawModTimeWindowS) * time.Second if f.RawModTimeWindowS < 1 && build.IsAndroid { - if usage, err := disk.Usage(f.Filesystem(nil).URI()); err != nil { + if usage, err := disk.Usage(f.Filesystem().URI()); err != nil { dur = 2 * time.Second l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: err == "%v"`, f.Path, err) } else if strings.HasPrefix(strings.ToLower(usage.Fstype), "ext2") || strings.HasPrefix(strings.ToLower(usage.Fstype), "ext3") || strings.HasPrefix(strings.ToLower(usage.Fstype), "ext4") { @@ -162,7 +159,7 @@ func (f *FolderConfiguration) CreateMarker() error { return nil } - ffs := f.Filesystem(nil) + ffs := f.Filesystem() // Create the marker as a directory err := ffs.Mkdir(DefaultMarkerName, 0o755) @@ -189,7 +186,7 @@ func (f *FolderConfiguration) CreateMarker() error { } func (f *FolderConfiguration) RemoveMarker() error { - ffs := f.Filesystem(nil) + ffs := f.Filesystem() _ = ffs.Remove(filepath.Join(DefaultMarkerName, f.markerFilename())) return ffs.Remove(DefaultMarkerName) } @@ -209,7 +206,7 @@ func (f *FolderConfiguration) markerContents() []byte { // CheckPath returns nil if the folder root exists and contains the marker file func (f *FolderConfiguration) CheckPath() error { - return f.checkFilesystemPath(f.Filesystem(nil), ".") + return f.checkFilesystemPath(f.Filesystem(), ".") } func (f *FolderConfiguration) checkFilesystemPath(ffs fs.Filesystem, path string) error { @@ -252,7 +249,7 @@ func (f *FolderConfiguration) CreateRoot() (err error) { permBits = 0o700 } - filesystem := f.Filesystem(nil) + filesystem := f.Filesystem() if _, err = filesystem.Stat("."); fs.IsNotExist(err) { err = filesystem.MkdirAll(".", permBits) @@ -363,7 +360,7 @@ func (f *FolderConfiguration) CheckAvailableSpace(req uint64) error { if val <= 0 { return nil } - fs := f.Filesystem(nil) + fs := f.Filesystem() usage, err := fs.Usage(".") if err != nil { return nil //nolint: nilerr diff --git a/lib/config/migrations.go b/lib/config/migrations.go index 4a4ecdf37..014d31404 100644 --- a/lib/config/migrations.go +++ b/lib/config/migrations.go @@ -208,7 +208,7 @@ func migrateToConfigV23(cfg *Configuration) { // marker name in later versions. for i := range cfg.Folders { - fs := cfg.Folders[i].Filesystem(nil) + fs := cfg.Folders[i].Filesystem() // Invalid config posted, or tests. if fs == nil { continue @@ -244,18 +244,18 @@ func migrateToConfigV21(cfg *Configuration) { switch folder.Versioning.Type { case "simple", "trashcan": // Clean out symlinks in the known place - cleanSymlinks(folder.Filesystem(nil), ".stversions") + cleanSymlinks(folder.Filesystem(), ".stversions") case "staggered": versionDir := folder.Versioning.Params["versionsPath"] if versionDir == "" { // default place - cleanSymlinks(folder.Filesystem(nil), ".stversions") + cleanSymlinks(folder.Filesystem(), ".stversions") } else if filepath.IsAbs(versionDir) { // absolute cleanSymlinks(fs.NewFilesystem(fs.FilesystemTypeBasic, versionDir), ".") } else { // relative to folder - cleanSymlinks(folder.Filesystem(nil), versionDir) + cleanSymlinks(folder.Filesystem(), versionDir) } } } diff --git a/lib/config/optionsconfiguration.go b/lib/config/optionsconfiguration.go index eecf34dda..16363b00d 100644 --- a/lib/config/optionsconfiguration.go +++ b/lib/config/optionsconfiguration.go @@ -61,7 +61,6 @@ type OptionsConfiguration struct { StunKeepaliveStartS int `json:"stunKeepaliveStartS" xml:"stunKeepaliveStartS" default:"180"` StunKeepaliveMinS int `json:"stunKeepaliveMinS" xml:"stunKeepaliveMinS" default:"20"` RawStunServers []string `json:"stunServers" xml:"stunServer" default:"default"` - DatabaseTuning Tuning `json:"databaseTuning" xml:"databaseTuning" restart:"true"` RawMaxCIRequestKiB int `json:"maxConcurrentIncomingRequestKiB" xml:"maxConcurrentIncomingRequestKiB"` AnnounceLANAddresses bool `json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"` SendFullIndexOnUpgrade bool `json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"` diff --git a/lib/config/tuning.go b/lib/config/tuning.go deleted file mode 100644 index c2fa9df43..000000000 --- a/lib/config/tuning.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2019 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 https://mozilla.org/MPL/2.0/. - -package config - -type Tuning int32 - -const ( - TuningAuto Tuning = 0 - TuningSmall Tuning = 1 - TuningLarge Tuning = 2 -) - -func (t Tuning) String() string { - switch t { - case TuningAuto: - return "auto" - case TuningSmall: - return "small" - case TuningLarge: - return "large" - default: - return "unknown" - } -} - -func (t Tuning) MarshalText() ([]byte, error) { - return []byte(t.String()), nil -} - -func (t *Tuning) UnmarshalText(bs []byte) error { - switch string(bs) { - case "auto": - *t = TuningAuto - case "small": - *t = TuningSmall - case "large": - *t = TuningLarge - default: - *t = TuningAuto - } - return nil -} diff --git a/lib/config/tuning_test.go b/lib/config/tuning_test.go deleted file mode 100644 index d913492ff..000000000 --- a/lib/config/tuning_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2019 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 https://mozilla.org/MPL/2.0/. - -package config_test - -import ( - "testing" - - "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db/backend" -) - -func TestTuningMatches(t *testing.T) { - if int(config.TuningAuto) != int(backend.TuningAuto) { - t.Error("mismatch for TuningAuto") - } - if int(config.TuningSmall) != int(backend.TuningSmall) { - t.Error("mismatch for TuningSmall") - } - if int(config.TuningLarge) != int(backend.TuningLarge) { - t.Error("mismatch for TuningLarge") - } -} diff --git a/lib/db/.gitignore b/lib/db/.gitignore deleted file mode 100644 index d5316784f..000000000 --- a/lib/db/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!*.zip -testdata/*.db diff --git a/lib/db/backend/backend_test.go b/lib/db/backend/backend_test.go deleted file mode 100644 index d755e0cb6..000000000 --- a/lib/db/backend/backend_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2019 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 https://mozilla.org/MPL/2.0/. - -package backend - -import "testing" - -// testBackendBehavior is the generic test suite that must be fulfilled by -// every backend implementation. It should be called by each implementation -// as (part of) their test suite. -func testBackendBehavior(t *testing.T, open func() Backend) { - t.Run("WriteIsolation", func(t *testing.T) { testWriteIsolation(t, open) }) - t.Run("DeleteNonexisten", func(t *testing.T) { testDeleteNonexistent(t, open) }) - t.Run("IteratorClosedDB", func(t *testing.T) { testIteratorClosedDB(t, open) }) -} - -func testWriteIsolation(t *testing.T, open func() Backend) { - // Values written during a transaction should not be read back, our - // updateGlobal depends on this. - - db := open() - defer db.Close() - - // Sanity check - _ = db.Put([]byte("a"), []byte("a")) - v, _ := db.Get([]byte("a")) - if string(v) != "a" { - t.Fatal("read back should work") - } - - // Now in a transaction we should still see the old value - tx, _ := db.NewWriteTransaction() - defer tx.Release() - _ = tx.Put([]byte("a"), []byte("b")) - v, _ = tx.Get([]byte("a")) - if string(v) != "a" { - t.Fatal("read in transaction should read the old value") - } -} - -func testDeleteNonexistent(t *testing.T, open func() Backend) { - // Deleting a non-existent key is not an error - - db := open() - defer db.Close() - - err := db.Delete([]byte("a")) - if err != nil { - t.Error(err) - } -} - -// Either creating the iterator or the .Error() method of the returned iterator -// should return an error and IsClosed(err) == true. -func testIteratorClosedDB(t *testing.T, open func() Backend) { - db := open() - - _ = db.Put([]byte("a"), []byte("a")) - - db.Close() - - it, err := db.NewPrefixIterator(nil) - if err != nil { - if !IsClosed(err) { - t.Error("NewPrefixIterator: IsClosed(err) == false:", err) - } - return - } - it.Next() - if err := it.Error(); !IsClosed(err) { - t.Error("Next: IsClosed(err) == false:", err) - } -} diff --git a/lib/db/backend/debug.go b/lib/db/backend/debug.go deleted file mode 100644 index 9a86fcefa..000000000 --- a/lib/db/backend/debug.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2019 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 https://mozilla.org/MPL/2.0/. - -package backend - -import ( - "github.com/syncthing/syncthing/lib/logger" -) - -var l = logger.DefaultLogger.NewFacility("backend", "The database backend") diff --git a/lib/db/backend/leveldb_backend.go b/lib/db/backend/leveldb_backend.go deleted file mode 100644 index 577ff4b82..000000000 --- a/lib/db/backend/leveldb_backend.go +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package backend - -import ( - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/util" -) - -const ( - // Never flush transactions smaller than this, even on Checkpoint(). - // This just needs to be just large enough to avoid flushing - // transactions when they are super tiny, thus creating millions of tiny - // transactions unnecessarily. - dbFlushBatchMin = 64 << KiB - // Once a transaction reaches this size, flush it unconditionally. This - // should be large enough to avoid forcing a flush between Checkpoint() - // calls in loops where we do those, so in principle just large enough - // to hold a FileInfo plus corresponding version list and metadata - // updates or two. - dbFlushBatchMax = 1 << MiB -) - -// leveldbBackend implements Backend on top of a leveldb -type leveldbBackend struct { - ldb *leveldb.DB - closeWG *closeWaitGroup - location string -} - -func newLeveldbBackend(ldb *leveldb.DB, location string) *leveldbBackend { - return &leveldbBackend{ - ldb: ldb, - closeWG: &closeWaitGroup{}, - location: location, - } -} - -func (b *leveldbBackend) NewReadTransaction() (ReadTransaction, error) { - return b.newSnapshot() -} - -func (b *leveldbBackend) newSnapshot() (leveldbSnapshot, error) { - rel, err := newReleaser(b.closeWG) - if err != nil { - return leveldbSnapshot{}, err - } - snap, err := b.ldb.GetSnapshot() - if err != nil { - rel.Release() - return leveldbSnapshot{}, wrapLeveldbErr(err) - } - return leveldbSnapshot{ - snap: snap, - rel: rel, - }, nil -} - -func (b *leveldbBackend) NewWriteTransaction(hooks ...CommitHook) (WriteTransaction, error) { - rel, err := newReleaser(b.closeWG) - if err != nil { - return nil, err - } - snap, err := b.newSnapshot() - if err != nil { - rel.Release() - return nil, err // already wrapped - } - return &leveldbTransaction{ - leveldbSnapshot: snap, - ldb: b.ldb, - batch: new(leveldb.Batch), - rel: rel, - commitHooks: hooks, - inFlush: false, - }, nil -} - -func (b *leveldbBackend) Close() error { - b.closeWG.CloseWait() - return wrapLeveldbErr(b.ldb.Close()) -} - -func (b *leveldbBackend) Get(key []byte) ([]byte, error) { - val, err := b.ldb.Get(key, nil) - return val, wrapLeveldbErr(err) -} - -func (b *leveldbBackend) NewPrefixIterator(prefix []byte) (Iterator, error) { - return &leveldbIterator{b.ldb.NewIterator(util.BytesPrefix(prefix), nil)}, nil -} - -func (b *leveldbBackend) NewRangeIterator(first, last []byte) (Iterator, error) { - return &leveldbIterator{b.ldb.NewIterator(&util.Range{Start: first, Limit: last}, nil)}, nil -} - -func (b *leveldbBackend) Put(key, val []byte) error { - return wrapLeveldbErr(b.ldb.Put(key, val, nil)) -} - -func (b *leveldbBackend) Delete(key []byte) error { - return wrapLeveldbErr(b.ldb.Delete(key, nil)) -} - -func (b *leveldbBackend) Compact() error { - // Race is detected during testing when db is closed while compaction - // is ongoing. - err := b.closeWG.Add(1) - if err != nil { - return err - } - defer b.closeWG.Done() - return wrapLeveldbErr(b.ldb.CompactRange(util.Range{})) -} - -func (b *leveldbBackend) Location() string { - return b.location -} - -// leveldbSnapshot implements backend.ReadTransaction -type leveldbSnapshot struct { - snap *leveldb.Snapshot - rel *releaser -} - -func (l leveldbSnapshot) Get(key []byte) ([]byte, error) { - val, err := l.snap.Get(key, nil) - return val, wrapLeveldbErr(err) -} - -func (l leveldbSnapshot) NewPrefixIterator(prefix []byte) (Iterator, error) { - return l.snap.NewIterator(util.BytesPrefix(prefix), nil), nil -} - -func (l leveldbSnapshot) NewRangeIterator(first, last []byte) (Iterator, error) { - return l.snap.NewIterator(&util.Range{Start: first, Limit: last}, nil), nil -} - -func (l leveldbSnapshot) Release() { - l.snap.Release() - l.rel.Release() -} - -// leveldbTransaction implements backend.WriteTransaction using a batch (not -// an actual leveldb transaction) -type leveldbTransaction struct { - leveldbSnapshot - ldb *leveldb.DB - batch *leveldb.Batch - rel *releaser - commitHooks []CommitHook - inFlush bool -} - -func (t *leveldbTransaction) Delete(key []byte) error { - t.batch.Delete(key) - return t.checkFlush(dbFlushBatchMax) -} - -func (t *leveldbTransaction) Put(key, val []byte) error { - t.batch.Put(key, val) - return t.checkFlush(dbFlushBatchMax) -} - -func (t *leveldbTransaction) Checkpoint() error { - return t.checkFlush(dbFlushBatchMin) -} - -func (t *leveldbTransaction) Commit() error { - err := wrapLeveldbErr(t.flush()) - t.leveldbSnapshot.Release() - t.rel.Release() - return err -} - -func (t *leveldbTransaction) Release() { - t.leveldbSnapshot.Release() - t.rel.Release() -} - -// checkFlush flushes and resets the batch if its size exceeds the given size. -func (t *leveldbTransaction) checkFlush(size int) error { - // Hooks might put values in the database, which triggers a checkFlush which might trigger a flush, - // which might trigger the hooks. - // Don't recurse... - if t.inFlush || len(t.batch.Dump()) < size { - return nil - } - return t.flush() -} - -func (t *leveldbTransaction) flush() error { - t.inFlush = true - defer func() { t.inFlush = false }() - - for _, hook := range t.commitHooks { - if err := hook(t); err != nil { - return err - } - } - if t.batch.Len() == 0 { - return nil - } - if err := t.ldb.Write(t.batch, nil); err != nil { - return wrapLeveldbErr(err) - } - t.batch.Reset() - return nil -} - -type leveldbIterator struct { - iterator.Iterator -} - -func (it *leveldbIterator) Error() error { - return wrapLeveldbErr(it.Iterator.Error()) -} - -// wrapLeveldbErr wraps errors so that the backend package can recognize them -func wrapLeveldbErr(err error) error { - switch err { - case leveldb.ErrClosed: - return errClosed - case leveldb.ErrNotFound: - return errNotFound - } - return err -} diff --git a/lib/db/backend/leveldb_open.go b/lib/db/backend/leveldb_open.go deleted file mode 100644 index 3908d7569..000000000 --- a/lib/db/backend/leveldb_open.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package backend - -import ( - "fmt" - "os" - "strconv" - "strings" - - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/opt" - "github.com/syndtr/goleveldb/leveldb/storage" - "github.com/syndtr/goleveldb/leveldb/util" -) - -const ( - dbMaxOpenFiles = 100 - - // A large database is > 200 MiB. It's a mostly arbitrary value, but - // it's also the case that each file is 2 MiB by default and when we - // have dbMaxOpenFiles of them we will need to start thrashing fd:s. - // Switching to large database settings causes larger files to be used - // when compacting, reducing the number. - dbLargeThreshold = dbMaxOpenFiles * (2 << MiB) - - KiB = 10 - MiB = 20 -) - -// OpenLevelDB attempts to open the database at the given location, and runs -// recovery on it if opening fails. Worst case, if recovery is not possible, -// the database is erased and created from scratch. -func OpenLevelDB(location string, tuning Tuning) (Backend, error) { - opts := optsFor(location, tuning) - ldb, err := open(location, opts) - if err != nil { - return nil, err - } - return newLeveldbBackend(ldb, location), nil -} - -// OpenLevelDBAuto is OpenLevelDB with TuningAuto tuning. -func OpenLevelDBAuto(location string) (Backend, error) { - return OpenLevelDB(location, TuningAuto) -} - -// OpenLevelDBRO attempts to open the database at the given location, read -// only. -func OpenLevelDBRO(location string) (Backend, error) { - opts := &opt.Options{ - OpenFilesCacheCapacity: dbMaxOpenFiles, - ReadOnly: true, - } - ldb, err := open(location, opts) - if err != nil { - return nil, err - } - return newLeveldbBackend(ldb, location), nil -} - -// OpenLevelDBMemory returns a new Backend referencing an in-memory database. -func OpenLevelDBMemory() Backend { - ldb, _ := leveldb.Open(storage.NewMemStorage(), nil) - return newLeveldbBackend(ldb, "") -} - -// optsFor returns the database options to use when opening a database with -// the given location and tuning. Settings can be overridden by debug -// environment variables. -func optsFor(location string, tuning Tuning) *opt.Options { - large := false - switch tuning { - case TuningLarge: - large = true - case TuningAuto: - large = dbIsLarge(location) - } - - var ( - // Set defaults used for small databases. - defaultBlockCacheCapacity = 0 // 0 means let leveldb use default - defaultBlockSize = 0 - defaultCompactionTableSize = 0 - defaultCompactionTableSizeMultiplier = 0 - defaultWriteBuffer = 16 << MiB // increased from leveldb default of 4 MiB - defaultCompactionL0Trigger = opt.DefaultCompactionL0Trigger // explicit because we use it as base for other stuff - ) - - if large { - // Change the parameters for better throughput at the price of some - // RAM and larger files. This results in larger batches of writes - // and compaction at a lower frequency. - l.Infoln("Using large-database tuning") - - defaultBlockCacheCapacity = 64 << MiB - defaultBlockSize = 64 << KiB - defaultCompactionTableSize = 16 << MiB - defaultCompactionTableSizeMultiplier = 20 // 2.0 after division by ten - defaultWriteBuffer = 64 << MiB - defaultCompactionL0Trigger = 8 // number of l0 files - } - - opts := &opt.Options{ - BlockCacheCapacity: debugEnvValue("BlockCacheCapacity", defaultBlockCacheCapacity), - BlockCacheEvictRemoved: debugEnvValue("BlockCacheEvictRemoved", 0) != 0, - BlockRestartInterval: debugEnvValue("BlockRestartInterval", 0), - BlockSize: debugEnvValue("BlockSize", defaultBlockSize), - CompactionExpandLimitFactor: debugEnvValue("CompactionExpandLimitFactor", 0), - CompactionGPOverlapsFactor: debugEnvValue("CompactionGPOverlapsFactor", 0), - CompactionL0Trigger: debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger), - CompactionSourceLimitFactor: debugEnvValue("CompactionSourceLimitFactor", 0), - CompactionTableSize: debugEnvValue("CompactionTableSize", defaultCompactionTableSize), - CompactionTableSizeMultiplier: float64(debugEnvValue("CompactionTableSizeMultiplier", defaultCompactionTableSizeMultiplier)) / 10.0, - CompactionTotalSize: debugEnvValue("CompactionTotalSize", 0), - CompactionTotalSizeMultiplier: float64(debugEnvValue("CompactionTotalSizeMultiplier", 0)) / 10.0, - DisableBufferPool: debugEnvValue("DisableBufferPool", 0) != 0, - DisableBlockCache: debugEnvValue("DisableBlockCache", 0) != 0, - DisableCompactionBackoff: debugEnvValue("DisableCompactionBackoff", 0) != 0, - DisableLargeBatchTransaction: debugEnvValue("DisableLargeBatchTransaction", 0) != 0, - NoSync: debugEnvValue("NoSync", 0) != 0, - NoWriteMerge: debugEnvValue("NoWriteMerge", 0) != 0, - OpenFilesCacheCapacity: debugEnvValue("OpenFilesCacheCapacity", dbMaxOpenFiles), - WriteBuffer: debugEnvValue("WriteBuffer", defaultWriteBuffer), - // The write slowdown and pause can be overridden, but even if they - // are not and the compaction trigger is overridden we need to - // adjust so that we don't pause writes for L0 compaction before we - // even *start* L0 compaction... - WriteL0SlowdownTrigger: debugEnvValue("WriteL0SlowdownTrigger", 2*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)), - WriteL0PauseTrigger: debugEnvValue("WriteL0SlowdownTrigger", 3*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)), - } - - return opts -} - -func open(location string, opts *opt.Options) (*leveldb.DB, error) { - db, err := leveldb.OpenFile(location, opts) - if leveldbIsCorrupted(err) { - db, err = leveldb.RecoverFile(location, opts) - } - if leveldbIsCorrupted(err) { - // The database is corrupted, and we've tried to recover it but it - // didn't work. At this point there isn't much to do beyond dropping - // the database and reindexing... - l.Infoln("Database corruption detected, unable to recover. Reinitializing...") - if err := os.RemoveAll(location); err != nil { - return nil, &errorSuggestion{err, "failed to delete corrupted database"} - } - db, err = leveldb.OpenFile(location, opts) - } - if err != nil { - return nil, &errorSuggestion{err, "is another instance of Syncthing running?"} - } - - if debugEnvValue("CompactEverything", 0) != 0 { - if err := db.CompactRange(util.Range{}); err != nil { - l.Warnln("Compacting database:", err) - } - } - - return db, nil -} - -func debugEnvValue(key string, def int) int { - v, err := strconv.ParseInt(os.Getenv("STDEBUG_"+key), 10, 63) - if err != nil { - return def - } - return int(v) -} - -// A "better" version of leveldb's errors.IsCorrupted. -func leveldbIsCorrupted(err error) bool { - switch { - case err == nil: - return false - - case errors.IsCorrupted(err): - return true - - case strings.Contains(err.Error(), "corrupted"): - return true - } - - return false -} - -// dbIsLarge returns whether the estimated size of the database at location -// is large enough to warrant optimization for large databases. -func dbIsLarge(location string) bool { - if ^uint(0)>>63 == 0 { - // We're compiled for a 32 bit architecture. We've seen trouble with - // large settings there. - // (https://forum.syncthing.net/t/many-small-ldb-files-with-database-tuning/13842) - return false - } - - entries, err := os.ReadDir(location) - if err != nil { - return false - } - - var size int64 - for _, entry := range entries { - if entry.Name() == "LOG" { - // don't count the size - continue - } - fi, err := entry.Info() - if err != nil { - continue - } - size += fi.Size() - } - - return size > dbLargeThreshold -} - -type errorSuggestion struct { - inner error - suggestion string -} - -func (e *errorSuggestion) Error() string { - return fmt.Sprintf("%s (%s)", e.inner.Error(), e.suggestion) -} diff --git a/lib/db/backend/leveldb_test.go b/lib/db/backend/leveldb_test.go deleted file mode 100644 index 3de46ad90..000000000 --- a/lib/db/backend/leveldb_test.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2019 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 https://mozilla.org/MPL/2.0/. - -package backend - -import "testing" - -func TestLevelDBBackendBehavior(t *testing.T) { - testBackendBehavior(t, OpenLevelDBMemory) -} diff --git a/lib/db/benchmark_test.go b/lib/db/benchmark_test.go deleted file mode 100644 index 6497eb829..000000000 --- a/lib/db/benchmark_test.go +++ /dev/null @@ -1,344 +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 https://mozilla.org/MPL/2.0/. - -package db_test - -import ( - "fmt" - "testing" - - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/protocol" -) - -var files, oneFile, firstHalf, secondHalf, changed100, unchanged100 []protocol.FileInfo - -func lazyInitBenchFiles() { - if files != nil { - return - } - - files = make([]protocol.FileInfo, 0, 1000) - for i := 0; i < 1000; i++ { - files = append(files, protocol.FileInfo{ - Name: fmt.Sprintf("file%d", i), - Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, - Blocks: genBlocks(i), - }) - } - - middle := len(files) / 2 - firstHalf = files[:middle] - secondHalf = files[middle:] - oneFile = firstHalf[middle-1 : middle] - - unchanged100 := files[100:200] - changed100 := append([]protocol.FileInfo{}, unchanged100...) - for i := range changed100 { - changed100[i].Version = changed100[i].Version.Copy().Update(myID) - } -} - -func getBenchFileSet(b testing.TB) (*db.Lowlevel, *db.FileSet) { - lazyInitBenchFiles() - - ldb := newLowlevelMemory(b) - benchS := newFileSet(b, "test)", ldb) - replace(benchS, remoteDevice0, files) - replace(benchS, protocol.LocalDeviceID, firstHalf) - - return ldb, benchS -} - -func BenchmarkReplaceAll(b *testing.B) { - ldb := newLowlevelMemory(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - m := newFileSet(b, "test)", ldb) - replace(m, protocol.LocalDeviceID, files) - } - - b.ReportAllocs() -} - -func BenchmarkUpdateOneChanged(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - changed := make([]protocol.FileInfo, 1) - changed[0] = oneFile[0] - changed[0].Version = changed[0].Version.Copy().Update(myID) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if i%2 == 0 { - benchS.Update(protocol.LocalDeviceID, changed) - } else { - benchS.Update(protocol.LocalDeviceID, oneFile) - } - } - - b.ReportAllocs() -} - -func BenchmarkUpdate100Changed(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if i%2 == 0 { - benchS.Update(protocol.LocalDeviceID, changed100) - } else { - benchS.Update(protocol.LocalDeviceID, unchanged100) - } - } - - b.ReportAllocs() -} - -func setup10Remotes(benchS *db.FileSet) { - idBase := remoteDevice1.String()[1:] - first := 'J' - for i := 0; i < 10; i++ { - id, _ := protocol.DeviceIDFromString(fmt.Sprintf("%v%s", first+rune(i), idBase)) - if i%2 == 0 { - benchS.Update(id, changed100) - } else { - benchS.Update(id, unchanged100) - } - } -} - -func BenchmarkUpdate100Changed10Remotes(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - setup10Remotes(benchS) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if i%2 == 0 { - benchS.Update(protocol.LocalDeviceID, changed100) - } else { - benchS.Update(protocol.LocalDeviceID, unchanged100) - } - } - - b.ReportAllocs() -} - -func BenchmarkUpdate100ChangedRemote(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if i%2 == 0 { - benchS.Update(remoteDevice0, changed100) - } else { - benchS.Update(remoteDevice0, unchanged100) - } - } - - b.ReportAllocs() -} - -func BenchmarkUpdate100ChangedRemote10Remotes(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if i%2 == 0 { - benchS.Update(remoteDevice0, changed100) - } else { - benchS.Update(remoteDevice0, unchanged100) - } - } - - b.ReportAllocs() -} - -func BenchmarkUpdateOneUnchanged(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - benchS.Update(protocol.LocalDeviceID, oneFile) - } - - b.ReportAllocs() -} - -func BenchmarkNeedHalf(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - count := 0 - snap := snapshot(b, benchS) - snap.WithNeed(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - if count != len(secondHalf) { - b.Errorf("wrong length %d != %d", count, len(secondHalf)) - } - } - - b.ReportAllocs() -} - -func BenchmarkNeedHalfRemote(b *testing.B) { - ldb := newLowlevelMemory(b) - defer ldb.Close() - fset := newFileSet(b, "test)", ldb) - replace(fset, remoteDevice0, firstHalf) - replace(fset, protocol.LocalDeviceID, files) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - count := 0 - snap := snapshot(b, fset) - snap.WithNeed(remoteDevice0, func(fi protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - if count != len(secondHalf) { - b.Errorf("wrong length %d != %d", count, len(secondHalf)) - } - } - - b.ReportAllocs() -} - -func BenchmarkHave(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - count := 0 - snap := snapshot(b, benchS) - snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - if count != len(firstHalf) { - b.Errorf("wrong length %d != %d", count, len(firstHalf)) - } - } - - b.ReportAllocs() -} - -func BenchmarkGlobal(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - count := 0 - snap := snapshot(b, benchS) - snap.WithGlobal(func(fi protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - if count != len(files) { - b.Errorf("wrong length %d != %d", count, len(files)) - } - } - - b.ReportAllocs() -} - -func BenchmarkNeedHalfTruncated(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - count := 0 - snap := snapshot(b, benchS) - snap.WithNeedTruncated(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - if count != len(secondHalf) { - b.Errorf("wrong length %d != %d", count, len(secondHalf)) - } - } - - b.ReportAllocs() -} - -func BenchmarkHaveTruncated(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - count := 0 - snap := snapshot(b, benchS) - snap.WithHaveTruncated(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - if count != len(firstHalf) { - b.Errorf("wrong length %d != %d", count, len(firstHalf)) - } - } - - b.ReportAllocs() -} - -func BenchmarkGlobalTruncated(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - count := 0 - snap := snapshot(b, benchS) - snap.WithGlobalTruncated(func(fi protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - if count != len(files) { - b.Errorf("wrong length %d != %d", count, len(files)) - } - } - - b.ReportAllocs() -} - -func BenchmarkNeedCount(b *testing.B) { - ldb, benchS := getBenchFileSet(b) - defer ldb.Close() - - benchS.Update(protocol.LocalDeviceID, changed100) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - snap := snapshot(b, benchS) - _ = snap.NeedSize(protocol.LocalDeviceID) - snap.Release() - } - - b.ReportAllocs() -} diff --git a/lib/db/blockmap.go b/lib/db/blockmap.go deleted file mode 100644 index 78d71c0b1..000000000 --- a/lib/db/blockmap.go +++ /dev/null @@ -1,64 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "encoding/binary" - "fmt" - - "github.com/syncthing/syncthing/lib/osutil" -) - -type BlockFinder struct { - db *Lowlevel -} - -func NewBlockFinder(db *Lowlevel) *BlockFinder { - return &BlockFinder{ - db: db, - } -} - -func (f *BlockFinder) String() string { - return fmt.Sprintf("BlockFinder@%p", f) -} - -// Iterate takes an iterator function which iterates over all matching blocks -// for the given hash. The iterator function has to return either true (if -// they are happy with the block) or false to continue iterating for whatever -// reason. The iterator finally returns the result, whether or not a -// satisfying block was eventually found. -func (f *BlockFinder) Iterate(folders []string, hash []byte, iterFn func(string, string, int32) bool) bool { - t, err := f.db.newReadOnlyTransaction() - if err != nil { - return false - } - defer t.close() - - var key []byte - for _, folder := range folders { - key, err = f.db.keyer.GenerateBlockMapKey(key, []byte(folder), hash, nil) - if err != nil { - return false - } - iter, err := t.NewPrefixIterator(key) - if err != nil { - return false - } - - for iter.Next() && iter.Error() == nil { - file := string(f.db.keyer.NameFromBlockMapKey(iter.Key())) - index := int32(binary.BigEndian.Uint32(iter.Value())) - if iterFn(folder, osutil.NativeFilename(file), index) { - iter.Release() - return true - } - } - iter.Release() - } - return false -} diff --git a/lib/db/blockmap_test.go b/lib/db/blockmap_test.go deleted file mode 100644 index e5d298836..000000000 --- a/lib/db/blockmap_test.go +++ /dev/null @@ -1,260 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "encoding/binary" - "testing" - - "github.com/syncthing/syncthing/lib/protocol" -) - -var ( - f1, f2, f3 protocol.FileInfo - folders = []string{"folder1", "folder2"} -) - -func init() { - blocks := genBlocks(30) - - f1 = protocol.FileInfo{ - Name: "f1", - Blocks: blocks[:10], - } - - f2 = protocol.FileInfo{ - Name: "f2", - Blocks: blocks[10:20], - } - - f3 = protocol.FileInfo{ - Name: "f3", - Blocks: blocks[20:], - } -} - -func setup(t testing.TB) (*Lowlevel, *BlockFinder) { - t.Helper() - db := newLowlevelMemory(t) - return db, NewBlockFinder(db) -} - -func dbEmpty(db *Lowlevel) bool { - iter, err := db.NewPrefixIterator([]byte{KeyTypeBlock}) - if err != nil { - panic(err) - } - defer iter.Release() - return !iter.Next() -} - -func addToBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) error { - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - - var keyBuf []byte - blockBuf := make([]byte, 4) - for _, f := range fs { - if !f.IsDirectory() && !f.IsDeleted() && !f.IsInvalid() { - name := []byte(f.Name) - for i, block := range f.Blocks { - binary.BigEndian.PutUint32(blockBuf, uint32(i)) - keyBuf, err = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name) - if err != nil { - return err - } - if err := t.Put(keyBuf, blockBuf); err != nil { - return err - } - } - } - } - return t.Commit() -} - -func discardFromBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) error { - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - - var keyBuf []byte - for _, ef := range fs { - if !ef.IsDirectory() && !ef.IsDeleted() && !ef.IsInvalid() { - name := []byte(ef.Name) - for _, block := range ef.Blocks { - keyBuf, err = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name) - if err != nil { - return err - } - if err := t.Delete(keyBuf); err != nil { - return err - } - } - } - } - return t.Commit() -} - -func TestBlockMapAddUpdateWipe(t *testing.T) { - db, f := setup(t) - defer db.Close() - - if !dbEmpty(db) { - t.Fatal("db not empty") - } - - folder := []byte("folder1") - - f3.Type = protocol.FileInfoTypeDirectory - - if err := addToBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3}); err != nil { - t.Fatal(err) - } - - f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool { - if folder != "folder1" || file != "f1" || index != 0 { - t.Fatal("Mismatch") - } - return true - }) - - f.Iterate(folders, f2.Blocks[0].Hash, func(folder, file string, index int32) bool { - if folder != "folder1" || file != "f2" || index != 0 { - t.Fatal("Mismatch") - } - return true - }) - - f.Iterate(folders, f3.Blocks[0].Hash, func(folder, file string, index int32) bool { - t.Fatal("Unexpected block") - return true - }) - - if err := discardFromBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3}); err != nil { - t.Fatal(err) - } - - f1.Deleted = true - f2.LocalFlags = protocol.FlagLocalMustRescan // one of the invalid markers - - if err := addToBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3}); err != nil { - t.Fatal(err) - } - - f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool { - t.Fatal("Unexpected block") - return false - }) - - f.Iterate(folders, f2.Blocks[0].Hash, func(folder, file string, index int32) bool { - t.Fatal("Unexpected block") - return false - }) - - f.Iterate(folders, f3.Blocks[0].Hash, func(folder, file string, index int32) bool { - if folder != "folder1" || file != "f3" || index != 0 { - t.Fatal("Mismatch") - } - return true - }) - - if err := db.dropFolder(folder); err != nil { - t.Fatal(err) - } - - if !dbEmpty(db) { - t.Fatal("db not empty") - } - - // Should not add - if err := addToBlockMap(db, folder, []protocol.FileInfo{f1, f2}); err != nil { - t.Fatal(err) - } - - if !dbEmpty(db) { - t.Fatal("db not empty") - } - - f1.Deleted = false - f1.LocalFlags = 0 - f2.Deleted = false - f2.LocalFlags = 0 - f3.Deleted = false - f3.LocalFlags = 0 -} - -func TestBlockFinderLookup(t *testing.T) { - db, f := setup(t) - defer db.Close() - - folder1 := []byte("folder1") - folder2 := []byte("folder2") - - if err := addToBlockMap(db, folder1, []protocol.FileInfo{f1}); err != nil { - t.Fatal(err) - } - if err := addToBlockMap(db, folder2, []protocol.FileInfo{f1}); err != nil { - t.Fatal(err) - } - - counter := 0 - f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool { - counter++ - switch counter { - case 1: - if folder != "folder1" || file != "f1" || index != 0 { - t.Fatal("Mismatch") - } - case 2: - if folder != "folder2" || file != "f1" || index != 0 { - t.Fatal("Mismatch") - } - default: - t.Fatal("Unexpected block") - } - return false - }) - - if counter != 2 { - t.Fatal("Incorrect count", counter) - } - - if err := discardFromBlockMap(db, folder1, []protocol.FileInfo{f1}); err != nil { - t.Fatal(err) - } - - f1.Deleted = true - - if err := addToBlockMap(db, folder1, []protocol.FileInfo{f1}); err != nil { - t.Fatal(err) - } - - counter = 0 - f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool { - counter++ - switch counter { - case 1: - if folder != "folder2" || file != "f1" || index != 0 { - t.Fatal("Mismatch") - } - default: - t.Fatal("Unexpected block") - } - return false - }) - - if counter != 1 { - t.Fatal("Incorrect count") - } - - f1.Deleted = false -} diff --git a/lib/db/db_test.go b/lib/db/db_test.go deleted file mode 100644 index 910e6767c..000000000 --- a/lib/db/db_test.go +++ /dev/null @@ -1,701 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "bytes" - "context" - "fmt" - "testing" - - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/events" - "github.com/syncthing/syncthing/lib/protocol" -) - -func genBlocks(n int) []protocol.BlockInfo { - b := make([]protocol.BlockInfo, n) - for i := range b { - h := make([]byte, 32) - for j := range h { - h[j] = byte(i + j) - } - b[i].Size = i - b[i].Hash = h - } - return b -} - -const myID = 1 - -var ( - remoteDevice0, remoteDevice1 protocol.DeviceID - invalid = "invalid" - slashPrefixed = "/notgood" - haveUpdate0to3 map[protocol.DeviceID][]protocol.FileInfo -) - -func init() { - remoteDevice0, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR") - remoteDevice1, _ = protocol.DeviceIDFromString("I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU") - haveUpdate0to3 = map[protocol.DeviceID][]protocol.FileInfo{ - protocol.LocalDeviceID: { - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: slashPrefixed, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - }, - remoteDevice0: { - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(5), RawInvalid: true}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(7)}, - }, - remoteDevice1: { - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(7)}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(5), RawInvalid: true}, - protocol.FileInfo{Name: invalid, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1004}}}, Blocks: genBlocks(5), RawInvalid: true}, - }, - } -} - -// TestRepairSequence checks that a few hand-crafted messed-up sequence entries get fixed. -func TestRepairSequence(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - - folderStr := "test" - folder := []byte(folderStr) - id := protocol.LocalDeviceID - short := protocol.LocalDeviceID.Short() - - files := []protocol.FileInfo{ - {Name: "fine", Blocks: genBlocks(1)}, - {Name: "duplicate", Blocks: genBlocks(2)}, - {Name: "missing", Blocks: genBlocks(3)}, - {Name: "overwriting", Blocks: genBlocks(4)}, - {Name: "inconsistent", Blocks: genBlocks(5)}, - {Name: "inconsistentNotIndirected", Blocks: genBlocks(2)}, - } - for i, f := range files { - files[i].Version = f.Version.Update(short) - } - - trans, err := db.newReadWriteTransaction() - if err != nil { - t.Fatal(err) - } - defer trans.close() - - addFile := func(f protocol.FileInfo, seq int64) { - dk, err := trans.keyer.GenerateDeviceFileKey(nil, folder, id[:], []byte(f.Name)) - if err != nil { - t.Fatal(err) - } - if err := trans.putFile(dk, f); err != nil { - t.Fatal(err) - } - sk, err := trans.keyer.GenerateSequenceKey(nil, folder, seq) - if err != nil { - t.Fatal(err) - } - if err := trans.Put(sk, dk); err != nil { - t.Fatal(err) - } - } - - // Plain normal entry - var seq int64 = 1 - files[0].Sequence = 1 - addFile(files[0], seq) - - // Second entry once updated with original sequence still in place - f := files[1] - f.Sequence = int64(len(files) + 1) - addFile(f, f.Sequence) - // Original sequence entry - seq++ - sk, err := trans.keyer.GenerateSequenceKey(nil, folder, seq) - if err != nil { - t.Fatal(err) - } - dk, err := trans.keyer.GenerateDeviceFileKey(nil, folder, id[:], []byte(f.Name)) - if err != nil { - t.Fatal(err) - } - if err := trans.Put(sk, dk); err != nil { - t.Fatal(err) - } - - // File later overwritten thus missing sequence entry - seq++ - files[2].Sequence = seq - addFile(files[2], seq) - - // File overwriting previous sequence entry (no seq bump) - seq++ - files[3].Sequence = seq - addFile(files[3], seq) - - // Inconistent files - seq++ - files[4].Sequence = 101 - addFile(files[4], seq) - seq++ - files[5].Sequence = 102 - addFile(files[5], seq) - - // And a sequence entry pointing at nothing because why not - sk, err = trans.keyer.GenerateSequenceKey(nil, folder, 100001) - if err != nil { - t.Fatal(err) - } - dk, err = trans.keyer.GenerateDeviceFileKey(nil, folder, id[:], []byte("nonexisting")) - if err != nil { - t.Fatal(err) - } - if err := trans.Put(sk, dk); err != nil { - t.Fatal(err) - } - - if err := trans.Commit(); err != nil { - t.Fatal(err) - } - - // Loading the metadata for the first time means a "re"calculation happens, - // along which the sequences get repaired too. - db.gcMut.RLock() - _, err = db.loadMetadataTracker(folderStr) - db.gcMut.RUnlock() - if err != nil { - t.Fatal(err) - } - - // Check the db - ro, err := db.newReadOnlyTransaction() - if err != nil { - t.Fatal(err) - } - defer ro.close() - - it, err := ro.NewPrefixIterator([]byte{KeyTypeDevice}) - if err != nil { - t.Fatal(err) - } - defer it.Release() - for it.Next() { - fi, err := ro.unmarshalTrunc(it.Value(), true) - if err != nil { - t.Fatal(err) - } - if sk, err = ro.keyer.GenerateSequenceKey(sk, folder, fi.SequenceNo()); err != nil { - t.Fatal(err) - } - dk, err := ro.Get(sk) - if backend.IsNotFound(err) { - t.Error("Missing sequence entry for", fi.FileName()) - } else if err != nil { - t.Fatal(err) - } - if !bytes.Equal(it.Key(), dk) { - t.Errorf("Wrong key for %v, expected %s, got %s", f.FileName(), it.Key(), dk) - } - } - if err := it.Error(); err != nil { - t.Fatal(err) - } - it.Release() - - it, err = ro.NewPrefixIterator([]byte{KeyTypeSequence}) - if err != nil { - t.Fatal(err) - } - defer it.Release() - for it.Next() { - fi, ok, err := ro.getFileTrunc(it.Value(), false) - if err != nil { - t.Fatal(err) - } - seq := ro.keyer.SequenceFromSequenceKey(it.Key()) - if !ok { - t.Errorf("Sequence entry %v points at nothing", seq) - } else if fi.SequenceNo() != seq { - t.Errorf("Inconsistent sequence entry for %v: %v != %v", fi.FileName(), fi.SequenceNo(), seq) - } - if len(fi.Blocks) == 0 { - t.Error("Missing blocks in", fi.FileName()) - } - } - if err := it.Error(); err != nil { - t.Fatal(err) - } - it.Release() -} - -func TestDowngrade(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - // sets the min version etc - if err := UpdateSchema(db); err != nil { - t.Fatal(err) - } - - // Bump the database version to something newer than we actually support - miscDB := NewMiscDataNamespace(db) - if err := miscDB.PutInt64("dbVersion", dbVersion+1); err != nil { - t.Fatal(err) - } - l.Infoln(dbVersion) - - // Pretend we just opened the DB and attempt to update it again - err := UpdateSchema(db) - - if err, ok := err.(*databaseDowngradeError); !ok { - t.Fatal("Expected error due to database downgrade, got", err) - } else if err.minSyncthingVersion != dbMinSyncthingVersion { - t.Fatalf("Error has %v as min Syncthing version, expected %v", err.minSyncthingVersion, dbMinSyncthingVersion) - } -} - -func TestCheckGlobals(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - - fs := newFileSet(t, "test", db) - - // Add any file - name := "foo" - fs.Update(protocol.LocalDeviceID, []protocol.FileInfo{ - { - Name: name, - Type: protocol.FileInfoTypeFile, - Version: protocol.Vector{Counters: []protocol.Counter{{ID: 1, Value: 1001}}}, - }, - }) - - // Remove just the file entry - if err := db.dropPrefix([]byte{KeyTypeDevice}); err != nil { - t.Fatal(err) - } - - // Clean up global entry of the now missing file - if repaired, err := db.checkGlobals(fs.folder); err != nil { - t.Fatal(err) - } else if repaired != 1 { - t.Error("Expected 1 repaired global item, got", repaired) - } - - // Check that the global entry is gone - gk, err := db.keyer.GenerateGlobalVersionKey(nil, []byte(fs.folder), []byte(name)) - if err != nil { - t.Fatal(err) - } - _, err = db.Get(gk) - if !backend.IsNotFound(err) { - t.Error("Expected key missing error, got", err) - } -} - -func TestDropDuplicates(t *testing.T) { - names := []string{ - "foo", - "bar", - "dcxvoijnds", - "3d/dsfase/4/ss2", - } - tcs := []struct{ in, out []int }{ - {[]int{0}, []int{0}}, - {[]int{0, 1}, []int{0, 1}}, - {[]int{0, 1, 0, 1}, []int{0, 1}}, - {[]int{0, 1, 1, 1, 1}, []int{0, 1}}, - {[]int{0, 0, 0, 1}, []int{0, 1}}, - {[]int{0, 1, 2, 3}, []int{0, 1, 2, 3}}, - {[]int{3, 2, 1, 0, 0, 1, 2, 3}, []int{0, 1, 2, 3}}, - {[]int{0, 1, 1, 3, 0, 1, 0, 1, 2, 3}, []int{0, 1, 2, 3}}, - } - - for tci, tc := range tcs { - inp := make([]protocol.FileInfo, len(tc.in)) - expSeq := make(map[string]int) - for i, j := range tc.in { - inp[i] = protocol.FileInfo{Name: names[j], Sequence: int64(i)} - expSeq[names[j]] = i - } - outp := normalizeFilenamesAndDropDuplicates(inp) - if len(outp) != len(tc.out) { - t.Errorf("tc %v: Expected %v entries, got %v", tci, len(tc.out), len(outp)) - continue - } - for i, f := range outp { - if exp := names[tc.out[i]]; exp != f.Name { - t.Errorf("tc %v: Got file %v at pos %v, expected %v", tci, f.Name, i, exp) - } - if exp := int64(expSeq[outp[i].Name]); exp != f.Sequence { - t.Errorf("tc %v: Got sequence %v at pos %v, expected %v", tci, f.Sequence, i, exp) - } - } - } -} - -func TestGCIndirect(t *testing.T) { - // Verify that the gcIndirect run actually removes block lists. - - db := newLowlevelMemory(t) - defer db.Close() - meta := newMetadataTracker(db.keyer, events.NoopLogger) - - // Add three files with different block lists - - files := []protocol.FileInfo{ - {Name: "a", Blocks: genBlocks(100)}, - {Name: "b", Blocks: genBlocks(200)}, - {Name: "c", Blocks: genBlocks(300)}, - } - - db.updateLocalFiles([]byte("folder"), files, meta) - - // Run a GC pass - - db.gcIndirect(context.Background()) - - // Verify that we have three different block lists - - n, err := numBlockLists(db) - if err != nil { - t.Fatal(err) - } - if n != len(files) { - t.Fatal("expected each file to have a block list") - } - - // Change the block lists for each file - - for i := range files { - files[i].Version = files[i].Version.Update(42) - files[i].Blocks = genBlocks(len(files[i].Blocks) + 1) - } - - db.updateLocalFiles([]byte("folder"), files, meta) - - // Verify that we now have *six* different block lists - - n, err = numBlockLists(db) - if err != nil { - t.Fatal(err) - } - if n != 2*len(files) { - t.Fatal("expected both old and new block lists to exist") - } - - // Run a GC pass - - db.gcIndirect(context.Background()) - - // Verify that we now have just the three we need, again - - n, err = numBlockLists(db) - if err != nil { - t.Fatal(err) - } - if n != len(files) { - t.Fatal("expected GC to collect all but the needed ones") - } - - // Double check the correctness by loading the block lists and comparing with what we stored - - tr, err := db.newReadOnlyTransaction() - if err != nil { - t.Fatal() - } - defer tr.Release() - for _, f := range files { - fi, ok, err := tr.getFile([]byte("folder"), protocol.LocalDeviceID[:], []byte(f.Name)) - if err != nil { - t.Fatal(err) - } - if !ok { - t.Fatal("mysteriously missing") - } - if len(fi.Blocks) != len(f.Blocks) { - t.Fatal("block list mismatch") - } - for i := range fi.Blocks { - if !bytes.Equal(fi.Blocks[i].Hash, f.Blocks[i].Hash) { - t.Fatal("hash mismatch") - } - } - } -} - -func TestUpdateTo14(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - - folderStr := "default" - folder := []byte(folderStr) - name := []byte("foo") - file := protocol.FileInfo{Name: string(name), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(blocksIndirectionCutoff - 1)} - file.BlocksHash = protocol.BlocksHash(file.Blocks) - fileWOBlocks := file - fileWOBlocks.Blocks = nil - meta, err := db.loadMetadataTracker(folderStr) - if err != nil { - t.Fatal(err) - } - - // Initially add the correct file the usual way, all good here. - if err := db.updateLocalFiles(folder, []protocol.FileInfo{file}, meta); err != nil { - t.Fatal(err) - } - - // Simulate the previous bug, where .putFile could write a file info without - // blocks, even though the file has them (and thus a non-nil BlocksHash). - trans, err := db.newReadWriteTransaction() - if err != nil { - t.Fatal(err) - } - defer trans.close() - key, err := db.keyer.GenerateDeviceFileKey(nil, folder, protocol.LocalDeviceID[:], name) - if err != nil { - t.Fatal(err) - } - fiBs := mustMarshal(fileWOBlocks.ToWire(true)) - if err := trans.Put(key, fiBs); err != nil { - t.Fatal(err) - } - if err := trans.Commit(); err != nil { - t.Fatal(err) - } - trans.close() - - // Run migration, pretending were still on schema 13. - if err := (&schemaUpdater{db}).updateSchemaTo14(13); err != nil { - t.Fatal(err) - } - - // checks - ro, err := db.newReadOnlyTransaction() - if err != nil { - t.Fatal(err) - } - defer ro.close() - if f, ok, err := ro.getFileByKey(key); err != nil { - t.Fatal(err) - } else if !ok { - t.Error("file missing") - } else if !f.MustRescan() { - t.Error("file not marked as MustRescan") - } - - if vl, err := ro.getGlobalVersions(nil, folder, name); err != nil { - t.Fatal(err) - } else if fv, ok := vlGetGlobal(vl); !ok { - t.Error("missing global") - } else if !fvIsInvalid(fv) { - t.Error("global not marked as invalid") - } -} - -func TestFlushRecursion(t *testing.T) { - // Verify that a commit hook can write to the transaction without - // causing another flush and thus recursion. - - db := newLowlevelMemory(t) - defer db.Close() - - // A commit hook that writes a small piece of data to the transaction. - hookFired := 0 - hook := func(tx backend.WriteTransaction) error { - err := tx.Put([]byte(fmt.Sprintf("hook-key-%d", hookFired)), []byte(fmt.Sprintf("hook-value-%d", hookFired))) - if err != nil { - t.Fatal(err) - } - hookFired++ - return nil - } - - // A transaction. - tx, err := db.NewWriteTransaction(hook) - if err != nil { - t.Fatal(err) - } - defer tx.Release() - - // Write stuff until the transaction flushes, thus firing the hook. - i := 0 - for hookFired == 0 { - err := tx.Put([]byte(fmt.Sprintf("key-%d", i)), []byte(fmt.Sprintf("value-%d", i))) - if err != nil { - t.Fatal(err) - } - i++ - } - - // The hook should have fired precisely once. - if hookFired != 1 { - t.Error("expect one hook fire, not", hookFired) - } -} - -func TestCheckLocalNeed(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - - folderStr := "test" - fs := newFileSet(t, folderStr, db) - - // Add files such that we are in sync for a and b, and need c and d. - files := []protocol.FileInfo{ - {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}, - {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}, - {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}, - {Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}, - } - fs.Update(protocol.LocalDeviceID, files) - files[2].Version = files[2].Version.Update(remoteDevice0.Short()) - files[3].Version = files[2].Version.Update(remoteDevice0.Short()) - fs.Update(remoteDevice0, files) - - checkNeed := func() { - snap := snapshot(t, fs) - defer snap.Release() - c := snap.NeedSize(protocol.LocalDeviceID) - if c.Files != 2 { - t.Errorf("Expected 2 needed files locally, got %v in meta", c.Files) - } - needed := make([]protocol.FileInfo, 0, 2) - snap.WithNeed(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { - needed = append(needed, fi) - return true - }) - if l := len(needed); l != 2 { - t.Errorf("Expected 2 needed files locally, got %v in db", l) - } else if needed[0].Name != "c" || needed[1].Name != "d" { - t.Errorf("Expected files c and d to be needed, got %v and %v", needed[0].Name, needed[1].Name) - } - } - - checkNeed() - - trans, err := db.newReadWriteTransaction() - if err != nil { - t.Fatal(err) - } - defer trans.close() - - // Add "b" to needed and remove "d" - folder := []byte(folderStr) - key, err := trans.keyer.GenerateNeedFileKey(nil, folder, []byte(files[1].Name)) - if err != nil { - t.Fatal(err) - } - if err = trans.Put(key, nil); err != nil { - t.Fatal(err) - } - key, err = trans.keyer.GenerateNeedFileKey(nil, folder, []byte(files[3].Name)) - if err != nil { - t.Fatal(err) - } - if err = trans.Delete(key); err != nil { - t.Fatal(err) - } - if err := trans.Commit(); err != nil { - t.Fatal(err) - } - - if repaired, err := db.checkLocalNeed(folder); err != nil { - t.Fatal(err) - } else if repaired != 2 { - t.Error("Expected 2 repaired local need items, got", repaired) - } - - checkNeed() -} - -func TestDuplicateNeedCount(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - - folder := "test" - - fs := newFileSet(t, folder, db) - files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID), Sequence: 1}} - fs.Update(protocol.LocalDeviceID, files) - files[0].Version = files[0].Version.Update(remoteDevice0.Short()) - fs.Update(remoteDevice0, files) - - db.checkRepair() - - fs = newFileSet(t, folder, db) - found := false - for _, c := range fs.meta.counts.Counts { - if protocol.LocalDeviceID == c.DeviceID && c.LocalFlags == needFlag { - if found { - t.Fatal("second need count for local device encountered") - } - found = true - } - } - if !found { - t.Fatal("no need count for local device encountered") - } -} - -func TestNeedAfterDropGlobal(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - - folder := "test" - - fs := newFileSet(t, folder, db) - - // Initial: - // Three devices and a file "test": local has Version 1, remoteDevice0 - // Version 2 and remoteDevice2 doesn't have it. - // All of them have "bar", just so the db knows about remoteDevice2. - files := []protocol.FileInfo{ - {Name: "foo", Version: protocol.Vector{}.Update(myID), Sequence: 1}, - {Name: "bar", Version: protocol.Vector{}.Update(myID), Sequence: 2}, - } - fs.Update(protocol.LocalDeviceID, files) - files[0].Version = files[0].Version.Update(myID) - fs.Update(remoteDevice0, files) - fs.Update(remoteDevice1, files[1:]) - - // remoteDevice1 needs one file: test - snap := snapshot(t, fs) - c := snap.NeedSize(remoteDevice1) - if c.Files != 1 { - t.Errorf("Expected 1 needed files initially, got %v", c.Files) - } - snap.Release() - - // Drop remoteDevice0, i.e. remove all their files from db. - // That changes the global file, which is now what local has. - fs.Drop(remoteDevice0) - - // remoteDevice1 still needs test. - snap = snapshot(t, fs) - c = snap.NeedSize(remoteDevice1) - if c.Files != 1 { - t.Errorf("Expected still 1 needed files, got %v", c.Files) - } - snap.Release() -} - -func numBlockLists(db *Lowlevel) (int, error) { - it, err := db.Backend.NewPrefixIterator([]byte{KeyTypeBlockList}) - if err != nil { - return 0, err - } - defer it.Release() - n := 0 - for it.Next() { - n++ - } - if err := it.Error(); err != nil { - return 0, err - } - return n, nil -} diff --git a/lib/db/keyer_test.go b/lib/db/keyer_test.go deleted file mode 100644 index 6b4372c9c..000000000 --- a/lib/db/keyer_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "bytes" - "testing" -) - -func TestDeviceKey(t *testing.T) { - fld := []byte("folder6789012345678901234567890123456789012345678901234567890123") - dev := []byte("device67890123456789012345678901") - name := []byte("name") - - db := newLowlevelMemory(t) - defer db.Close() - - key, err := db.keyer.GenerateDeviceFileKey(nil, fld, dev, name) - if err != nil { - t.Fatal(err) - } - - fld2, ok := db.keyer.FolderFromDeviceFileKey(key) - if !ok { - t.Fatal("unexpectedly not found") - } - if !bytes.Equal(fld2, fld) { - t.Errorf("wrong folder %q != %q", fld2, fld) - } - dev2, ok := db.keyer.DeviceFromDeviceFileKey(key) - if !ok { - t.Fatal("unexpectedly not found") - } - if !bytes.Equal(dev2, dev) { - t.Errorf("wrong device %q != %q", dev2, dev) - } - name2 := db.keyer.NameFromDeviceFileKey(key) - if !bytes.Equal(name2, name) { - t.Errorf("wrong name %q != %q", name2, name) - } -} - -func TestGlobalKey(t *testing.T) { - fld := []byte("folder6789012345678901234567890123456789012345678901234567890123") - name := []byte("name") - - db := newLowlevelMemory(t) - defer db.Close() - - key, err := db.keyer.GenerateGlobalVersionKey(nil, fld, name) - if err != nil { - t.Fatal(err) - } - - name2 := db.keyer.NameFromGlobalVersionKey(key) - if !bytes.Equal(name2, name) { - t.Errorf("wrong name %q != %q", name2, name) - } -} - -func TestSequenceKey(t *testing.T) { - fld := []byte("folder6789012345678901234567890123456789012345678901234567890123") - - db := newLowlevelMemory(t) - defer db.Close() - - const seq = 1234567890 - key, err := db.keyer.GenerateSequenceKey(nil, fld, seq) - if err != nil { - t.Fatal(err) - } - outSeq := db.keyer.SequenceFromSequenceKey(key) - if outSeq != seq { - t.Errorf("sequence number mangled, %d != %d", outSeq, seq) - } -} diff --git a/lib/db/lowlevel.go b/lib/db/lowlevel.go deleted file mode 100644 index 368f7ff99..000000000 --- a/lib/db/lowlevel.go +++ /dev/null @@ -1,1453 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/binary" - "errors" - "fmt" - "hash/maphash" - "os" - "regexp" - "time" - - "github.com/greatroar/blobloom" - "github.com/thejerf/suture/v4" - "google.golang.org/protobuf/proto" - - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/events" - "github.com/syncthing/syncthing/lib/fs" - "github.com/syncthing/syncthing/lib/protocol" - "github.com/syncthing/syncthing/lib/stringutil" - "github.com/syncthing/syncthing/lib/svcutil" - "github.com/syncthing/syncthing/lib/sync" -) - -const ( - // We set the bloom filter capacity to handle 100k individual items with - // a false positive probability of 1% for the first pass. Once we know - // how many items we have we will use that number instead, if it's more - // than 100k. For fewer than 100k items we will just get better false - // positive rate instead. - indirectGCBloomCapacity = 100000 - indirectGCBloomFalsePositiveRate = 0.01 // 1% - indirectGCBloomMaxBytes = 32 << 20 // Use at most 32MiB memory, which covers our desired FP rate at 27 M items - indirectGCDefaultInterval = 13 * time.Hour - indirectGCTimeKey = "lastIndirectGCTime" - - // Use indirection for the block list when it exceeds this many entries - blocksIndirectionCutoff = 3 - // Use indirection for the version vector when it exceeds this many entries - versionIndirectionCutoff = 10 - - recheckDefaultInterval = 30 * 24 * time.Hour - - needsRepairSuffix = ".needsrepair" -) - -// Lowlevel is the lowest level database interface. It has a very simple -// purpose: hold the actual backend database, and the in-memory state -// that belong to that database. In the same way that a single on disk -// database can only be opened once, there should be only one Lowlevel for -// any given backend. -type Lowlevel struct { - *suture.Supervisor - backend.Backend - folderIdx *smallIndex - deviceIdx *smallIndex - keyer keyer - gcMut sync.RWMutex - gcKeyCount int - indirectGCInterval time.Duration - recheckInterval time.Duration - oneFileSetCreated chan struct{} - evLogger events.Logger - - blockFilter *bloomFilter - versionFilter *bloomFilter -} - -func NewLowlevel(backend backend.Backend, evLogger events.Logger, opts ...Option) (*Lowlevel, error) { - // Only log restarts in debug mode. - spec := svcutil.SpecWithDebugLogger(l) - db := &Lowlevel{ - Supervisor: suture.New("db.Lowlevel", spec), - Backend: backend, - folderIdx: newSmallIndex(backend, []byte{KeyTypeFolderIdx}), - deviceIdx: newSmallIndex(backend, []byte{KeyTypeDeviceIdx}), - gcMut: sync.NewRWMutex(), - indirectGCInterval: indirectGCDefaultInterval, - recheckInterval: recheckDefaultInterval, - oneFileSetCreated: make(chan struct{}), - evLogger: evLogger, - } - for _, opt := range opts { - opt(db) - } - db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx) - db.Add(svcutil.AsService(db.gcRunner, "db.Lowlevel/gcRunner")) - if path := db.needsRepairPath(); path != "" { - if _, err := os.Lstat(path); err == nil { - l.Infoln("Database was marked for repair - this may take a while") - if err := db.checkRepair(); err != nil { - db.handleFailure(err) - return nil, err - } - os.Remove(path) - } - } - return db, nil -} - -type Option func(*Lowlevel) - -// WithRecheckInterval sets the time interval in between metadata recalculations -// and consistency checks. -func WithRecheckInterval(dur time.Duration) Option { - return func(db *Lowlevel) { - if dur > 0 { - db.recheckInterval = dur - } - } -} - -// WithIndirectGCInterval sets the time interval in between GC runs. -func WithIndirectGCInterval(dur time.Duration) Option { - return func(db *Lowlevel) { - if dur > 0 { - db.indirectGCInterval = dur - } - } -} - -// ListFolders returns the list of folders currently in the database -func (db *Lowlevel) ListFolders() []string { - return db.folderIdx.Values() -} - -// updateRemoteFiles adds a list of fileinfos to the database and updates the -// global versionlist and metadata. -func (db *Lowlevel) updateRemoteFiles(folder, device []byte, fs []protocol.FileInfo, meta *metadataTracker) error { - db.gcMut.RLock() - defer db.gcMut.RUnlock() - - t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) - if err != nil { - return err - } - defer t.close() - - var dk, gk, keyBuf []byte - devID, err := protocol.DeviceIDFromBytes(device) - if err != nil { - return err - } - for _, f := range fs { - name := []byte(f.Name) - dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, device, name) - if err != nil { - return err - } - - ef, ok, err := t.getFileTrunc(dk, true) - if err != nil { - return err - } - - if ok { - meta.removeFile(devID, ef) - } - meta.addFile(devID, f) - - l.Debugf("insert (remote); folder=%q device=%v %v", folder, devID, f) - if err := t.putFile(dk, f); err != nil { - return err - } - - gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name) - if err != nil { - return err - } - keyBuf, err = t.updateGlobal(gk, keyBuf, folder, device, f, meta) - if err != nil { - return err - } - - if err := t.Checkpoint(); err != nil { - return err - } - } - - return t.Commit() -} - -// updateLocalFiles adds fileinfos to the db, and updates the global versionlist, -// metadata, sequence and blockmap buckets. -func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta *metadataTracker) error { - db.gcMut.RLock() - defer db.gcMut.RUnlock() - - t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) - if err != nil { - return err - } - defer t.close() - - var dk, gk, keyBuf []byte - blockBuf := make([]byte, 4) - for _, f := range fs { - name := []byte(f.Name) - dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name) - if err != nil { - return err - } - - ef, ok, err := t.getFileByKey(dk) - if err != nil { - return err - } - - blocksHashSame := ok && bytes.Equal(ef.BlocksHash, f.BlocksHash) - if ok { - keyBuf, err = db.removeLocalBlockAndSequenceInfo(keyBuf, folder, name, ef, !blocksHashSame, &t) - if err != nil { - return err - } - } - - f.Sequence = meta.nextLocalSeq() - - if ok { - meta.removeFile(protocol.LocalDeviceID, ef) - } - meta.addFile(protocol.LocalDeviceID, f) - - l.Debugf("insert (local); folder=%q %v", folder, f) - if err := t.putFile(dk, f); err != nil { - return err - } - - gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, []byte(f.Name)) - if err != nil { - return err - } - keyBuf, err = t.updateGlobal(gk, keyBuf, folder, protocol.LocalDeviceID[:], f, meta) - if err != nil { - return err - } - - keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, f.Sequence) - if err != nil { - return err - } - if err := t.Put(keyBuf, dk); err != nil { - return err - } - l.Debugf("adding sequence; folder=%q sequence=%v %v", folder, f.Sequence, f.Name) - - if len(f.Blocks) != 0 && !f.IsInvalid() && f.Size > 0 { - for i, block := range f.Blocks { - binary.BigEndian.PutUint32(blockBuf, uint32(i)) - keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name) - if err != nil { - return err - } - if err := t.Put(keyBuf, blockBuf); err != nil { - return err - } - } - if !blocksHashSame { - keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, f.BlocksHash, name) - if err != nil { - return err - } - if err = t.Put(keyBuf, nil); err != nil { - return err - } - } - } - - if err := t.Checkpoint(); err != nil { - return err - } - } - - return t.Commit() -} - -func (db *Lowlevel) removeLocalFiles(folder []byte, nameStrs []string, meta *metadataTracker) error { - db.gcMut.RLock() - defer db.gcMut.RUnlock() - - t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) - if err != nil { - return err - } - defer t.close() - - var dk, gk, buf []byte - for _, nameStr := range nameStrs { - name := []byte(nameStr) - dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name) - if err != nil { - return err - } - - ef, ok, err := t.getFileByKey(dk) - if err != nil { - return err - } - if !ok { - l.Debugf("remove (local); folder=%q %v: file doesn't exist", folder, nameStr) - continue - } - - buf, err = db.removeLocalBlockAndSequenceInfo(buf, folder, name, ef, true, &t) - if err != nil { - return err - } - - meta.removeFile(protocol.LocalDeviceID, ef) - - gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name) - if err != nil { - return err - } - buf, err = t.removeFromGlobal(gk, buf, folder, protocol.LocalDeviceID[:], name, meta) - if err != nil { - return err - } - - err = t.Delete(dk) - if err != nil { - return err - } - - if err := t.Checkpoint(); err != nil { - return err - } - } - - return t.Commit() -} - -func (db *Lowlevel) removeLocalBlockAndSequenceInfo(keyBuf, folder, name []byte, ef protocol.FileInfo, removeFromBlockListMap bool, t *readWriteTransaction) ([]byte, error) { - var err error - if len(ef.Blocks) != 0 && !ef.IsInvalid() && ef.Size > 0 { - for _, block := range ef.Blocks { - keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name) - if err != nil { - return nil, err - } - if err := t.Delete(keyBuf); err != nil { - return nil, err - } - } - if removeFromBlockListMap { - keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, ef.BlocksHash, name) - if err != nil { - return nil, err - } - if err = t.Delete(keyBuf); err != nil { - return nil, err - } - } - } - - keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo()) - if err != nil { - return nil, err - } - if err := t.Delete(keyBuf); err != nil { - return nil, err - } - l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName()) - return keyBuf, nil -} - -func (db *Lowlevel) dropFolder(folder []byte) error { - db.gcMut.RLock() - defer db.gcMut.RUnlock() - - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - - // Remove all items related to the given folder from the device->file bucket - k0, err := db.keyer.GenerateDeviceFileKey(nil, folder, nil, nil) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(k0.WithoutNameAndDevice()); err != nil { - return err - } - - // Remove all sequences related to the folder - k1, err := db.keyer.GenerateSequenceKey(k0, folder, 0) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(k1.WithoutSequence()); err != nil { - return err - } - - // Remove all items related to the given folder from the global bucket - k2, err := db.keyer.GenerateGlobalVersionKey(k1, folder, nil) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(k2.WithoutName()); err != nil { - return err - } - - // Remove all needs related to the folder - k3, err := db.keyer.GenerateNeedFileKey(k2, folder, nil) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(k3.WithoutName()); err != nil { - return err - } - - // Remove the blockmap of the folder - k4, err := db.keyer.GenerateBlockMapKey(k3, folder, nil, nil) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(k4.WithoutHashAndName()); err != nil { - return err - } - - k5, err := db.keyer.GenerateBlockListMapKey(k4, folder, nil, nil) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(k5.WithoutHashAndName()); err != nil { - return err - } - - return t.Commit() -} - -func (db *Lowlevel) dropDeviceFolder(device, folder []byte, meta *metadataTracker) error { - db.gcMut.RLock() - defer db.gcMut.RUnlock() - - t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) - if err != nil { - return err - } - defer t.close() - - key, err := db.keyer.GenerateDeviceFileKey(nil, folder, device, nil) - if err != nil { - return err - } - dbi, err := t.NewPrefixIterator(key) - if err != nil { - return err - } - defer dbi.Release() - - var gk, keyBuf []byte - for dbi.Next() { - name := db.keyer.NameFromDeviceFileKey(dbi.Key()) - gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name) - if err != nil { - return err - } - keyBuf, err = t.removeFromGlobal(gk, keyBuf, folder, device, name, meta) - if err != nil { - return err - } - if err := t.Delete(dbi.Key()); err != nil { - return err - } - if err := t.Checkpoint(); err != nil { - return err - } - } - dbi.Release() - if err := dbi.Error(); err != nil { - return err - } - - if bytes.Equal(device, protocol.LocalDeviceID[:]) { - key, err := db.keyer.GenerateBlockMapKey(nil, folder, nil, nil) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(key.WithoutHashAndName()); err != nil { - return err - } - key2, err := db.keyer.GenerateBlockListMapKey(key, folder, nil, nil) - if err != nil { - return err - } - if err := t.deleteKeyPrefix(key2.WithoutHashAndName()); err != nil { - return err - } - } - return t.Commit() -} - -func (db *Lowlevel) checkGlobals(folderStr string) (int, error) { - t, err := db.newReadWriteTransaction() - if err != nil { - return 0, err - } - defer t.close() - - folder := []byte(folderStr) - key, err := db.keyer.GenerateGlobalVersionKey(nil, folder, nil) - if err != nil { - return 0, err - } - dbi, err := t.NewPrefixIterator(key.WithoutName()) - if err != nil { - return 0, err - } - defer dbi.Release() - - fixed := 0 - var dk []byte - ro := t.readOnlyTransaction - for dbi.Next() { - var vl dbproto.VersionList - if err := proto.Unmarshal(dbi.Value(), &vl); err != nil || len(vl.Versions) == 0 { - if err := t.Delete(dbi.Key()); err != nil && !backend.IsNotFound(err) { - return 0, err - } - continue - } - - // Check the global version list for consistency. An issue in previous - // versions of goleveldb could result in reordered writes so that - // there are global entries pointing to no longer existing files. Here - // we find those and clear them out. - - name := db.keyer.NameFromGlobalVersionKey(dbi.Key()) - newVL := &dbproto.VersionList{} - var changed, changedHere bool - for _, fv := range vl.Versions { - changedHere, err = checkGlobalsFilterDevices(dk, folder, name, fv.Devices, newVL, ro) - if err != nil { - return 0, err - } - changed = changed || changedHere - - changedHere, err = checkGlobalsFilterDevices(dk, folder, name, fv.InvalidDevices, newVL, ro) - if err != nil { - return 0, err - } - changed = changed || changedHere - } - - if len(newVL.Versions) == 0 { - if err := t.Delete(dbi.Key()); err != nil && !backend.IsNotFound(err) { - return 0, err - } - fixed++ - } else if changed { - if err := t.Put(dbi.Key(), mustMarshal(newVL)); err != nil { - return 0, err - } - fixed++ - } - } - dbi.Release() - if err := dbi.Error(); err != nil { - return 0, err - } - - l.Debugf("global db check completed for %v", folder) - return fixed, t.Commit() -} - -func checkGlobalsFilterDevices(dk, folder, name []byte, devices [][]byte, vl *dbproto.VersionList, t readOnlyTransaction) (bool, error) { - var changed bool - var err error - for _, device := range devices { - dk, err = t.keyer.GenerateDeviceFileKey(dk, folder, device, name) - if err != nil { - return false, err - } - f, ok, err := t.getFileTrunc(dk, false) - if err != nil { - return false, err - } - if !ok { - changed = true - continue - } - _, _, _, _, _, _, err = vlUpdate(vl, folder, device, f, t) - if err != nil { - return false, err - } - } - return changed, nil -} - -func (db *Lowlevel) getIndexID(device, folder []byte) (protocol.IndexID, error) { - key, err := db.keyer.GenerateIndexIDKey(nil, device, folder) - if err != nil { - return 0, err - } - cur, err := db.Get(key) - if backend.IsNotFound(err) { - return 0, nil - } else if err != nil { - return 0, err - } - - var id protocol.IndexID - if err := id.Unmarshal(cur); err != nil { - return 0, nil //nolint: nilerr - } - - return id, nil -} - -func (db *Lowlevel) setIndexID(device, folder []byte, id protocol.IndexID) error { - bs, _ := id.Marshal() // marshalling can't fail - key, err := db.keyer.GenerateIndexIDKey(nil, device, folder) - if err != nil { - return err - } - return db.Put(key, bs) -} - -func (db *Lowlevel) dropFolderIndexIDs(folder []byte) error { - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - - if err := t.deleteKeyPrefixMatching([]byte{KeyTypeIndexID}, func(key []byte) bool { - keyFolder, ok := t.keyer.FolderFromIndexIDKey(key) - if !ok { - l.Debugf("Deleting IndexID with missing FolderIdx: %v", key) - return true - } - return bytes.Equal(keyFolder, folder) - }); err != nil { - return err - } - return t.Commit() -} - -func (db *Lowlevel) dropIndexIDs() error { - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - if err := t.deleteKeyPrefix([]byte{KeyTypeIndexID}); err != nil { - return err - } - return t.Commit() -} - -// dropOtherDeviceIndexIDs drops all index IDs for devices other than the -// local device. This means we will resend our indexes to all other devices, -// but they don't have to resend to us. -func (db *Lowlevel) dropOtherDeviceIndexIDs() error { - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - if err := t.deleteKeyPrefixMatching([]byte{KeyTypeIndexID}, func(key []byte) bool { - dev, _ := t.keyer.DeviceFromIndexIDKey(key) - return !bytes.Equal(dev, protocol.LocalDeviceID[:]) - }); err != nil { - return err - } - return t.Commit() -} - -func (db *Lowlevel) dropMtimes(folder []byte) error { - key, err := db.keyer.GenerateMtimesKey(nil, folder) - if err != nil { - return err - } - return db.dropPrefix(key) -} - -func (db *Lowlevel) dropFolderMeta(folder []byte) error { - key, err := db.keyer.GenerateFolderMetaKey(nil, folder) - if err != nil { - return err - } - return db.dropPrefix(key) -} - -func (db *Lowlevel) dropPrefix(prefix []byte) error { - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - - if err := t.deleteKeyPrefix(prefix); err != nil { - return err - } - return t.Commit() -} - -func (db *Lowlevel) gcRunner(ctx context.Context) error { - // Calculate the time for the next GC run. Even if we should run GC - // directly, give the system a while to get up and running and do other - // stuff first. (We might have migrations and stuff which would be - // better off running before GC.) - next := db.timeUntil(indirectGCTimeKey, db.indirectGCInterval) - if next < time.Minute { - next = time.Minute - } - - t := time.NewTimer(next) - defer t.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-t.C: - if err := db.gcIndirect(ctx); err != nil { - l.Warnln("Database indirection GC failed:", err) - } - db.recordTime(indirectGCTimeKey) - t.Reset(db.timeUntil(indirectGCTimeKey, db.indirectGCInterval)) - } - } -} - -// recordTime records the current time under the given key, affecting the -// next call to timeUntil with the same key. -func (db *Lowlevel) recordTime(key string) { - miscDB := NewMiscDataNamespace(db) - _ = miscDB.PutInt64(key, time.Now().Unix()) // error wilfully ignored -} - -// timeUntil returns how long we should wait until the next interval, or -// zero if it should happen directly. -func (db *Lowlevel) timeUntil(key string, every time.Duration) time.Duration { - miscDB := NewMiscDataNamespace(db) - lastTime, _, _ := miscDB.Int64(key) // error wilfully ignored - nextTime := time.Unix(lastTime, 0).Add(every) - sleepTime := time.Until(nextTime) - if sleepTime < 0 { - sleepTime = 0 - } - return sleepTime -} - -func (db *Lowlevel) gcIndirect(ctx context.Context) (err error) { - // The indirection GC uses bloom filters to track used block lists and - // versions. This means iterating over all items, adding their hashes to - // the filter, then iterating over the indirected items and removing - // those that don't match the filter. The filter will give false - // positives so we will keep around one percent of things that we don't - // really need (at most). - // - // Indirection GC needs to run when there are no modifications to the - // FileInfos or indirected items. - - l.Debugln("Starting database GC") - - // Create a new set of bloom filters, while holding the gcMut which - // guarantees that no other modifications are happening concurrently. - - db.gcMut.Lock() - capacity := indirectGCBloomCapacity - if db.gcKeyCount > capacity { - capacity = db.gcKeyCount - } - db.blockFilter = newBloomFilter(capacity) - db.versionFilter = newBloomFilter(capacity) - db.gcMut.Unlock() - - defer func() { - // Forget the bloom filters on the way out. - db.gcMut.Lock() - db.blockFilter = nil - db.versionFilter = nil - db.gcMut.Unlock() - }() - - var discardedBlocks, matchedBlocks, discardedVersions, matchedVersions int - - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.Release() - - // Set up the bloom filters with the initial capacity and false positive - // rate, or higher capacity if we've done this before and seen lots of - // items. For simplicity's sake we track just one count, which is the - // highest of the various indirected items. - - // Iterate the FileInfos, unmarshal the block and version hashes and - // add them to the filter. - - // This happens concurrently with normal database modifications, though - // those modifications will now also add their blocks and versions to - // the bloom filters. - - it, err := t.NewPrefixIterator([]byte{KeyTypeDevice}) - if err != nil { - return err - } - defer it.Release() - for it.Next() { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - var hashes dbproto.IndirectionHashesOnly - if err := proto.Unmarshal(it.Value(), &hashes); err != nil { - return err - } - db.recordIndirectionHashes(&hashes) - } - it.Release() - if err := it.Error(); err != nil { - return err - } - - // For the next phase we grab the GC lock again and hold it for the rest - // of the method call. Now there can't be any further modifications to - // the database or the bloom filters. - - db.gcMut.Lock() - defer db.gcMut.Unlock() - - // Only print something if the process takes more than "a moment". - logWait := make(chan struct{}) - logTimer := time.AfterFunc(10*time.Second, func() { - l.Infoln("Database GC in progress - many Syncthing operations will be unresponsive until it's finished") - close(logWait) - }) - defer func() { - if logTimer.Stop() { - return - } - <-logWait // Make sure messages are sent in order. - l.Infof("Database GC complete (discarded/remaining: %v/%v blocks, %v/%v versions)", - discardedBlocks, matchedBlocks, discardedVersions, matchedVersions) - }() - - // Iterate over block lists, removing keys with hashes that don't match - // the filter. - - it, err = t.NewPrefixIterator([]byte{KeyTypeBlockList}) - if err != nil { - return err - } - defer it.Release() - for it.Next() { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - key := blockListKey(it.Key()) - if db.blockFilter.has(key.Hash()) { - matchedBlocks++ - continue - } - if err := t.Delete(key); err != nil { - return err - } - discardedBlocks++ - } - it.Release() - if err := it.Error(); err != nil { - return err - } - - // Iterate over version lists, removing keys with hashes that don't match - // the filter. - - it, err = db.NewPrefixIterator([]byte{KeyTypeVersion}) - if err != nil { - return err - } - for it.Next() { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - key := versionKey(it.Key()) - if db.versionFilter.has(key.Hash()) { - matchedVersions++ - continue - } - if err := t.Delete(key); err != nil { - return err - } - discardedVersions++ - } - it.Release() - if err := it.Error(); err != nil { - return err - } - - // Remember the number of unique keys we kept until the next pass. - db.gcKeyCount = matchedBlocks - if matchedVersions > matchedBlocks { - db.gcKeyCount = matchedVersions - } - - if err := t.Commit(); err != nil { - return err - } - - l.Debugf("Finished GC (discarded/remaining: %v/%v blocks, %v/%v versions)", discardedBlocks, matchedBlocks, discardedVersions, matchedVersions) - - return nil -} - -func (db *Lowlevel) recordIndirectionHashesForFile(f *protocol.FileInfo) { - db.recordIndirectionHashes(&dbproto.IndirectionHashesOnly{BlocksHash: f.BlocksHash, VersionHash: f.VersionHash}) -} - -func (db *Lowlevel) recordIndirectionHashes(hs *dbproto.IndirectionHashesOnly) { - // must be called with gcMut held (at least read-held) - if db.blockFilter != nil && len(hs.BlocksHash) > 0 { - db.blockFilter.add(hs.BlocksHash) - } - if db.versionFilter != nil && len(hs.VersionHash) > 0 { - db.versionFilter.add(hs.VersionHash) - } -} - -func newBloomFilter(capacity int) *bloomFilter { - return &bloomFilter{ - f: blobloom.NewSyncOptimized(blobloom.Config{ - Capacity: uint64(capacity), - FPRate: indirectGCBloomFalsePositiveRate, - MaxBits: 8 * indirectGCBloomMaxBytes, - }), - seed: maphash.MakeSeed(), - } -} - -type bloomFilter struct { - f *blobloom.SyncFilter - seed maphash.Seed -} - -func (b *bloomFilter) add(id []byte) { b.f.Add(b.hash(id)) } -func (b *bloomFilter) has(id []byte) bool { return b.f.Has(b.hash(id)) } - -// Hash function for the bloomfilter: maphash of the SHA-256. -// -// The randomization in maphash should ensure that we get different collisions -// across runs, so colliding keys are not kept indefinitely. -func (b *bloomFilter) hash(id []byte) uint64 { - if len(id) != sha256.Size { - panic("bug: bloomFilter.hash passed something not a SHA256 hash") - } - var h maphash.Hash - h.SetSeed(b.seed) - _, _ = h.Write(id) - return h.Sum64() -} - -// checkRepair checks folder metadata and sequences for miscellaneous errors. -func (db *Lowlevel) checkRepair() error { - db.gcMut.RLock() - defer db.gcMut.RUnlock() - for _, folder := range db.ListFolders() { - if _, err := db.getMetaAndCheckGCLocked(folder); err != nil { - return err - } - } - return nil -} - -func (db *Lowlevel) getMetaAndCheck(folder string) (*metadataTracker, error) { - db.gcMut.RLock() - defer db.gcMut.RUnlock() - - return db.getMetaAndCheckGCLocked(folder) -} - -func (db *Lowlevel) getMetaAndCheckGCLocked(folder string) (*metadataTracker, error) { - fixed, err := db.checkLocalNeed([]byte(folder)) - if err != nil { - return nil, fmt.Errorf("checking local need: %w", err) - } - if fixed != 0 { - l.Infof("Repaired %d local need entries for folder %v in database", fixed, folder) - } - - fixed, err = db.checkGlobals(folder) - if err != nil { - return nil, fmt.Errorf("checking globals: %w", err) - } - if fixed != 0 { - l.Infof("Repaired %d global entries for folder %v in database", fixed, folder) - } - - oldMeta := newMetadataTracker(db.keyer, db.evLogger) - _ = oldMeta.fromDB(db, []byte(folder)) // Ignore error, it leads to index id reset too - meta, err := db.recalcMeta(folder) - if err != nil { - return nil, fmt.Errorf("recalculating metadata: %w", err) - } - - fixed, err = db.repairSequenceGCLocked(folder, meta) - if err != nil { - return nil, fmt.Errorf("repairing sequences: %w", err) - } - if fixed != 0 { - l.Infof("Repaired %d sequence entries for folder %v in database", fixed, folder) - meta, err = db.recalcMeta(folder) - if err != nil { - return nil, fmt.Errorf("recalculating metadata: %w", err) - } - } - - if err := db.checkSequencesUnchanged(folder, oldMeta, meta); err != nil { - return nil, fmt.Errorf("checking for changed sequences: %w", err) - } - - return meta, nil -} - -func (db *Lowlevel) loadMetadataTracker(folder string) (*metadataTracker, error) { - meta := newMetadataTracker(db.keyer, db.evLogger) - if err := meta.fromDB(db, []byte(folder)); err != nil { - if errors.Is(err, errMetaInconsistent) { - l.Infof("Stored folder metadata for %q is inconsistent; recalculating", folder) - } else { - l.Infof("No stored folder metadata for %q; recalculating", folder) - } - return db.getMetaAndCheck(folder) - } - - curSeq := meta.Sequence(protocol.LocalDeviceID) - if metaOK, err := db.verifyLocalSequence(curSeq, folder); err != nil { - return nil, fmt.Errorf("verifying sequences: %w", err) - } else if !metaOK { - l.Infof("Stored folder metadata for %q is out of date after crash; recalculating", folder) - return db.getMetaAndCheck(folder) - } - - if age := time.Since(meta.Created()); age > db.recheckInterval { - l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, stringutil.NiceDurationString(age)) - return db.getMetaAndCheck(folder) - } - - return meta, nil -} - -func (db *Lowlevel) recalcMeta(folderStr string) (*metadataTracker, error) { - folder := []byte(folderStr) - - meta := newMetadataTracker(db.keyer, db.evLogger) - - t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) - if err != nil { - return nil, err - } - defer t.close() - - var deviceID protocol.DeviceID - err = t.withAllFolderTruncated(folder, func(device []byte, f protocol.FileInfo) bool { - copy(deviceID[:], device) - meta.addFile(deviceID, f) - return true - }) - if err != nil { - return nil, err - } - - err = t.withGlobal(folder, nil, true, func(f protocol.FileInfo) bool { - meta.addFile(protocol.GlobalDeviceID, f) - return true - }) - if err != nil { - return nil, err - } - - meta.emptyNeeded(protocol.LocalDeviceID) - err = t.withNeed(folder, protocol.LocalDeviceID[:], true, func(f protocol.FileInfo) bool { - meta.addNeeded(protocol.LocalDeviceID, f) - return true - }) - if err != nil { - return nil, err - } - for _, device := range meta.devices() { - meta.emptyNeeded(device) - err = t.withNeed(folder, device[:], true, func(f protocol.FileInfo) bool { - meta.addNeeded(device, f) - return true - }) - if err != nil { - return nil, err - } - } - - meta.SetCreated() - if err := t.Commit(); err != nil { - return nil, err - } - return meta, nil -} - -// Verify the local sequence number from actual sequence entries. Returns -// true if it was all good, or false if a fixup was necessary. -func (db *Lowlevel) verifyLocalSequence(curSeq int64, folder string) (bool, error) { - // Walk the sequence index from the current (supposedly) highest - // sequence number and raise the alarm if we get anything. This recovers - // from the occasion where we have written sequence entries to disk but - // not yet written new metadata to disk. - // - // Note that we can have the same thing happen for remote devices but - // there it's not a problem -- we'll simply advertise a lower sequence - // number than we've actually seen and receive some duplicate updates - // and then be in sync again. - - t, err := db.newReadOnlyTransaction() - if err != nil { - return false, err - } - ok := true - if err := t.withHaveSequence([]byte(folder), curSeq+1, func(_ protocol.FileInfo) bool { - ok = false // we got something, which we should not have - return false - }); err != nil { - return false, err - } - t.close() - - return ok, nil -} - -// repairSequenceGCLocked makes sure the sequence numbers in the sequence keys -// match those in the corresponding file entries. It returns the amount of fixed -// entries. -func (db *Lowlevel) repairSequenceGCLocked(folderStr string, meta *metadataTracker) (int, error) { - t, err := db.newReadWriteTransaction(meta.CommitHook([]byte(folderStr))) - if err != nil { - return 0, err - } - defer t.close() - - fixed := 0 - - folder := []byte(folderStr) - - // First check that every file entry has a matching sequence entry - // (this was previously db schema upgrade to 9). - - dk, err := t.keyer.GenerateDeviceFileKey(nil, folder, protocol.LocalDeviceID[:], nil) - if err != nil { - return 0, err - } - it, err := t.NewPrefixIterator(dk.WithoutName()) - if err != nil { - return 0, err - } - defer it.Release() - - var sk sequenceKey - for it.Next() { - intf, err := t.unmarshalTrunc(it.Value(), false) - if err != nil { - // Delete local items with invalid indirected blocks/versions. - // They will be rescanned. - var ierr *blocksIndirectionError - if ok := errors.As(err, &ierr); ok && backend.IsNotFound(err) { - intf, err = t.unmarshalTrunc(it.Value(), true) - if err != nil { - return 0, err - } - name := []byte(intf.FileName()) - gk, err := t.keyer.GenerateGlobalVersionKey(nil, folder, name) - if err != nil { - return 0, err - } - _, err = t.removeFromGlobal(gk, nil, folder, protocol.LocalDeviceID[:], name, nil) - if err != nil { - return 0, err - } - sk, err = db.keyer.GenerateSequenceKey(sk, folder, intf.SequenceNo()) - if err != nil { - return 0, err - } - if err := t.Delete(sk); err != nil { - return 0, err - } - if err := t.Delete(it.Key()); err != nil { - return 0, err - } - } - return 0, err - } - if sk, err = t.keyer.GenerateSequenceKey(sk, folder, intf.Sequence); err != nil { - return 0, err - } - switch dk, err = t.Get(sk); { - case err != nil: - if !backend.IsNotFound(err) { - return 0, err - } - fallthrough - case !bytes.Equal(it.Key(), dk): - fixed++ - intf.Sequence = meta.nextLocalSeq() - if sk, err = t.keyer.GenerateSequenceKey(sk, folder, intf.Sequence); err != nil { - return 0, err - } - if err := t.Put(sk, it.Key()); err != nil { - return 0, err - } - if err := t.putFile(it.Key(), intf); err != nil { - return 0, err - } - } - if err := t.Checkpoint(); err != nil { - return 0, err - } - } - if err := it.Error(); err != nil { - return 0, err - } - - it.Release() - - // Secondly check there's no sequence entries pointing at incorrect things. - - sk, err = t.keyer.GenerateSequenceKey(sk, folder, 0) - if err != nil { - return 0, err - } - - it, err = t.NewPrefixIterator(sk.WithoutSequence()) - if err != nil { - return 0, err - } - defer it.Release() - - for it.Next() { - // Check that the sequence from the key matches the - // sequence in the file. - fi, ok, err := t.getFileTrunc(it.Value(), true) - if err != nil { - return 0, err - } - if ok { - if seq := t.keyer.SequenceFromSequenceKey(it.Key()); seq == fi.SequenceNo() { - continue - } - } - // Either the file is missing or has a different sequence number - fixed++ - if err := t.Delete(it.Key()); err != nil { - return 0, err - } - } - if err := it.Error(); err != nil { - return 0, err - } - - it.Release() - - return fixed, t.Commit() -} - -// Does not take care of metadata - if anything is repaired, the need count -// needs to be recalculated. -func (db *Lowlevel) checkLocalNeed(folder []byte) (int, error) { - repaired := 0 - - t, err := db.newReadWriteTransaction() - if err != nil { - return 0, err - } - defer t.close() - - key, err := t.keyer.GenerateNeedFileKey(nil, folder, nil) - if err != nil { - return 0, err - } - dbi, err := t.NewPrefixIterator(key.WithoutName()) - if err != nil { - return 0, err - } - defer dbi.Release() - - var needName string - var needDone bool - next := func() { - needDone = !dbi.Next() - if !needDone { - needName = string(t.keyer.NameFromGlobalVersionKey(dbi.Key())) - } - } - next() - itErr := t.withNeedIteratingGlobal(folder, protocol.LocalDeviceID[:], true, func(fi protocol.FileInfo) bool { - for !needDone && needName < fi.Name { - repaired++ - if err = t.Delete(dbi.Key()); err != nil && !backend.IsNotFound(err) { - return false - } - l.Debugln("check local need: removing", needName) - next() - } - if needName == fi.Name { - next() - } else { - repaired++ - key, err = t.keyer.GenerateNeedFileKey(key, folder, []byte(fi.Name)) - if err != nil { - return false - } - if err = t.Put(key, nil); err != nil { - return false - } - l.Debugln("check local need: adding", fi.Name) - } - return true - }) - if err != nil { - return 0, err - } - if itErr != nil { - return 0, itErr - } - - for !needDone { - repaired++ - if err := t.Delete(dbi.Key()); err != nil && !backend.IsNotFound(err) { - return 0, err - } - l.Debugln("check local need: removing", needName) - next() - } - - if err := dbi.Error(); err != nil { - return 0, err - } - dbi.Release() - - if err = t.Commit(); err != nil { - return 0, err - } - - return repaired, nil -} - -// checkSequencesUnchanged resets delta indexes for any device where the -// sequence changed. -func (db *Lowlevel) checkSequencesUnchanged(folder string, oldMeta, meta *metadataTracker) error { - t, err := db.newReadWriteTransaction() - if err != nil { - return err - } - defer t.close() - - var key []byte - deleteIndexID := func(devID protocol.DeviceID) error { - key, err = db.keyer.GenerateIndexIDKey(key, devID[:], []byte(folder)) - if err != nil { - return err - } - return t.Delete(key) - } - - if oldMeta.Sequence(protocol.LocalDeviceID) != meta.Sequence(protocol.LocalDeviceID) { - if err := deleteIndexID(protocol.LocalDeviceID); err != nil { - return err - } - l.Infof("Local sequence for folder %v changed while repairing - dropping delta indexes", folder) - } - - oldDevices := oldMeta.devices() - oldSequences := make(map[protocol.DeviceID]int64, len(oldDevices)) - for _, devID := range oldDevices { - oldSequences[devID] = oldMeta.Sequence(devID) - } - for _, devID := range meta.devices() { - oldSeq := oldSequences[devID] - delete(oldSequences, devID) - // A lower sequence number just means we will receive some indexes again. - if oldSeq >= meta.Sequence(devID) { - if oldSeq > meta.Sequence(devID) { - db.evLogger.Log(events.Failure, "lower remote sequence after recalculating metadata") - } - continue - } - db.evLogger.Log(events.Failure, "higher remote sequence after recalculating metadata") - if err := deleteIndexID(devID); err != nil { - return err - } - l.Infof("Sequence of device %v for folder %v changed while repairing - dropping delta indexes", devID.Short(), folder) - } - for devID := range oldSequences { - if err := deleteIndexID(devID); err != nil { - return err - } - l.Debugf("Removed indexID of device %v for folder %v which isn't present anymore", devID.Short(), folder) - } - - return t.Commit() -} - -func (db *Lowlevel) needsRepairPath() string { - path := db.Location() - if path == "" { - return "" - } - if path[len(path)-1] == fs.PathSeparator { - path = path[:len(path)-1] - } - return path + needsRepairSuffix -} - -func (db *Lowlevel) checkErrorForRepair(err error) { - if errors.Is(err, errEntryFromGlobalMissing) || errors.Is(err, errEmptyGlobal) { - // Inconsistency error, mark db for repair on next start. - if path := db.needsRepairPath(); path != "" { - if fd, err := os.Create(path); err == nil { - fd.Close() - } - } - } -} - -func (db *Lowlevel) handleFailure(err error) { - db.checkErrorForRepair(err) - if shouldReportFailure(err) { - db.evLogger.Log(events.Failure, err.Error()) - } -} - -var ldbPathRe = regexp.MustCompile(`(open|write|read) .+[\\/].+[\\/]index[^\\/]+[\\/][^\\/]+: `) - -func shouldReportFailure(err error) bool { - return !ldbPathRe.MatchString(err.Error()) -} diff --git a/lib/db/meta.go b/lib/db/meta.go deleted file mode 100644 index fd7ec1fce..000000000 --- a/lib/db/meta.go +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright (C) 2017 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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "errors" - "fmt" - "math/bits" - "time" - - "google.golang.org/protobuf/proto" - - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/events" - "github.com/syncthing/syncthing/lib/protocol" - "github.com/syncthing/syncthing/lib/sync" -) - -var errMetaInconsistent = errors.New("inconsistent counts detected") - -type countsMap struct { - counts CountsSet - indexes map[metaKey]int // device ID + local flags -> index in counts -} - -// metadataTracker keeps metadata on a per device, per local flag basis. -type metadataTracker struct { - keyer keyer - countsMap - mut sync.RWMutex - dirty bool - evLogger events.Logger -} - -type metaKey struct { - dev protocol.DeviceID - flag uint32 -} - -const needFlag uint32 = 1 << 31 // Last bit, as early ones are local flags - -func newMetadataTracker(keyer keyer, evLogger events.Logger) *metadataTracker { - return &metadataTracker{ - keyer: keyer, - mut: sync.NewRWMutex(), - countsMap: countsMap{ - indexes: make(map[metaKey]int), - }, - evLogger: evLogger, - } -} - -// Unmarshal loads a metadataTracker from the corresponding protobuf -// representation -func (m *metadataTracker) Unmarshal(bs []byte) error { - var dbc dbproto.CountsSet - if err := proto.Unmarshal(bs, &dbc); err != nil { - return err - } - m.counts.Created = dbc.Created - m.counts.Counts = make([]Counts, len(dbc.Counts)) - for i, c := range dbc.Counts { - m.counts.Counts[i] = countsFromWire(c) - } - - // Initialize the index map - m.indexes = make(map[metaKey]int) - for i, c := range m.counts.Counts { - m.indexes[metaKey{c.DeviceID, c.LocalFlags}] = i - } - return nil -} - -// protoMarshal returns the protobuf representation of the metadataTracker. -// Must be called with the read lock held. -func (m *metadataTracker) protoMarshal() ([]byte, error) { - dbc := &dbproto.CountsSet{ - Counts: make([]*dbproto.Counts, len(m.counts.Counts)), - Created: m.counts.Created, - } - for i, c := range m.counts.Counts { - dbc.Counts[i] = c.toWire() - } - return proto.Marshal(dbc) -} - -func (m *metadataTracker) CommitHook(folder []byte) backend.CommitHook { - return func(t backend.WriteTransaction) error { - return m.toDB(t, folder) - } -} - -// toDB saves the marshalled metadataTracker to the given db, under the key -// corresponding to the given folder -func (m *metadataTracker) toDB(t backend.WriteTransaction, folder []byte) error { - key, err := m.keyer.GenerateFolderMetaKey(nil, folder) - if err != nil { - return err - } - - m.mut.RLock() - defer m.mut.RUnlock() - - if !m.dirty { - return nil - } - - bs, err := m.protoMarshal() - if err != nil { - return err - } - err = t.Put(key, bs) - if err == nil { - m.dirty = false - } - - return err -} - -// fromDB initializes the metadataTracker from the marshalled data found in -// the database under the key corresponding to the given folder -func (m *metadataTracker) fromDB(db *Lowlevel, folder []byte) error { - key, err := db.keyer.GenerateFolderMetaKey(nil, folder) - if err != nil { - return err - } - bs, err := db.Get(key) - if err != nil { - return err - } - if err = m.Unmarshal(bs); err != nil { - return err - } - if m.counts.Created == 0 { - return errMetaInconsistent - } - return nil -} - -// countsPtr returns a pointer to the corresponding Counts struct, if -// necessary allocating one in the process -func (m *metadataTracker) countsPtr(dev protocol.DeviceID, flag uint32) *Counts { - // must be called with the mutex held - - if bits.OnesCount32(flag) > 1 { - panic("incorrect usage: set at most one bit in flag") - } - - key := metaKey{dev, flag} - idx, ok := m.indexes[key] - if !ok { - idx = len(m.counts.Counts) - m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev, LocalFlags: flag}) - m.indexes[key] = idx - // Need bucket must be initialized when a device first occurs in - // the metadatatracker, even if there's no change to the need - // bucket itself. - nkey := metaKey{dev, needFlag} - if _, ok := m.indexes[nkey]; !ok { - // Initially a new device needs everything, except deletes - nidx := len(m.counts.Counts) - m.counts.Counts = append(m.counts.Counts, m.allNeededCounts(dev)) - m.indexes[nkey] = nidx - } - } - return &m.counts.Counts[idx] -} - -// allNeeded makes sure there is a counts in case the device needs everything. -func (m *countsMap) allNeededCounts(dev protocol.DeviceID) Counts { - var counts Counts - if idx, ok := m.indexes[metaKey{protocol.GlobalDeviceID, 0}]; ok { - counts = m.counts.Counts[idx] - counts.Deleted = 0 // Don't need deletes if having nothing - } - counts.DeviceID = dev - counts.LocalFlags = needFlag - return counts -} - -// addFile adds a file to the counts, adjusting the sequence number as -// appropriate -func (m *metadataTracker) addFile(dev protocol.DeviceID, f protocol.FileInfo) { - m.mut.Lock() - defer m.mut.Unlock() - - m.updateSeqLocked(dev, f) - - m.updateFileLocked(dev, f, m.addFileLocked) -} - -func (m *metadataTracker) updateFileLocked(dev protocol.DeviceID, f protocol.FileInfo, fn func(protocol.DeviceID, uint32, protocol.FileInfo)) { - m.dirty = true - - if f.IsInvalid() && (f.FileLocalFlags() == 0 || dev == protocol.GlobalDeviceID) { - // This is a remote invalid file or concern the global state. - // In either case invalid files are not accounted. - return - } - - if flags := f.FileLocalFlags(); flags == 0 { - // Account regular files in the zero-flags bucket. - fn(dev, 0, f) - } else { - // Account in flag specific buckets. - eachFlagBit(flags, func(flag uint32) { - fn(dev, flag, f) - }) - } -} - -// emptyNeeded ensures that there is a need count for the given device and that it is empty. -func (m *metadataTracker) emptyNeeded(dev protocol.DeviceID) { - m.mut.Lock() - defer m.mut.Unlock() - - m.dirty = true - - empty := Counts{ - DeviceID: dev, - LocalFlags: needFlag, - } - key := metaKey{dev, needFlag} - if idx, ok := m.indexes[key]; ok { - m.counts.Counts[idx] = empty - return - } - m.indexes[key] = len(m.counts.Counts) - m.counts.Counts = append(m.counts.Counts, empty) -} - -// addNeeded adds a file to the needed counts -func (m *metadataTracker) addNeeded(dev protocol.DeviceID, f protocol.FileInfo) { - m.mut.Lock() - defer m.mut.Unlock() - - m.dirty = true - - m.addFileLocked(dev, needFlag, f) -} - -func (m *metadataTracker) Sequence(dev protocol.DeviceID) int64 { - m.mut.Lock() - defer m.mut.Unlock() - return m.countsPtr(dev, 0).Sequence -} - -func (m *metadataTracker) updateSeqLocked(dev protocol.DeviceID, f protocol.FileInfo) { - if dev == protocol.GlobalDeviceID { - return - } - if cp := m.countsPtr(dev, 0); f.SequenceNo() > cp.Sequence { - cp.Sequence = f.SequenceNo() - } -} - -func (m *metadataTracker) addFileLocked(dev protocol.DeviceID, flag uint32, f protocol.FileInfo) { - cp := m.countsPtr(dev, flag) - - switch { - case f.IsDeleted(): - cp.Deleted++ - case f.IsDirectory() && !f.IsSymlink(): - cp.Directories++ - case f.IsSymlink(): - cp.Symlinks++ - default: - cp.Files++ - } - cp.Bytes += f.FileSize() -} - -// removeFile removes a file from the counts -func (m *metadataTracker) removeFile(dev protocol.DeviceID, f protocol.FileInfo) { - m.mut.Lock() - defer m.mut.Unlock() - - m.updateFileLocked(dev, f, m.removeFileLocked) -} - -// removeNeeded removes a file from the needed counts -func (m *metadataTracker) removeNeeded(dev protocol.DeviceID, f protocol.FileInfo) { - m.mut.Lock() - defer m.mut.Unlock() - - m.dirty = true - - m.removeFileLocked(dev, needFlag, f) -} - -func (m *metadataTracker) removeFileLocked(dev protocol.DeviceID, flag uint32, f protocol.FileInfo) { - cp := m.countsPtr(dev, flag) - - switch { - case f.IsDeleted(): - cp.Deleted-- - case f.IsDirectory() && !f.IsSymlink(): - cp.Directories-- - case f.IsSymlink(): - cp.Symlinks-- - default: - cp.Files-- - } - cp.Bytes -= f.FileSize() - - // If we've run into an impossible situation, correct it for now and set - // the created timestamp to zero. Next time we start up the metadata - // will be seen as infinitely old and recalculated from scratch. - if cp.Deleted < 0 { - m.evLogger.Log(events.Failure, fmt.Sprintf("meta deleted count for flag 0x%x dropped below zero", flag)) - cp.Deleted = 0 - m.counts.Created = 0 - } - if cp.Files < 0 { - m.evLogger.Log(events.Failure, fmt.Sprintf("meta files count for flag 0x%x dropped below zero", flag)) - cp.Files = 0 - m.counts.Created = 0 - } - if cp.Directories < 0 { - m.evLogger.Log(events.Failure, fmt.Sprintf("meta directories count for flag 0x%x dropped below zero", flag)) - cp.Directories = 0 - m.counts.Created = 0 - } - if cp.Symlinks < 0 { - m.evLogger.Log(events.Failure, fmt.Sprintf("meta deleted count for flag 0x%x dropped below zero", flag)) - cp.Symlinks = 0 - m.counts.Created = 0 - } -} - -// resetAll resets all metadata for the given device -func (m *metadataTracker) resetAll(dev protocol.DeviceID) { - m.mut.Lock() - m.dirty = true - for i, c := range m.counts.Counts { - if c.DeviceID == dev { - if c.LocalFlags != needFlag { - m.counts.Counts[i] = Counts{ - DeviceID: c.DeviceID, - LocalFlags: c.LocalFlags, - } - } else { - m.counts.Counts[i] = m.allNeededCounts(dev) - } - } - } - m.mut.Unlock() -} - -// resetCounts resets the file, dir, etc. counters, while retaining the -// sequence number -func (m *metadataTracker) resetCounts(dev protocol.DeviceID) { - m.mut.Lock() - m.dirty = true - - for i, c := range m.counts.Counts { - if c.DeviceID == dev { - m.counts.Counts[i] = Counts{ - DeviceID: c.DeviceID, - Sequence: c.Sequence, - LocalFlags: c.LocalFlags, - } - } - } - - m.mut.Unlock() -} - -func (m *countsMap) Counts(dev protocol.DeviceID, flag uint32) Counts { - if bits.OnesCount32(flag) > 1 { - panic("incorrect usage: set at most one bit in flag") - } - - idx, ok := m.indexes[metaKey{dev, flag}] - if !ok { - if flag == needFlag { - // If there's nothing about a device in the index yet, - // it needs everything. - return m.allNeededCounts(dev) - } - return Counts{} - } - - return m.counts.Counts[idx] -} - -// Snapshot returns a copy of the metadata for reading. -func (m *metadataTracker) Snapshot() *countsMap { - m.mut.RLock() - defer m.mut.RUnlock() - - c := &countsMap{ - counts: CountsSet{ - Counts: make([]Counts, len(m.counts.Counts)), - Created: m.counts.Created, - }, - indexes: make(map[metaKey]int, len(m.indexes)), - } - for k, v := range m.indexes { - c.indexes[k] = v - } - copy(c.counts.Counts, m.counts.Counts) - - return c -} - -// nextLocalSeq allocates a new local sequence number -func (m *metadataTracker) nextLocalSeq() int64 { - m.mut.Lock() - defer m.mut.Unlock() - - c := m.countsPtr(protocol.LocalDeviceID, 0) - c.Sequence++ - return c.Sequence -} - -// devices returns the list of devices tracked, excluding the local device -// (which we don't know the ID of) -func (m *metadataTracker) devices() []protocol.DeviceID { - m.mut.RLock() - defer m.mut.RUnlock() - return m.countsMap.devices() -} - -func (m *countsMap) devices() []protocol.DeviceID { - devs := make([]protocol.DeviceID, 0, len(m.counts.Counts)) - - for _, dev := range m.counts.Counts { - if dev.Sequence > 0 { - if dev.DeviceID == protocol.GlobalDeviceID || dev.DeviceID == protocol.LocalDeviceID { - continue - } - devs = append(devs, dev.DeviceID) - } - } - - return devs -} - -func (m *metadataTracker) Created() time.Time { - m.mut.RLock() - defer m.mut.RUnlock() - return time.Unix(0, m.counts.Created) -} - -func (m *metadataTracker) SetCreated() { - m.mut.Lock() - m.counts.Created = time.Now().UnixNano() - m.dirty = true - m.mut.Unlock() -} - -// eachFlagBit calls the function once for every bit that is set in flags -func eachFlagBit(flags uint32, fn func(flag uint32)) { - // Test each bit from the right, as long as there are bits left in the - // flag set. Clear any bits found and stop testing as soon as there are - // no more bits set. - - currentBit := uint32(1 << 0) - for flags != 0 { - if flags¤tBit != 0 { - fn(currentBit) - flags &^= currentBit - } - currentBit <<= 1 - } -} diff --git a/lib/db/meta_test.go b/lib/db/meta_test.go deleted file mode 100644 index e68257a70..000000000 --- a/lib/db/meta_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "math/bits" - "sort" - "testing" - - "github.com/syncthing/syncthing/lib/events" - "github.com/syncthing/syncthing/lib/protocol" -) - -func TestEachFlagBit(t *testing.T) { - cases := []struct { - flags uint32 - iterations int - }{ - {0, 0}, - {1<<0 | 1<<3, 2}, - {1 << 0, 1}, - {1 << 31, 1}, - {1<<10 | 1<<20 | 1<<30, 3}, - } - - for _, tc := range cases { - var flags uint32 - iterations := 0 - - eachFlagBit(tc.flags, func(f uint32) { - iterations++ - flags |= f - if bits.OnesCount32(f) != 1 { - t.Error("expected exactly one bit to be set in every call") - } - }) - - if flags != tc.flags { - t.Errorf("expected 0x%x flags, got 0x%x", tc.flags, flags) - } - if iterations != tc.iterations { - t.Errorf("expected %d iterations, got %d", tc.iterations, iterations) - } - } -} - -func TestMetaDevices(t *testing.T) { - d1 := protocol.DeviceID{1} - d2 := protocol.DeviceID{2} - meta := newMetadataTracker(nil, events.NoopLogger) - - meta.addFile(d1, protocol.FileInfo{Sequence: 1}) - meta.addFile(d1, protocol.FileInfo{Sequence: 2, LocalFlags: 1}) - meta.addFile(d2, protocol.FileInfo{Sequence: 1}) - meta.addFile(d2, protocol.FileInfo{Sequence: 2, LocalFlags: 2}) - meta.addFile(protocol.LocalDeviceID, protocol.FileInfo{Sequence: 1}) - - // There are five device/flags combos - if l := len(meta.counts.Counts); l < 5 { - t.Error("expected at least five buckets, not", l) - } - - // There are only two non-local devices - devs := meta.devices() - if l := len(devs); l != 2 { - t.Fatal("expected two devices, not", l) - } - - // Check that we got the two devices we expect - sort.Slice(devs, func(a, b int) bool { - return devs[a].Compare(devs[b]) == -1 - }) - if devs[0] != d1 { - t.Error("first device should be d1") - } - if devs[1] != d2 { - t.Error("second device should be d2") - } -} - -func TestMetaSequences(t *testing.T) { - d1 := protocol.DeviceID{1} - meta := newMetadataTracker(nil, events.NoopLogger) - - meta.addFile(d1, protocol.FileInfo{Sequence: 1}) - meta.addFile(d1, protocol.FileInfo{Sequence: 2, RawInvalid: true}) - meta.addFile(d1, protocol.FileInfo{Sequence: 3}) - meta.addFile(d1, protocol.FileInfo{Sequence: 4, RawInvalid: true}) - meta.addFile(protocol.LocalDeviceID, protocol.FileInfo{Sequence: 1}) - meta.addFile(protocol.LocalDeviceID, protocol.FileInfo{Sequence: 2}) - meta.addFile(protocol.LocalDeviceID, protocol.FileInfo{Sequence: 3, LocalFlags: 1}) - meta.addFile(protocol.LocalDeviceID, protocol.FileInfo{Sequence: 4, LocalFlags: 2}) - - if seq := meta.Sequence(d1); seq != 4 { - t.Error("sequence of first device should be 4, not", seq) - } - if seq := meta.Sequence(protocol.LocalDeviceID); seq != 4 { - t.Error("sequence of first device should be 4, not", seq) - } -} - -func TestRecalcMeta(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - // Add some files - s1 := newFileSet(t, "test", ldb) - files := []protocol.FileInfo{ - {Name: "a", Size: 1000}, - {Name: "b", Size: 2000}, - } - s1.Update(protocol.LocalDeviceID, files) - - // Verify local/global size - snap := snapshot(t, s1) - ls := snap.LocalSize() - gs := snap.GlobalSize() - snap.Release() - if ls.Bytes != 3000 { - t.Fatalf("Wrong initial local byte count, %d != 3000", ls.Bytes) - } - if gs.Bytes != 3000 { - t.Fatalf("Wrong initial global byte count, %d != 3000", gs.Bytes) - } - - // Reach into the database to make the metadata tracker intentionally - // wrong and out of date - curSeq := s1.meta.Sequence(protocol.LocalDeviceID) - tran, err := ldb.newReadWriteTransaction() - if err != nil { - t.Fatal(err) - } - s1.meta.mut.Lock() - s1.meta.countsPtr(protocol.LocalDeviceID, 0).Sequence = curSeq - 1 // too low - s1.meta.countsPtr(protocol.LocalDeviceID, 0).Bytes = 1234 // wrong - s1.meta.countsPtr(protocol.GlobalDeviceID, 0).Bytes = 1234 // wrong - s1.meta.dirty = true - s1.meta.mut.Unlock() - if err := s1.meta.toDB(tran, []byte("test")); err != nil { - t.Fatal(err) - } - if err := tran.Commit(); err != nil { - t.Fatal(err) - } - - // Verify that our bad data "took" - snap = snapshot(t, s1) - ls = snap.LocalSize() - gs = snap.GlobalSize() - snap.Release() - if ls.Bytes != 1234 { - t.Fatalf("Wrong changed local byte count, %d != 1234", ls.Bytes) - } - if gs.Bytes != 1234 { - t.Fatalf("Wrong changed global byte count, %d != 1234", gs.Bytes) - } - - // Create a new fileset, which will realize the inconsistency and recalculate - s2 := newFileSet(t, "test", ldb) - - // Verify local/global size - snap = snapshot(t, s2) - ls = snap.LocalSize() - gs = snap.GlobalSize() - snap.Release() - if ls.Bytes != 3000 { - t.Fatalf("Wrong fixed local byte count, %d != 3000", ls.Bytes) - } - if gs.Bytes != 3000 { - t.Fatalf("Wrong fixed global byte count, %d != 3000", gs.Bytes) - } -} - -func TestMetaKeyCollisions(t *testing.T) { - if protocol.LocalAllFlags&needFlag != 0 { - t.Error("Collision between need flag and protocol local file flags") - } -} diff --git a/lib/db/namespaced.go b/lib/db/namespaced.go deleted file mode 100644 index ec7f3298c..000000000 --- a/lib/db/namespaced.go +++ /dev/null @@ -1,156 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "encoding/binary" - "time" - - "github.com/syncthing/syncthing/lib/db/backend" -) - -// NamespacedKV is a simple key-value store using a specific namespace within -// a leveldb. -type NamespacedKV struct { - db backend.Backend - prefix string -} - -// NewNamespacedKV returns a new NamespacedKV that lives in the namespace -// specified by the prefix. -func NewNamespacedKV(db backend.Backend, prefix string) *NamespacedKV { - return &NamespacedKV{ - db: db, - prefix: prefix, - } -} - -// PutInt64 stores a new int64. Any existing value (even if of another type) -// is overwritten. -func (n *NamespacedKV) PutInt64(key string, val int64) error { - var valBs [8]byte - binary.BigEndian.PutUint64(valBs[:], uint64(val)) - return n.db.Put(n.prefixedKey(key), valBs[:]) -} - -// Int64 returns the stored value interpreted as an int64 and a boolean that -// is false if no value was stored at the key. -func (n *NamespacedKV) Int64(key string) (int64, bool, error) { - valBs, err := n.db.Get(n.prefixedKey(key)) - if err != nil { - return 0, false, filterNotFound(err) - } - val := binary.BigEndian.Uint64(valBs) - return int64(val), true, nil -} - -// PutTime stores a new time.Time. Any existing value (even if of another -// type) is overwritten. -func (n *NamespacedKV) PutTime(key string, val time.Time) error { - valBs, _ := val.MarshalBinary() // never returns an error - return n.db.Put(n.prefixedKey(key), valBs) -} - -// Time returns the stored value interpreted as a time.Time and a boolean -// that is false if no value was stored at the key. -func (n NamespacedKV) Time(key string) (time.Time, bool, error) { - var t time.Time - valBs, err := n.db.Get(n.prefixedKey(key)) - if err != nil { - return t, false, filterNotFound(err) - } - err = t.UnmarshalBinary(valBs) - return t, err == nil, err -} - -// PutString stores a new string. Any existing value (even if of another type) -// is overwritten. -func (n *NamespacedKV) PutString(key, val string) error { - return n.db.Put(n.prefixedKey(key), []byte(val)) -} - -// String returns the stored value interpreted as a string and a boolean that -// is false if no value was stored at the key. -func (n NamespacedKV) String(key string) (string, bool, error) { - valBs, err := n.db.Get(n.prefixedKey(key)) - if err != nil { - return "", false, filterNotFound(err) - } - return string(valBs), true, nil -} - -// PutBytes stores a new byte slice. Any existing value (even if of another type) -// is overwritten. -func (n *NamespacedKV) PutBytes(key string, val []byte) error { - return n.db.Put(n.prefixedKey(key), val) -} - -// Bytes returns the stored value as a raw byte slice and a boolean that -// is false if no value was stored at the key. -func (n NamespacedKV) Bytes(key string) ([]byte, bool, error) { - valBs, err := n.db.Get(n.prefixedKey(key)) - if err != nil { - return nil, false, filterNotFound(err) - } - return valBs, true, nil -} - -// PutBool stores a new boolean. Any existing value (even if of another type) -// is overwritten. -func (n *NamespacedKV) PutBool(key string, val bool) error { - if val { - return n.db.Put(n.prefixedKey(key), []byte{0x0}) - } - return n.db.Put(n.prefixedKey(key), []byte{0x1}) -} - -// Bool returns the stored value as a boolean and a boolean that -// is false if no value was stored at the key. -func (n NamespacedKV) Bool(key string) (bool, bool, error) { - valBs, err := n.db.Get(n.prefixedKey(key)) - if err != nil { - return false, false, filterNotFound(err) - } - return valBs[0] == 0x0, true, nil -} - -// Delete deletes the specified key. It is allowed to delete a nonexistent -// key. -func (n NamespacedKV) Delete(key string) error { - return n.db.Delete(n.prefixedKey(key)) -} - -func (n NamespacedKV) prefixedKey(key string) []byte { - return []byte(n.prefix + key) -} - -// Well known namespaces that can be instantiated without knowing the key -// details. - -// NewDeviceStatisticsNamespace creates a KV namespace for device statistics -// for the given device. -func NewDeviceStatisticsNamespace(db backend.Backend, device string) *NamespacedKV { - return NewNamespacedKV(db, string(KeyTypeDeviceStatistic)+device) -} - -// NewFolderStatisticsNamespace creates a KV namespace for folder statistics -// for the given folder. -func NewFolderStatisticsNamespace(db backend.Backend, folder string) *NamespacedKV { - return NewNamespacedKV(db, string(KeyTypeFolderStatistic)+folder) -} - -// NewMiscDataNamespace creates a KV namespace for miscellaneous metadata. -func NewMiscDataNamespace(db backend.Backend) *NamespacedKV { - return NewNamespacedKV(db, string(KeyTypeMiscData)) -} - -func filterNotFound(err error) error { - if backend.IsNotFound(err) { - return nil - } - return err -} diff --git a/lib/db/namespaced_test.go b/lib/db/namespaced_test.go deleted file mode 100644 index bf012d5f3..000000000 --- a/lib/db/namespaced_test.go +++ /dev/null @@ -1,177 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "testing" - "time" -) - -func TestNamespacedInt(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - n1 := NewNamespacedKV(ldb, "foo") - n2 := NewNamespacedKV(ldb, "bar") - - // Key is missing to start with - - if v, ok, err := n1.Int64("test"); err != nil { - t.Error("Unexpected error:", err) - } else if v != 0 || ok { - t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok) - } - - if err := n1.PutInt64("test", 42); err != nil { - t.Fatal(err) - } - - // It should now exist in n1 - - if v, ok, err := n1.Int64("test"); err != nil { - t.Error("Unexpected error:", err) - } else if v != 42 || !ok { - t.Errorf("Incorrect return v %v != 42 || ok %v != true", v, ok) - } - - // ... but not in n2, which is in a different namespace - - if v, ok, err := n2.Int64("test"); err != nil { - t.Error("Unexpected error:", err) - } else if v != 0 || ok { - t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok) - } - - if err := n1.Delete("test"); err != nil { - t.Fatal(err) - } - - // It should no longer exist - - if v, ok, err := n1.Int64("test"); err != nil { - t.Error("Unexpected error:", err) - } else if v != 0 || ok { - t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok) - } -} - -func TestNamespacedTime(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - n1 := NewNamespacedKV(ldb, "foo") - - if v, ok, err := n1.Time("test"); err != nil { - t.Error("Unexpected error:", err) - } else if !v.IsZero() || ok { - t.Errorf("Incorrect return v %v != %v || ok %v != false", v, time.Time{}, ok) - } - - now := time.Now() - if err := n1.PutTime("test", now); err != nil { - t.Fatal(err) - } - - if v, ok, err := n1.Time("test"); err != nil { - t.Error("Unexpected error:", err) - } else if !v.Equal(now) || !ok { - t.Errorf("Incorrect return v %v != %v || ok %v != true", v, now, ok) - } -} - -func TestNamespacedString(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - n1 := NewNamespacedKV(ldb, "foo") - - if v, ok, err := n1.String("test"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "" || ok { - t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok) - } - - if err := n1.PutString("test", "yo"); err != nil { - t.Fatal(err) - } - - if v, ok, err := n1.String("test"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "yo" || !ok { - t.Errorf("Incorrect return v %q != \"yo\" || ok %v != true", v, ok) - } -} - -func TestNamespacedReset(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - n1 := NewNamespacedKV(ldb, "foo") - - if err := n1.PutString("test1", "yo1"); err != nil { - t.Fatal(err) - } - if err := n1.PutString("test2", "yo2"); err != nil { - t.Fatal(err) - } - if err := n1.PutString("test3", "yo3"); err != nil { - t.Fatal(err) - } - - if v, ok, err := n1.String("test1"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "yo1" || !ok { - t.Errorf("Incorrect return v %q != \"yo1\" || ok %v != true", v, ok) - } - if v, ok, err := n1.String("test2"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "yo2" || !ok { - t.Errorf("Incorrect return v %q != \"yo2\" || ok %v != true", v, ok) - } - if v, ok, err := n1.String("test3"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "yo3" || !ok { - t.Errorf("Incorrect return v %q != \"yo3\" || ok %v != true", v, ok) - } - - reset(n1) - - if v, ok, err := n1.String("test1"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "" || ok { - t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok) - } - if v, ok, err := n1.String("test2"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "" || ok { - t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok) - } - if v, ok, err := n1.String("test3"); err != nil { - t.Error("Unexpected error:", err) - } else if v != "" || ok { - t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok) - } -} - -// reset removes all entries in this namespace. -func reset(n *NamespacedKV) { - tr, err := n.db.NewWriteTransaction() - if err != nil { - return - } - defer tr.Release() - - it, err := tr.NewPrefixIterator([]byte(n.prefix)) - if err != nil { - return - } - for it.Next() { - _ = tr.Delete(it.Key()) - } - it.Release() - _ = tr.Commit() -} diff --git a/lib/db/schemaupdater.go b/lib/db/schemaupdater.go deleted file mode 100644 index 3a9c4abb2..000000000 --- a/lib/db/schemaupdater.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "fmt" - - "google.golang.org/protobuf/proto" - - "github.com/syncthing/syncthing/internal/gen/bep" - "github.com/syncthing/syncthing/lib/protocol" -) - -// dbMigrationVersion is for migrations that do not change the schema and thus -// do not put restrictions on downgrades (e.g. for repairs after a bugfix). -const ( - dbVersion = 14 - dbMigrationVersion = 20 - dbMinSyncthingVersion = "v1.9.0" -) - -type migration struct { - schemaVersion int64 - migrationVersion int64 - minSyncthingVersion string - migration func(prevSchema int) error -} - -type databaseDowngradeError struct { - minSyncthingVersion string -} - -func (e *databaseDowngradeError) Error() string { - if e.minSyncthingVersion == "" { - return "newer Syncthing required" - } - return fmt.Sprintf("Syncthing %s required", e.minSyncthingVersion) -} - -// UpdateSchema updates a possibly outdated database to the current schema and -// also does repairs where necessary. -func UpdateSchema(db *Lowlevel) error { - updater := &schemaUpdater{db} - return updater.updateSchema() -} - -type schemaUpdater struct { - *Lowlevel -} - -func (db *schemaUpdater) updateSchema() error { - // Updating the schema can touch any and all parts of the database. Make - // sure we do not run GC concurrently with schema migrations. - db.gcMut.Lock() - defer db.gcMut.Unlock() - - miscDB := NewMiscDataNamespace(db.Lowlevel) - prevVersion, _, err := miscDB.Int64("dbVersion") - if err != nil { - return err - } - - if prevVersion > 0 && prevVersion < 14 { - // This is a database version that is too old to be upgraded directly. - // The user will have to upgrade to an older version first. - return fmt.Errorf("database version %d is too old to be upgraded directly; step via Syncthing v1.27.0 to upgrade", prevVersion) - } - - if prevVersion > dbVersion { - err := &databaseDowngradeError{} - if minSyncthingVersion, ok, dbErr := miscDB.String("dbMinSyncthingVersion"); dbErr != nil { - return dbErr - } else if ok { - err.minSyncthingVersion = minSyncthingVersion - } - return err - } - - prevMigration, _, err := miscDB.Int64("dbMigrationVersion") - if err != nil { - return err - } - // Cover versions before adding `dbMigrationVersion` (== 0) and possible future weirdness. - if prevMigration < prevVersion { - prevMigration = prevVersion - } - - if prevVersion == dbVersion && prevMigration >= dbMigrationVersion { - return nil - } - - migrations := []migration{ - {14, 14, "v1.9.0", db.updateSchemaTo14}, - {14, 16, "v1.9.0", db.checkRepairMigration}, - {14, 17, "v1.9.0", db.migration17}, - {14, 19, "v1.9.0", db.dropAllIndexIDsMigration}, - {14, 20, "v1.9.0", db.dropOutgoingIndexIDsMigration}, - } - - for _, m := range migrations { - if prevMigration < m.migrationVersion { - l.Infof("Running database migration %d...", m.migrationVersion) - if err := m.migration(int(prevVersion)); err != nil { - return fmt.Errorf("failed to do migration %v: %w", m.migrationVersion, err) - } - if err := db.writeVersions(m, miscDB); err != nil { - return fmt.Errorf("failed to write versions after migration %v: %w", m.migrationVersion, err) - } - } - } - - if err := db.writeVersions(migration{ - schemaVersion: dbVersion, - migrationVersion: dbMigrationVersion, - minSyncthingVersion: dbMinSyncthingVersion, - }, miscDB); err != nil { - return fmt.Errorf("failed to write versions after migrations: %w", err) - } - - l.Infoln("Compacting database after migration...") - return db.Compact() -} - -func (*schemaUpdater) writeVersions(m migration, miscDB *NamespacedKV) error { - if err := miscDB.PutInt64("dbVersion", m.schemaVersion); err != nil { - return err - } - if err := miscDB.PutString("dbMinSyncthingVersion", m.minSyncthingVersion); err != nil { - return err - } - if err := miscDB.PutInt64("dbMigrationVersion", m.migrationVersion); err != nil { - return err - } - return nil -} - -func (db *schemaUpdater) updateSchemaTo14(_ int) error { - // Checks for missing blocks and marks those entries as requiring a - // rehash/being invalid. The db is checked/repaired afterwards, i.e. - // no care is taken to get metadata and sequences right. - // If the corresponding files changed on disk compared to the global - // version, this will cause a conflict. - - var key, gk []byte - for _, folderStr := range db.ListFolders() { - folder := []byte(folderStr) - meta := newMetadataTracker(db.keyer, db.evLogger) - meta.counts.Created = 0 // Recalculate metadata afterwards - - t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) - if err != nil { - return err - } - defer t.close() - - key, err = t.keyer.GenerateDeviceFileKey(key, folder, protocol.LocalDeviceID[:], nil) - if err != nil { - return err - } - it, err := t.NewPrefixIterator(key) - if err != nil { - return err - } - defer it.Release() - for it.Next() { - var bepf bep.FileInfo - if err := proto.Unmarshal(it.Value(), &bepf); err != nil { - return err - } - fi := protocol.FileInfoFromDB(&bepf) - if len(fi.Blocks) > 0 || len(fi.BlocksHash) == 0 { - continue - } - key = t.keyer.GenerateBlockListKey(key, fi.BlocksHash) - _, err := t.Get(key) - if err == nil { - continue - } - - fi.SetMustRescan() - if err = t.putFile(it.Key(), fi); err != nil { - return err - } - - gk, err = t.keyer.GenerateGlobalVersionKey(gk, folder, []byte(fi.Name)) - if err != nil { - return err - } - key, err = t.updateGlobal(gk, key, folder, protocol.LocalDeviceID[:], fi, meta) - if err != nil { - return err - } - } - it.Release() - - if err = t.Commit(); err != nil { - return err - } - t.close() - } - - return nil -} - -func (db *schemaUpdater) checkRepairMigration(_ int) error { - for _, folder := range db.ListFolders() { - _, err := db.getMetaAndCheckGCLocked(folder) - if err != nil { - return err - } - } - return nil -} - -// migration17 finds all files that were pulled as invalid from an invalid -// global and make sure they get scanned/pulled again. -func (db *schemaUpdater) migration17(prev int) error { - if prev < 16 { - // Issue was introduced in migration to 16 - return nil - } - t, err := db.newReadOnlyTransaction() - if err != nil { - return err - } - defer t.close() - - for _, folderStr := range db.ListFolders() { - folder := []byte(folderStr) - meta, err := db.loadMetadataTracker(folderStr) - if err != nil { - return err - } - batch := NewFileInfoBatch(func(fs []protocol.FileInfo) error { - return db.updateLocalFiles(folder, fs, meta) - }) - var innerErr error - err = t.withHave(folder, protocol.LocalDeviceID[:], nil, false, func(fi protocol.FileInfo) bool { - if fi.IsInvalid() && fi.FileLocalFlags() == 0 { - fi.SetMustRescan() - fi.Version = protocol.Vector{} - batch.Append(fi) - innerErr = batch.FlushIfFull() - return innerErr == nil - } - return true - }) - if innerErr != nil { - return innerErr - } - if err != nil { - return err - } - if err := batch.Flush(); err != nil { - return err - } - } - return nil -} - -func (db *schemaUpdater) dropAllIndexIDsMigration(_ int) error { - return db.dropIndexIDs() -} - -func (db *schemaUpdater) dropOutgoingIndexIDsMigration(_ int) error { - return db.dropOtherDeviceIndexIDs() -} diff --git a/lib/db/set.go b/lib/db/set.go deleted file mode 100644 index 06e91e14b..000000000 --- a/lib/db/set.go +++ /dev/null @@ -1,553 +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 https://mozilla.org/MPL/2.0/. - -// Package db provides a set type to track local/remote files with newness -// checks. We must do a certain amount of normalization in here. We will get -// fed paths with either native or wire-format separators and encodings -// depending on who calls us. We transform paths to wire-format (NFC and -// slashes) on the way to the database, and transform to native format -// (varying separator and encoding) on the way back out. -package db - -import ( - "bytes" - "fmt" - - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/fs" - "github.com/syncthing/syncthing/lib/osutil" - "github.com/syncthing/syncthing/lib/protocol" - "github.com/syncthing/syncthing/lib/sync" -) - -type FileSet struct { - folder string - db *Lowlevel - meta *metadataTracker - - updateMutex sync.Mutex // protects database updates and the corresponding metadata changes -} - -// The Iterator is called with either a protocol.FileInfo or a -// FileInfoTruncated (depending on the method) and returns true to -// continue iteration, false to stop. -type Iterator func(f protocol.FileInfo) bool - -func NewFileSet(folder string, db *Lowlevel) (*FileSet, error) { - select { - case <-db.oneFileSetCreated: - default: - close(db.oneFileSetCreated) - } - meta, err := db.loadMetadataTracker(folder) - if err != nil { - db.handleFailure(err) - return nil, err - } - s := &FileSet{ - folder: folder, - db: db, - meta: meta, - updateMutex: sync.NewMutex(), - } - if id := s.IndexID(protocol.LocalDeviceID); id == 0 { - // No index ID set yet. We create one now. - id = protocol.NewIndexID() - err := s.db.setIndexID(protocol.LocalDeviceID[:], []byte(s.folder), id) - if err != nil && !backend.IsClosed(err) { - fatalError(err, fmt.Sprintf("%s Creating new IndexID", s.folder), s.db) - } - } - return s, nil -} - -func (s *FileSet) Drop(device protocol.DeviceID) { - opStr := fmt.Sprintf("%s Drop(%v)", s.folder, device) - l.Debugf(opStr) - - s.updateMutex.Lock() - defer s.updateMutex.Unlock() - - if err := s.db.dropDeviceFolder(device[:], []byte(s.folder), s.meta); backend.IsClosed(err) { - return - } else if err != nil { - fatalError(err, opStr, s.db) - } - - if device == protocol.LocalDeviceID { - s.meta.resetCounts(device) - // We deliberately do not reset the sequence number here. Dropping - // all files for the local device ID only happens in testing - which - // expects the sequence to be retained, like an old Replace() of all - // files would do. However, if we ever did it "in production" we - // would anyway want to retain the sequence for delta indexes to be - // happy. - } else { - // Here, on the other hand, we want to make sure that any file - // announced from the remote is newer than our current sequence - // number. - s.meta.resetAll(device) - } - - t, err := s.db.newReadWriteTransaction() - if backend.IsClosed(err) { - return - } else if err != nil { - fatalError(err, opStr, s.db) - } - defer t.close() - - if err := s.meta.toDB(t, []byte(s.folder)); backend.IsClosed(err) { - return - } else if err != nil { - fatalError(err, opStr, s.db) - } - if err := t.Commit(); backend.IsClosed(err) { - return - } else if err != nil { - fatalError(err, opStr, s.db) - } -} - -func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) { - opStr := fmt.Sprintf("%s Update(%v, [%d])", s.folder, device, len(fs)) - l.Debugf(opStr) - - // do not modify fs in place, it is still used in outer scope - fs = append([]protocol.FileInfo(nil), fs...) - - // If one file info is present multiple times, only keep the last. - // Updating the same file multiple times is problematic, because the - // previous updates won't yet be represented in the db when we update it - // again. Additionally even if that problem was taken care of, it would - // be pointless because we remove the previously added file info again - // right away. - fs = normalizeFilenamesAndDropDuplicates(fs) - - s.updateMutex.Lock() - defer s.updateMutex.Unlock() - - if device == protocol.LocalDeviceID { - // For the local device we have a bunch of metadata to track. - if err := s.db.updateLocalFiles([]byte(s.folder), fs, s.meta); err != nil && !backend.IsClosed(err) { - fatalError(err, opStr, s.db) - } - return - } - // Easy case, just update the files and we're done. - if err := s.db.updateRemoteFiles([]byte(s.folder), device[:], fs, s.meta); err != nil && !backend.IsClosed(err) { - fatalError(err, opStr, s.db) - } -} - -func (s *FileSet) RemoveLocalItems(items []string) { - opStr := fmt.Sprintf("%s RemoveLocalItems([%d])", s.folder, len(items)) - l.Debugf(opStr) - - s.updateMutex.Lock() - defer s.updateMutex.Unlock() - - for i := range items { - items[i] = osutil.NormalizedFilename(items[i]) - } - - if err := s.db.removeLocalFiles([]byte(s.folder), items, s.meta); err != nil && !backend.IsClosed(err) { - fatalError(err, opStr, s.db) - } -} - -type Snapshot struct { - folder string - t readOnlyTransaction - meta *countsMap - fatalError func(error, string) -} - -func (s *FileSet) Snapshot() (*Snapshot, error) { - opStr := fmt.Sprintf("%s Snapshot()", s.folder) - l.Debugf(opStr) - - s.updateMutex.Lock() - defer s.updateMutex.Unlock() - - t, err := s.db.newReadOnlyTransaction() - if err != nil { - s.db.handleFailure(err) - return nil, err - } - return &Snapshot{ - folder: s.folder, - t: t, - meta: s.meta.Snapshot(), - fatalError: func(err error, opStr string) { - fatalError(err, opStr, s.db) - }, - }, nil -} - -func (s *Snapshot) Release() { - s.t.close() -} - -func (s *Snapshot) WithNeed(device protocol.DeviceID, fn Iterator) { - opStr := fmt.Sprintf("%s WithNeed(%v)", s.folder, device) - l.Debugf(opStr) - if err := s.t.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *Snapshot) WithNeedTruncated(device protocol.DeviceID, fn Iterator) { - opStr := fmt.Sprintf("%s WithNeedTruncated(%v)", s.folder, device) - l.Debugf(opStr) - if err := s.t.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *Snapshot) WithHave(device protocol.DeviceID, fn Iterator) { - opStr := fmt.Sprintf("%s WithHave(%v)", s.folder, device) - l.Debugf(opStr) - if err := s.t.withHave([]byte(s.folder), device[:], nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *Snapshot) WithHaveTruncated(device protocol.DeviceID, fn Iterator) { - opStr := fmt.Sprintf("%s WithHaveTruncated(%v)", s.folder, device) - l.Debugf(opStr) - if err := s.t.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *Snapshot) WithHaveSequence(startSeq int64, fn Iterator) { - opStr := fmt.Sprintf("%s WithHaveSequence(%v)", s.folder, startSeq) - l.Debugf(opStr) - if err := s.t.withHaveSequence([]byte(s.folder), startSeq, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -// Except for an item with a path equal to prefix, only children of prefix are iterated. -// E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not. -func (s *Snapshot) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) { - opStr := fmt.Sprintf(`%s WithPrefixedHaveTruncated(%v, "%v")`, s.folder, device, prefix) - l.Debugf(opStr) - if err := s.t.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *Snapshot) WithGlobal(fn Iterator) { - opStr := fmt.Sprintf("%s WithGlobal()", s.folder) - l.Debugf(opStr) - if err := s.t.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *Snapshot) WithGlobalTruncated(fn Iterator) { - opStr := fmt.Sprintf("%s WithGlobalTruncated()", s.folder) - l.Debugf(opStr) - if err := s.t.withGlobal([]byte(s.folder), nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -// Except for an item with a path equal to prefix, only children of prefix are iterated. -// E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not. -func (s *Snapshot) WithPrefixedGlobalTruncated(prefix string, fn Iterator) { - opStr := fmt.Sprintf(`%s WithPrefixedGlobalTruncated("%v")`, s.folder, prefix) - l.Debugf(opStr) - if err := s.t.withGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *Snapshot) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) { - opStr := fmt.Sprintf("%s Get(%v)", s.folder, file) - l.Debugf(opStr) - f, ok, err := s.t.getFile([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file))) - if backend.IsClosed(err) { - return protocol.FileInfo{}, false - } else if err != nil { - s.fatalError(err, opStr) - } - f.Name = osutil.NativeFilename(f.Name) - return f, ok -} - -func (s *Snapshot) GetGlobal(file string) (protocol.FileInfo, bool) { - opStr := fmt.Sprintf("%s GetGlobal(%v)", s.folder, file) - l.Debugf(opStr) - _, fi, ok, err := s.t.getGlobal(nil, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), false) - if backend.IsClosed(err) { - return protocol.FileInfo{}, false - } else if err != nil { - s.fatalError(err, opStr) - } - if !ok { - return protocol.FileInfo{}, false - } - fi.Name = osutil.NativeFilename(fi.Name) - return fi, true -} - -func (s *Snapshot) GetGlobalTruncated(file string) (protocol.FileInfo, bool) { - opStr := fmt.Sprintf("%s GetGlobalTruncated(%v)", s.folder, file) - l.Debugf(opStr) - _, fi, ok, err := s.t.getGlobal(nil, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), true) - if backend.IsClosed(err) { - return protocol.FileInfo{}, false - } else if err != nil { - s.fatalError(err, opStr) - } - if !ok { - return protocol.FileInfo{}, false - } - fi.Name = osutil.NativeFilename(fi.Name) - return fi, true -} - -func (s *Snapshot) Availability(file string) []protocol.DeviceID { - opStr := fmt.Sprintf("%s Availability(%v)", s.folder, file) - l.Debugf(opStr) - av, err := s.t.availability([]byte(s.folder), []byte(osutil.NormalizedFilename(file))) - if backend.IsClosed(err) { - return nil - } else if err != nil { - s.fatalError(err, opStr) - } - return av -} - -func (s *Snapshot) DebugGlobalVersions(file string) *DebugVersionList { - opStr := fmt.Sprintf("%s DebugGlobalVersions(%v)", s.folder, file) - l.Debugf(opStr) - vl, err := s.t.getGlobalVersions(nil, []byte(s.folder), []byte(osutil.NormalizedFilename(file))) - if backend.IsClosed(err) || backend.IsNotFound(err) { - return nil - } else if err != nil { - s.fatalError(err, opStr) - } - return &DebugVersionList{vl} -} - -func (s *Snapshot) Sequence(device protocol.DeviceID) int64 { - return s.meta.Counts(device, 0).Sequence -} - -// RemoteSequences returns a map of the sequence numbers seen for each -// remote device sharing this folder. -func (s *Snapshot) RemoteSequences() map[protocol.DeviceID]int64 { - res := make(map[protocol.DeviceID]int64) - for _, device := range s.meta.devices() { - switch device { - case protocol.EmptyDeviceID, protocol.LocalDeviceID, protocol.GlobalDeviceID: - continue - default: - if seq := s.Sequence(device); seq > 0 { - res[device] = seq - } - } - } - - return res -} - -func (s *Snapshot) LocalSize() Counts { - local := s.meta.Counts(protocol.LocalDeviceID, 0) - return local.Add(s.ReceiveOnlyChangedSize()) -} - -func (s *Snapshot) ReceiveOnlyChangedSize() Counts { - return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly) -} - -func (s *Snapshot) GlobalSize() Counts { - return s.meta.Counts(protocol.GlobalDeviceID, 0) -} - -func (s *Snapshot) NeedSize(device protocol.DeviceID) Counts { - return s.meta.Counts(device, needFlag) -} - -func (s *Snapshot) WithBlocksHash(hash []byte, fn Iterator) { - opStr := fmt.Sprintf(`%s WithBlocksHash("%x")`, s.folder, hash) - l.Debugf(opStr) - if err := s.t.withBlocksHash([]byte(s.folder), hash, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) { - s.fatalError(err, opStr) - } -} - -func (s *FileSet) Sequence(device protocol.DeviceID) int64 { - return s.meta.Sequence(device) -} - -func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID { - opStr := fmt.Sprintf("%s IndexID(%v)", s.folder, device) - l.Debugf(opStr) - id, err := s.db.getIndexID(device[:], []byte(s.folder)) - if backend.IsClosed(err) { - return 0 - } else if err != nil { - fatalError(err, opStr, s.db) - } - return id -} - -func (s *FileSet) SetIndexID(device protocol.DeviceID, id protocol.IndexID) { - if device == protocol.LocalDeviceID { - panic("do not explicitly set index ID for local device") - } - opStr := fmt.Sprintf("%s SetIndexID(%v, %v)", s.folder, device, id) - l.Debugf(opStr) - if err := s.db.setIndexID(device[:], []byte(s.folder), id); err != nil && !backend.IsClosed(err) { - fatalError(err, opStr, s.db) - } -} - -func (s *FileSet) MtimeOption() fs.Option { - opStr := fmt.Sprintf("%s MtimeOption()", s.folder) - l.Debugf(opStr) - prefix, err := s.db.keyer.GenerateMtimesKey(nil, []byte(s.folder)) - if backend.IsClosed(err) { - return nil - } else if err != nil { - fatalError(err, opStr, s.db) - } - kv := NewNamespacedKV(s.db, string(prefix)) - return fs.NewMtimeOption(kv) -} - -func (s *FileSet) ListDevices() []protocol.DeviceID { - return s.meta.devices() -} - -func (s *FileSet) RepairSequence() (int, error) { - s.updateAndGCMutexLock() // Ensures consistent locking order - defer s.updateMutex.Unlock() - defer s.db.gcMut.RUnlock() - return s.db.repairSequenceGCLocked(s.folder, s.meta) -} - -func (s *FileSet) updateAndGCMutexLock() { - s.updateMutex.Lock() - s.db.gcMut.RLock() -} - -// DropFolder clears out all information related to the given folder from the -// database. -func DropFolder(db *Lowlevel, folder string) { - opStr := fmt.Sprintf("DropFolder(%v)", folder) - l.Debugf(opStr) - droppers := []func([]byte) error{ - db.dropFolder, - db.dropMtimes, - db.dropFolderMeta, - db.dropFolderIndexIDs, - db.folderIdx.Delete, - } - for _, drop := range droppers { - if err := drop([]byte(folder)); backend.IsClosed(err) { - return - } else if err != nil { - fatalError(err, opStr, db) - } - } -} - -// DropDeltaIndexIDs removes all delta index IDs from the database. -// This will cause a full index transmission on the next connection. -// Must be called before using FileSets, i.e. before NewFileSet is called for -// the first time. -func DropDeltaIndexIDs(db *Lowlevel) { - select { - case <-db.oneFileSetCreated: - panic("DropDeltaIndexIDs must not be called after NewFileSet for the same Lowlevel") - default: - } - opStr := "DropDeltaIndexIDs" - l.Debugf(opStr) - err := db.dropIndexIDs() - if backend.IsClosed(err) { - return - } else if err != nil { - fatalError(err, opStr, db) - } -} - -func normalizeFilenamesAndDropDuplicates(fs []protocol.FileInfo) []protocol.FileInfo { - positions := make(map[string]int, len(fs)) - for i, f := range fs { - norm := osutil.NormalizedFilename(f.Name) - if pos, ok := positions[norm]; ok { - fs[pos] = protocol.FileInfo{} - } - positions[norm] = i - fs[i].Name = norm - } - for i := 0; i < len(fs); { - if fs[i].Name == "" { - fs = append(fs[:i], fs[i+1:]...) - continue - } - i++ - } - return fs -} - -func nativeFileIterator(fn Iterator) Iterator { - return func(fi protocol.FileInfo) bool { - fi.Name = osutil.NativeFilename(fi.Name) - return fn(fi) - } -} - -func fatalError(err error, opStr string, db *Lowlevel) { - db.checkErrorForRepair(err) - l.Warnf("Fatal error: %v: %v", opStr, err) - panic(ldbPathRe.ReplaceAllString(err.Error(), "$1 x: ")) -} - -// DebugFileVersion is the database-internal representation of a file -// version, with a nicer string representation, used only by API debug -// methods. -type DebugVersionList struct { - *dbproto.VersionList -} - -func (vl DebugVersionList) String() string { - var b bytes.Buffer - var id protocol.DeviceID - b.WriteString("[") - for i, v := range vl.Versions { - if i > 0 { - b.WriteString(", ") - } - fmt.Fprintf(&b, "{Version:%v, Deleted:%v, Devices:[", protocol.VectorFromWire(v.Version), v.Deleted) - for j, dev := range v.Devices { - if j > 0 { - b.WriteString(", ") - } - copy(id[:], dev) - fmt.Fprint(&b, id.Short()) - } - b.WriteString("], Invalid:[") - for j, dev := range v.InvalidDevices { - if j > 0 { - b.WriteString(", ") - } - copy(id[:], dev) - fmt.Fprint(&b, id.Short()) - } - fmt.Fprint(&b, "]}") - } - b.WriteString("]") - return b.String() -} diff --git a/lib/db/set_test.go b/lib/db/set_test.go deleted file mode 100644 index 459d9531e..000000000 --- a/lib/db/set_test.go +++ /dev/null @@ -1,1901 +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 https://mozilla.org/MPL/2.0/. - -package db_test - -import ( - "bytes" - "fmt" - "os" - "path/filepath" - "sort" - "testing" - "time" - - "github.com/d4l3k/messagediff" - - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/events" - "github.com/syncthing/syncthing/lib/protocol" -) - -var remoteDevice0, remoteDevice1 protocol.DeviceID - -func init() { - remoteDevice0, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR") - remoteDevice1, _ = protocol.DeviceIDFromString("I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU") -} - -const myID = 1 - -func genBlocks(n int) []protocol.BlockInfo { - b := make([]protocol.BlockInfo, n) - for i := range b { - h := make([]byte, 32) - for j := range h { - h[j] = byte(i + j) - } - b[i].Size = i - b[i].Hash = h - } - return b -} - -func globalList(t testing.TB, s *db.FileSet) []protocol.FileInfo { - var fs []protocol.FileInfo - snap := snapshot(t, s) - defer snap.Release() - snap.WithGlobal(func(fi protocol.FileInfo) bool { - fs = append(fs, fi) - return true - }) - return fs -} - -func globalListPrefixed(t testing.TB, s *db.FileSet, prefix string) []protocol.FileInfo { - var fs []protocol.FileInfo - snap := snapshot(t, s) - defer snap.Release() - snap.WithPrefixedGlobalTruncated(prefix, func(fi protocol.FileInfo) bool { - fs = append(fs, fi) - return true - }) - return fs -} - -func haveList(t testing.TB, s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo { - var fs []protocol.FileInfo - snap := snapshot(t, s) - defer snap.Release() - snap.WithHave(n, func(fi protocol.FileInfo) bool { - fs = append(fs, fi) - return true - }) - return fs -} - -func haveListPrefixed(t testing.TB, s *db.FileSet, n protocol.DeviceID, prefix string) []protocol.FileInfo { - var fs []protocol.FileInfo - snap := snapshot(t, s) - defer snap.Release() - snap.WithPrefixedHaveTruncated(n, prefix, func(fi protocol.FileInfo) bool { - fs = append(fs, fi) - return true - }) - return fs -} - -func needList(t testing.TB, s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo { - var fs []protocol.FileInfo - snap := snapshot(t, s) - defer snap.Release() - snap.WithNeed(n, func(fi protocol.FileInfo) bool { - fs = append(fs, fi) - return true - }) - return fs -} - -type fileList []protocol.FileInfo - -func (l fileList) Len() int { - return len(l) -} - -func (l fileList) Less(a, b int) bool { - return l[a].Name < l[b].Name -} - -func (l fileList) Swap(a, b int) { - l[a], l[b] = l[b], l[a] -} - -func (l fileList) String() string { - var b bytes.Buffer - b.WriteString("[]protocol.FileList{\n") - for _, f := range l { - fmt.Fprintf(&b, " %q: #%v, %d bytes, %d blocks, perms=%o\n", f.Name, f.Version, f.Size, len(f.Blocks), f.Permissions) - } - b.WriteString("}") - return b.String() -} - -func setSequence(seq int64, files fileList) int64 { - for i := range files { - seq++ - files[i].Sequence = seq - } - return seq -} - -func setBlocksHash(files fileList) { - for i, f := range files { - files[i].BlocksHash = protocol.BlocksHash(f.Blocks) - } -} - -func TestGlobalSet(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - m := newFileSet(t, "test", ldb) - - local0 := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(3)}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "z", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(8)}, - } - localSeq := setSequence(0, local0) - setBlocksHash(local0) - local1 := fileList{ - protocol.FileInfo{Name: "a", Sequence: 6, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: "b", Sequence: 7, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Sequence: 8, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(3)}, - protocol.FileInfo{Name: "d", Sequence: 9, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "z", Sequence: 10, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Deleted: true}, - } - setSequence(localSeq, local1) - setBlocksHash(local1) - localTot := fileList{ - local1[0], - local1[1], - local1[2], - local1[3], - protocol.FileInfo{Name: "z", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Deleted: true}, - } - - remote0 := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(5)}, - } - remoteSeq := setSequence(0, remote0) - setBlocksHash(remote0) - remote1 := fileList{ - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(6)}, - protocol.FileInfo{Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(7)}, - } - setSequence(remoteSeq, remote1) - setBlocksHash(remote1) - remoteTot := fileList{ - remote0[0], - remote1[0], - remote0[2], - remote1[1], - } - - expectedGlobal := fileList{ - remote0[0], // a - remote1[0], // b - remote0[2], // c - localTot[3], // d - remote1[1], // e - localTot[4], // z - } - - expectedLocalNeed := fileList{ - remote1[0], - remote0[2], - remote1[1], - } - - expectedRemoteNeed := fileList{ - local0[3], - } - - replace(m, protocol.LocalDeviceID, local0) - replace(m, protocol.LocalDeviceID, local1) - replace(m, remoteDevice0, remote0) - m.Update(remoteDevice0, remote1) - - check := func() { - t.Helper() - - g := fileList(globalList(t, m)) - sort.Sort(g) - - if fmt.Sprint(g) != fmt.Sprint(expectedGlobal) { - t.Errorf("Global incorrect;\n A: %v !=\n E: %v", g, expectedGlobal) - } - - var globalFiles, globalDirectories, globalDeleted int - var globalBytes int64 - for _, f := range g { - if f.IsInvalid() { - continue - } - switch { - case f.IsDeleted(): - globalDeleted++ - case f.IsDirectory(): - globalDirectories++ - default: - globalFiles++ - } - globalBytes += f.FileSize() - } - gs := globalSize(t, m) - if gs.Files != globalFiles { - t.Errorf("Incorrect GlobalSize files; %d != %d", gs.Files, globalFiles) - } - if gs.Directories != globalDirectories { - t.Errorf("Incorrect GlobalSize directories; %d != %d", gs.Directories, globalDirectories) - } - if gs.Deleted != globalDeleted { - t.Errorf("Incorrect GlobalSize deleted; %d != %d", gs.Deleted, globalDeleted) - } - if gs.Bytes != globalBytes { - t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes) - } - - h := fileList(haveList(t, m, protocol.LocalDeviceID)) - sort.Sort(h) - - if fmt.Sprint(h) != fmt.Sprint(localTot) { - t.Errorf("Have incorrect (local);\n A: %v !=\n E: %v", h, localTot) - } - - var haveFiles, haveDirectories, haveDeleted int - var haveBytes int64 - for _, f := range h { - if f.IsInvalid() { - continue - } - switch { - case f.IsDeleted(): - haveDeleted++ - case f.IsDirectory(): - haveDirectories++ - default: - haveFiles++ - } - haveBytes += f.FileSize() - } - ls := localSize(t, m) - if ls.Files != haveFiles { - t.Errorf("Incorrect LocalSize files; %d != %d", ls.Files, haveFiles) - } - if ls.Directories != haveDirectories { - t.Errorf("Incorrect LocalSize directories; %d != %d", ls.Directories, haveDirectories) - } - if ls.Deleted != haveDeleted { - t.Errorf("Incorrect LocalSize deleted; %d != %d", ls.Deleted, haveDeleted) - } - if ls.Bytes != haveBytes { - t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes) - } - - h = fileList(haveList(t, m, remoteDevice0)) - sort.Sort(h) - - if fmt.Sprint(h) != fmt.Sprint(remoteTot) { - t.Errorf("Have incorrect (remote);\n A: %v !=\n E: %v", h, remoteTot) - } - - n := fileList(needList(t, m, protocol.LocalDeviceID)) - sort.Sort(n) - - if fmt.Sprint(n) != fmt.Sprint(expectedLocalNeed) { - t.Errorf("Need incorrect (local);\n A: %v !=\n E: %v", n, expectedLocalNeed) - } - - checkNeed(t, m, protocol.LocalDeviceID, expectedLocalNeed) - - n = fileList(needList(t, m, remoteDevice0)) - sort.Sort(n) - - if fmt.Sprint(n) != fmt.Sprint(expectedRemoteNeed) { - t.Errorf("Need incorrect (remote);\n A: %v !=\n E: %v", n, expectedRemoteNeed) - } - - checkNeed(t, m, remoteDevice0, expectedRemoteNeed) - - snap := snapshot(t, m) - defer snap.Release() - f, ok := snap.Get(protocol.LocalDeviceID, "b") - if !ok { - t.Error("Unexpectedly not OK") - } - if fmt.Sprint(f) != fmt.Sprint(localTot[1]) { - t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, localTot[1]) - } - - f, ok = snap.Get(remoteDevice0, "b") - if !ok { - t.Error("Unexpectedly not OK") - } - if fmt.Sprint(f) != fmt.Sprint(remote1[0]) { - t.Errorf("Get incorrect (remote);\n A: %v !=\n E: %v", f, remote1[0]) - } - - f, ok = snap.GetGlobal("b") - if !ok { - t.Error("Unexpectedly not OK") - } - if fmt.Sprint(f) != fmt.Sprint(expectedGlobal[1]) { - t.Errorf("GetGlobal incorrect;\n A: %v !=\n E: %v", f, remote1[0]) - } - - f, ok = snap.Get(protocol.LocalDeviceID, "zz") - if ok { - t.Error("Unexpectedly OK") - } - if f.Name != "" { - t.Errorf("Get incorrect (local);\n A: %v !=\n E: %v", f, protocol.FileInfo{}) - } - - f, ok = snap.GetGlobal("zz") - if ok { - t.Error("Unexpectedly OK") - } - if f.Name != "" { - t.Errorf("GetGlobal incorrect;\n A: %v !=\n E: %v", f, protocol.FileInfo{}) - } - } - - check() - - snap := snapshot(t, m) - - av := []protocol.DeviceID{protocol.LocalDeviceID, remoteDevice0} - a := snap.Availability("a") - if !(len(a) == 2 && (a[0] == av[0] && a[1] == av[1] || a[0] == av[1] && a[1] == av[0])) { - t.Errorf("Availability incorrect;\n A: %v !=\n E: %v", a, av) - } - a = snap.Availability("b") - if len(a) != 1 || a[0] != remoteDevice0 { - t.Errorf("Availability incorrect;\n A: %v !=\n E: %v", a, remoteDevice0) - } - a = snap.Availability("d") - if len(a) != 1 || a[0] != protocol.LocalDeviceID { - t.Errorf("Availability incorrect;\n A: %v !=\n E: %v", a, protocol.LocalDeviceID) - } - - snap.Release() - - // Now bring another remote into play - - secRemote := fileList{ - local1[0], // a - remote1[0], // b - local1[3], // d - remote1[1], // e - local1[4], // z - } - secRemote[0].Version = secRemote[0].Version.Update(remoteDevice1.Short()) - secRemote[1].Version = secRemote[1].Version.Update(remoteDevice1.Short()) - secRemote[4].Version = secRemote[4].Version.Update(remoteDevice1.Short()) - secRemote[4].Deleted = false - secRemote[4].Blocks = genBlocks(1) - setSequence(0, secRemote) - - expectedGlobal = fileList{ - secRemote[0], // a - secRemote[1], // b - remote0[2], // c - localTot[3], // d - secRemote[3], // e - secRemote[4], // z - } - - expectedLocalNeed = fileList{ - secRemote[0], // a - secRemote[1], // b - remote0[2], // c - secRemote[3], // e - secRemote[4], // z - } - - expectedRemoteNeed = fileList{ - secRemote[0], // a - secRemote[1], // b - local0[3], // d - secRemote[4], // z - } - - expectedSecRemoteNeed := fileList{ - remote0[2], // c - } - - m.Update(remoteDevice1, secRemote) - - check() - - h := fileList(haveList(t, m, remoteDevice1)) - sort.Sort(h) - - if fmt.Sprint(h) != fmt.Sprint(secRemote) { - t.Errorf("Have incorrect (secRemote);\n A: %v !=\n E: %v", h, secRemote) - } - - n := fileList(needList(t, m, remoteDevice1)) - sort.Sort(n) - - if fmt.Sprint(n) != fmt.Sprint(expectedSecRemoteNeed) { - t.Errorf("Need incorrect (secRemote);\n A: %v !=\n E: %v", n, expectedSecRemoteNeed) - } - - checkNeed(t, m, remoteDevice1, expectedSecRemoteNeed) -} - -func TestNeedWithInvalid(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - localHave := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - } - remote0Have := fileList{ - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(5), RawInvalid: true}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(7)}, - } - remote1Have := fileList{ - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(7)}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(5), RawInvalid: true}, - protocol.FileInfo{Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1004}}}, Blocks: genBlocks(5), RawInvalid: true}, - } - - expectedNeed := fileList{ - remote0Have[0], - remote1Have[0], - remote0Have[2], - } - - replace(s, protocol.LocalDeviceID, localHave) - replace(s, remoteDevice0, remote0Have) - replace(s, remoteDevice1, remote1Have) - - need := fileList(needList(t, s, protocol.LocalDeviceID)) - sort.Sort(need) - - if fmt.Sprint(need) != fmt.Sprint(expectedNeed) { - t.Errorf("Need incorrect;\n A: %v !=\n E: %v", need, expectedNeed) - } - - checkNeed(t, s, protocol.LocalDeviceID, expectedNeed) -} - -func TestUpdateToInvalid(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - s := newFileSet(t, folder, ldb) - f := db.NewBlockFinder(ldb) - - localHave := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1), Size: 1}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2), Size: 1}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(5), LocalFlags: protocol.FlagLocalIgnored, Size: 1}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(7), Size: 1}, - protocol.FileInfo{Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, LocalFlags: protocol.FlagLocalIgnored, Size: 1}, - } - - replace(s, protocol.LocalDeviceID, localHave) - - have := fileList(haveList(t, s, protocol.LocalDeviceID)) - sort.Sort(have) - - if fmt.Sprint(have) != fmt.Sprint(localHave) { - t.Errorf("Have incorrect before invalidation;\n A: %v !=\n E: %v", have, localHave) - } - - oldBlockHash := localHave[1].Blocks[0].Hash - - localHave[1].LocalFlags = protocol.FlagLocalIgnored - localHave[1].Blocks = nil - - localHave[4].LocalFlags = 0 - localHave[4].Blocks = genBlocks(3) - - s.Update(protocol.LocalDeviceID, append(fileList{}, localHave[1], localHave[4])) - - have = fileList(haveList(t, s, protocol.LocalDeviceID)) - sort.Sort(have) - - if fmt.Sprint(have) != fmt.Sprint(localHave) { - t.Errorf("Have incorrect after invalidation;\n A: %v !=\n E: %v", have, localHave) - } - - f.Iterate([]string{folder}, oldBlockHash, func(folder, file string, index int32) bool { - if file == localHave[1].Name { - t.Errorf("Found unexpected block in blockmap for invalidated file") - return true - } - return false - }) - - if !f.Iterate([]string{folder}, localHave[4].Blocks[0].Hash, func(folder, file string, index int32) bool { - return file == localHave[4].Name - }) { - t.Errorf("First block of un-invalidated file is missing from blockmap") - } -} - -func TestInvalidAvailability(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - remote0Have := fileList{ - protocol.FileInfo{Name: "both", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "r1only", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(5), RawInvalid: true}, - protocol.FileInfo{Name: "r0only", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(7)}, - protocol.FileInfo{Name: "none", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1004}}}, Blocks: genBlocks(5), RawInvalid: true}, - } - remote1Have := fileList{ - protocol.FileInfo{Name: "both", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "r1only", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(7)}, - protocol.FileInfo{Name: "r0only", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(5), RawInvalid: true}, - protocol.FileInfo{Name: "none", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1004}}}, Blocks: genBlocks(5), RawInvalid: true}, - } - - replace(s, remoteDevice0, remote0Have) - replace(s, remoteDevice1, remote1Have) - - snap := snapshot(t, s) - defer snap.Release() - - if av := snap.Availability("both"); len(av) != 2 { - t.Error("Incorrect availability for 'both':", av) - } - - if av := snap.Availability("r0only"); len(av) != 1 || av[0] != remoteDevice0 { - t.Error("Incorrect availability for 'r0only':", av) - } - - if av := snap.Availability("r1only"); len(av) != 1 || av[0] != remoteDevice1 { - t.Error("Incorrect availability for 'r1only':", av) - } - - if av := snap.Availability("none"); len(av) != 0 { - t.Error("Incorrect availability for 'none':", av) - } -} - -func TestGlobalReset(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - m := newFileSet(t, "test", ldb) - - local := []protocol.FileInfo{ - {Name: "a", Sequence: 1, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "b", Sequence: 2, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "c", Sequence: 3, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "d", Sequence: 4, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - remote := []protocol.FileInfo{ - {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}}, - {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}}, - {Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - replace(m, protocol.LocalDeviceID, local) - g := globalList(t, m) - sort.Sort(fileList(g)) - - if diff, equal := messagediff.PrettyDiff(local, g); !equal { - t.Errorf("Global incorrect;\nglobal: %v\n!=\nlocal: %v\ndiff:\n%s", g, local, diff) - } - - replace(m, remoteDevice0, remote) - replace(m, remoteDevice0, nil) - - g = globalList(t, m) - sort.Sort(fileList(g)) - - if diff, equal := messagediff.PrettyDiff(local, g); !equal { - t.Errorf("Global incorrect;\nglobal: %v\n!=\nlocal: %v\ndiff:\n%s", g, local, diff) - } -} - -func TestNeed(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - m := newFileSet(t, "test", ldb) - - local := []protocol.FileInfo{ - {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - remote := []protocol.FileInfo{ - {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}}, - {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}}, - {Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - shouldNeed := []protocol.FileInfo{ - {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}}, - {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}}, - {Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - replace(m, protocol.LocalDeviceID, local) - replace(m, remoteDevice0, remote) - - need := needList(t, m, protocol.LocalDeviceID) - - sort.Sort(fileList(need)) - sort.Sort(fileList(shouldNeed)) - - if fmt.Sprint(need) != fmt.Sprint(shouldNeed) { - t.Errorf("Need incorrect;\n%v !=\n%v", need, shouldNeed) - } - - checkNeed(t, m, protocol.LocalDeviceID, shouldNeed) -} - -func TestSequence(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - m := newFileSet(t, "test", ldb) - - local1 := []protocol.FileInfo{ - {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - local2 := []protocol.FileInfo{ - local1[0], - // [1] deleted - local1[2], - {Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}}, - {Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - replace(m, protocol.LocalDeviceID, local1) - c0 := m.Sequence(protocol.LocalDeviceID) - - replace(m, protocol.LocalDeviceID, local2) - c1 := m.Sequence(protocol.LocalDeviceID) - if !(c1 > c0) { - t.Fatal("Local version number should have incremented") - } -} - -func TestListDropFolder(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s0 := newFileSet(t, "test0", ldb) - local1 := []protocol.FileInfo{ - {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - replace(s0, protocol.LocalDeviceID, local1) - - s1 := newFileSet(t, "test1", ldb) - local2 := []protocol.FileInfo{ - {Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}}, - {Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}}, - {Name: "f", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}}, - } - replace(s1, remoteDevice0, local2) - - // Check that we have both folders and their data is in the global list - - expectedFolderList := []string{"test0", "test1"} - actualFolderList := ldb.ListFolders() - if diff, equal := messagediff.PrettyDiff(expectedFolderList, actualFolderList); !equal { - t.Fatalf("FolderList mismatch. Diff:\n%s", diff) - } - if l := len(globalList(t, s0)); l != 3 { - t.Errorf("Incorrect global length %d != 3 for s0", l) - } - if l := len(globalList(t, s1)); l != 3 { - t.Errorf("Incorrect global length %d != 3 for s1", l) - } - - // Drop one of them and check that it's gone. - - db.DropFolder(ldb, "test1") - - expectedFolderList = []string{"test0"} - actualFolderList = ldb.ListFolders() - if diff, equal := messagediff.PrettyDiff(expectedFolderList, actualFolderList); !equal { - t.Fatalf("FolderList mismatch. Diff:\n%s", diff) - } - if l := len(globalList(t, s0)); l != 3 { - t.Errorf("Incorrect global length %d != 3 for s0", l) - } - if l := len(globalList(t, s1)); l != 0 { - t.Errorf("Incorrect global length %d != 0 for s1", l) - } -} - -func TestGlobalNeedWithInvalid(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test1", ldb) - - rem0 := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, RawInvalid: true}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: remoteDevice0.Short(), Value: 1002}}}}, - } - replace(s, remoteDevice0, rem0) - - rem1 := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, RawInvalid: true}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, RawInvalid: true, ModifiedS: 10}, - } - replace(s, remoteDevice1, rem1) - - total := fileList{ - // There's a valid copy of each file, so it should be merged - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(4)}, - // in conflict and older, but still wins as the other is invalid - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: remoteDevice0.Short(), Value: 1002}}}}, - } - - need := fileList(needList(t, s, protocol.LocalDeviceID)) - if fmt.Sprint(need) != fmt.Sprint(total) { - t.Errorf("Need incorrect;\n A: %v !=\n E: %v", need, total) - } - checkNeed(t, s, protocol.LocalDeviceID, total) - - global := fileList(globalList(t, s)) - if fmt.Sprint(global) != fmt.Sprint(total) { - t.Errorf("Global incorrect;\n A: %v !=\n E: %v", global, total) - } -} - -func TestLongPath(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - var b bytes.Buffer - for i := 0; i < 100; i++ { - b.WriteString("012345678901234567890123456789012345678901234567890") - } - name := b.String() // 5000 characters - - local := []protocol.FileInfo{ - {Name: name, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - replace(s, protocol.LocalDeviceID, local) - - gf := globalList(t, s) - if l := len(gf); l != 1 { - t.Fatalf("Incorrect len %d != 1 for global list", l) - } - if gf[0].Name != local[0].Name { - t.Errorf("Incorrect long filename;\n%q !=\n%q", - gf[0].Name, local[0].Name) - } -} - -func BenchmarkUpdateOneFile(b *testing.B) { - local0 := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(3)}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(4)}, - // A longer name is more realistic and causes more allocations - protocol.FileInfo{Name: "zajksdhaskjdh/askjdhaskjdashkajshd/kasjdhaskjdhaskdjhaskdjash/dkjashdaksjdhaskdjahskdjh", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(8)}, - } - - be, err := backend.Open("testdata/benchmarkupdate.db", backend.TuningAuto) - if err != nil { - b.Fatal(err) - } - ldb := newLowlevel(b, be) - defer func() { - ldb.Close() - os.RemoveAll("testdata/benchmarkupdate.db") - }() - - m := newFileSet(b, "test", ldb) - replace(m, protocol.LocalDeviceID, local0) - l := local0[4:5] - - for i := 0; i < b.N; i++ { - l[0].Version = l[0].Version.Update(myID) - m.Update(protocol.LocalDeviceID, local0) - } - - b.ReportAllocs() -} - -func TestIndexID(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - // The Index ID for some random device is zero by default. - id := s.IndexID(remoteDevice0) - if id != 0 { - t.Errorf("index ID for remote device should default to zero, not %d", id) - } - - // The Index ID for someone else should be settable - s.SetIndexID(remoteDevice0, 42) - id = s.IndexID(remoteDevice0) - if id != 42 { - t.Errorf("index ID for remote device should be remembered; got %d, expected %d", id, 42) - } - - // Our own index ID should be generated randomly. - id = s.IndexID(protocol.LocalDeviceID) - if id == 0 { - t.Errorf("index ID for local device should be random, not zero") - } - t.Logf("random index ID is 0x%016x", id) - - // But of course always the same after that. - again := s.IndexID(protocol.LocalDeviceID) - if again != id { - t.Errorf("index ID changed; %d != %d", again, id) - } -} - -func TestDropFiles(t *testing.T) { - ldb := newLowlevelMemory(t) - - m := newFileSet(t, "test", ldb) - - local0 := fileList{ - protocol.FileInfo{Name: "a", Sequence: 1, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: "b", Sequence: 2, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Sequence: 3, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(3)}, - protocol.FileInfo{Name: "d", Sequence: 4, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(4)}, - protocol.FileInfo{Name: "z", Sequence: 5, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(8)}, - } - - remote0 := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(5)}, - } - - // Insert files - - m.Update(protocol.LocalDeviceID, local0) - m.Update(remoteDevice0, remote0) - - // Check that they're there - - h := haveList(t, m, protocol.LocalDeviceID) - if len(h) != len(local0) { - t.Errorf("Incorrect number of files after update, %d != %d", len(h), len(local0)) - } - - h = haveList(t, m, remoteDevice0) - if len(h) != len(remote0) { - t.Errorf("Incorrect number of files after update, %d != %d", len(h), len(local0)) - } - - g := globalList(t, m) - if len(g) != len(local0) { - // local0 covers all files - t.Errorf("Incorrect global files after update, %d != %d", len(g), len(local0)) - } - - // Drop the local files and recheck - - m.Drop(protocol.LocalDeviceID) - - h = haveList(t, m, protocol.LocalDeviceID) - if len(h) != 0 { - t.Errorf("Incorrect number of files after drop, %d != %d", len(h), 0) - } - - h = haveList(t, m, remoteDevice0) - if len(h) != len(remote0) { - t.Errorf("Incorrect number of files after update, %d != %d", len(h), len(local0)) - } - - g = globalList(t, m) - if len(g) != len(remote0) { - // the ones in remote0 remain - t.Errorf("Incorrect global files after update, %d != %d", len(g), len(remote0)) - } -} - -func TestIssue4701(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - localHave := fileList{ - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, LocalFlags: protocol.FlagLocalIgnored}, - } - - s.Update(protocol.LocalDeviceID, localHave) - - if c := localSize(t, s); c.Files != 1 { - t.Errorf("Expected 1 local file, got %v", c.Files) - } - if c := globalSize(t, s); c.Files != 1 { - t.Errorf("Expected 1 global file, got %v", c.Files) - } - - localHave[1].LocalFlags = 0 - s.Update(protocol.LocalDeviceID, localHave) - - if c := localSize(t, s); c.Files != 2 { - t.Errorf("Expected 2 local files, got %v", c.Files) - } - if c := globalSize(t, s); c.Files != 2 { - t.Errorf("Expected 2 global files, got %v", c.Files) - } - - localHave[0].LocalFlags = protocol.FlagLocalIgnored - localHave[1].LocalFlags = protocol.FlagLocalIgnored - s.Update(protocol.LocalDeviceID, localHave) - - if c := localSize(t, s); c.Files != 0 { - t.Errorf("Expected 0 local files, got %v", c.Files) - } - if c := globalSize(t, s); c.Files != 0 { - t.Errorf("Expected 0 global files, got %v", c.Files) - } -} - -func TestWithHaveSequence(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - s := newFileSet(t, folder, ldb) - - // The files must not be in alphabetical order - localHave := fileList{ - protocol.FileInfo{Name: "e", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, RawInvalid: true}, - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)}, - protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(7)}, - protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)}, - protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(5), RawInvalid: true}, - } - - replace(s, protocol.LocalDeviceID, localHave) - - i := 2 - snap := snapshot(t, s) - defer snap.Release() - snap.WithHaveSequence(int64(i), func(fi protocol.FileInfo) bool { - if !fi.IsEquivalent(localHave[i-1], 0) { - t.Fatalf("Got %v\nExpected %v", fi, localHave[i-1]) - } - i++ - return true - }) -} - -func TestStressWithHaveSequence(t *testing.T) { - // This races two loops against each other: one that continuously does - // updates, and one that continuously does sequence walks. The test fails - // if the sequence walker sees a discontinuity. - - if testing.Short() { - t.Skip("Takes a long time") - } - - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - s := newFileSet(t, folder, ldb) - - var localHave []protocol.FileInfo - for i := 0; i < 100; i++ { - localHave = append(localHave, protocol.FileInfo{Name: fmt.Sprintf("file%d", i), Blocks: genBlocks(i * 10)}) - } - - done := make(chan struct{}) - t0 := time.Now() - go func() { - for time.Since(t0) < 10*time.Second { - for j, f := range localHave { - localHave[j].Version = f.Version.Update(42) - } - - s.Update(protocol.LocalDeviceID, localHave) - } - close(done) - }() - - var prevSeq int64 -loop: - for { - select { - case <-done: - break loop - default: - } - snap := snapshot(t, s) - snap.WithHaveSequence(prevSeq+1, func(fi protocol.FileInfo) bool { - if fi.SequenceNo() < prevSeq+1 { - t.Fatal("Skipped ", prevSeq+1, fi.SequenceNo()) - } - prevSeq = fi.SequenceNo() - return true - }) - snap.Release() - } -} - -func TestIssue4925(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - s := newFileSet(t, folder, ldb) - - localHave := fileList{ - protocol.FileInfo{Name: "dir"}, - protocol.FileInfo{Name: "dir.file"}, - protocol.FileInfo{Name: "dir/file"}, - } - - replace(s, protocol.LocalDeviceID, localHave) - - for _, prefix := range []string{"dir", "dir/"} { - pl := haveListPrefixed(t, s, protocol.LocalDeviceID, prefix) - if l := len(pl); l != 2 { - t.Errorf("Expected 2, got %v local items below %v", l, prefix) - } - pl = globalListPrefixed(t, s, prefix) - if l := len(pl); l != 2 { - t.Errorf("Expected 2, got %v global items below %v", l, prefix) - } - } -} - -func TestMoveGlobalBack(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - file := "foo" - s := newFileSet(t, folder, ldb) - - localHave := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}, Blocks: genBlocks(1), ModifiedS: 10, Size: 1}} - remote0Have := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}, {ID: remoteDevice0.Short(), Value: 1}}}, Blocks: genBlocks(2), ModifiedS: 0, Size: 2}} - - s.Update(protocol.LocalDeviceID, localHave) - s.Update(remoteDevice0, remote0Have) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 { - t.Error("Expected 1 local need, got", need) - } else if !need[0].IsEquivalent(remote0Have[0], 0) { - t.Errorf("Local need incorrect;\n A: %v !=\n E: %v", need[0], remote0Have[0]) - } - checkNeed(t, s, protocol.LocalDeviceID, remote0Have[:1]) - - if need := needList(t, s, remoteDevice0); len(need) != 0 { - t.Error("Expected no need for remote 0, got", need) - } - checkNeed(t, s, remoteDevice0, nil) - - ls := localSize(t, s) - if haveBytes := localHave[0].Size; ls.Bytes != haveBytes { - t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes) - } - - gs := globalSize(t, s) - if globalBytes := remote0Have[0].Size; gs.Bytes != globalBytes { - t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes) - } - - // That's what happens when something becomes unignored or something. - // In any case it will be moved back from first spot in the global list - // which is the scenario to be tested here. - remote0Have[0].Version = remote0Have[0].Version.Update(remoteDevice0.Short()).DropOthers(remoteDevice0.Short()) - s.Update(remoteDevice0, remote0Have) - - if need := needList(t, s, remoteDevice0); len(need) != 1 { - t.Error("Expected 1 need for remote 0, got", need) - } else if !need[0].IsEquivalent(localHave[0], 0) { - t.Errorf("Need for remote 0 incorrect;\n A: %v !=\n E: %v", need[0], localHave[0]) - } - checkNeed(t, s, remoteDevice0, localHave[:1]) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 { - t.Error("Expected no local need, got", need) - } - checkNeed(t, s, protocol.LocalDeviceID, nil) - - ls = localSize(t, s) - if haveBytes := localHave[0].Size; ls.Bytes != haveBytes { - t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes) - } - - gs = globalSize(t, s) - if globalBytes := localHave[0].Size; gs.Bytes != globalBytes { - t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes) - } -} - -// TestIssue5007 checks, that updating the local device with an invalid file -// info with the newest version does indeed remove that file from the list of -// needed files. -// https://github.com/syncthing/syncthing/issues/5007 -func TestIssue5007(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - file := "foo" - s := newFileSet(t, folder, ldb) - - fs := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}} - - s.Update(remoteDevice0, fs) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 { - t.Fatal("Expected 1 local need, got", need) - } else if !need[0].IsEquivalent(fs[0], 0) { - t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0]) - } - checkNeed(t, s, protocol.LocalDeviceID, fs[:1]) - - fs[0].LocalFlags = protocol.FlagLocalIgnored - s.Update(protocol.LocalDeviceID, fs) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 { - t.Fatal("Expected no local need, got", need) - } - checkNeed(t, s, protocol.LocalDeviceID, nil) -} - -// TestNeedDeleted checks that a file that doesn't exist locally isn't needed -// when the global file is deleted. -func TestNeedDeleted(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - file := "foo" - s := newFileSet(t, folder, ldb) - - fs := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}, Deleted: true}} - - s.Update(remoteDevice0, fs) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 { - t.Fatal("Expected no local need, got", need) - } - checkNeed(t, s, protocol.LocalDeviceID, nil) - - fs[0].Deleted = false - fs[0].Version = fs[0].Version.Update(remoteDevice0.Short()) - s.Update(remoteDevice0, fs) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 { - t.Fatal("Expected 1 local need, got", need) - } else if !need[0].IsEquivalent(fs[0], 0) { - t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0]) - } - checkNeed(t, s, protocol.LocalDeviceID, fs[:1]) - - fs[0].Deleted = true - fs[0].Version = fs[0].Version.Update(remoteDevice0.Short()) - s.Update(remoteDevice0, fs) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 { - t.Fatal("Expected no local need, got", need) - } - checkNeed(t, s, protocol.LocalDeviceID, nil) -} - -func TestReceiveOnlyAccounting(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - s := newFileSet(t, folder, ldb) - - local := protocol.DeviceID{1} - remote := protocol.DeviceID{2} - - // Three files that have been created by the remote device - - version := protocol.Vector{Counters: []protocol.Counter{{ID: remote.Short(), Value: 1}}} - files := fileList{ - protocol.FileInfo{Name: "f1", Size: 10, Sequence: 1, Version: version}, - protocol.FileInfo{Name: "f2", Size: 10, Sequence: 1, Version: version}, - protocol.FileInfo{Name: "f3", Size: 10, Sequence: 1, Version: version}, - } - - // We have synced them locally - - replace(s, protocol.LocalDeviceID, files) - replace(s, remote, files) - - if n := localSize(t, s).Files; n != 3 { - t.Fatal("expected 3 local files initially, not", n) - } - if n := localSize(t, s).Bytes; n != 30 { - t.Fatal("expected 30 local bytes initially, not", n) - } - if n := globalSize(t, s).Files; n != 3 { - t.Fatal("expected 3 global files initially, not", n) - } - if n := globalSize(t, s).Bytes; n != 30 { - t.Fatal("expected 30 global bytes initially, not", n) - } - if n := receiveOnlyChangedSize(t, s).Files; n != 0 { - t.Fatal("expected 0 receive only changed files initially, not", n) - } - if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 { - t.Fatal("expected 0 receive only changed bytes initially, not", n) - } - - // Detected a local change in a receive only folder - - changed := files[0] - changed.Version = changed.Version.Update(local.Short()) - changed.Size = 100 - changed.ModifiedBy = local.Short() - changed.LocalFlags = protocol.FlagLocalReceiveOnly - s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed}) - - // Check that we see the files - - if n := localSize(t, s).Files; n != 3 { - t.Fatal("expected 3 local files after local change, not", n) - } - if n := localSize(t, s).Bytes; n != 120 { - t.Fatal("expected 120 local bytes after local change, not", n) - } - if n := globalSize(t, s).Files; n != 3 { - t.Fatal("expected 3 global files after local change, not", n) - } - if n := globalSize(t, s).Bytes; n != 30 { - t.Fatal("expected 30 global files after local change, not", n) - } - if n := receiveOnlyChangedSize(t, s).Files; n != 1 { - t.Fatal("expected 1 receive only changed file after local change, not", n) - } - if n := receiveOnlyChangedSize(t, s).Bytes; n != 100 { - t.Fatal("expected 100 receive only changed bytes after local change, not", n) - } - - // Fake a revert. That's a two step process, first converting our - // changed file into a less preferred variant, then pulling down the old - // version. - - changed.Version = protocol.Vector{} - changed.LocalFlags &^= protocol.FlagLocalReceiveOnly - s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed}) - - s.Update(protocol.LocalDeviceID, []protocol.FileInfo{files[0]}) - - // Check that we see the files, same data as initially - - if n := localSize(t, s).Files; n != 3 { - t.Fatal("expected 3 local files after revert, not", n) - } - if n := localSize(t, s).Bytes; n != 30 { - t.Fatal("expected 30 local bytes after revert, not", n) - } - if n := globalSize(t, s).Files; n != 3 { - t.Fatal("expected 3 global files after revert, not", n) - } - if n := globalSize(t, s).Bytes; n != 30 { - t.Fatal("expected 30 global bytes after revert, not", n) - } - if n := receiveOnlyChangedSize(t, s).Files; n != 0 { - t.Fatal("expected 0 receive only changed files after revert, not", n) - } - if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 { - t.Fatal("expected 0 receive only changed bytes after revert, not", n) - } -} - -func TestNeedAfterUnignore(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - folder := "test" - file := "foo" - s := newFileSet(t, folder, ldb) - - remID := remoteDevice0.Short() - - // Initial state: Devices in sync, locally ignored - local := protocol.FileInfo{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: remID, Value: 1}, {ID: myID, Value: 1}}}, ModifiedS: 10} - local.SetIgnored() - remote := protocol.FileInfo{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: remID, Value: 1}, {ID: myID, Value: 1}}}, ModifiedS: 10} - s.Update(protocol.LocalDeviceID, fileList{local}) - s.Update(remoteDevice0, fileList{remote}) - - // Unignore locally -> conflicting changes. Remote is newer, thus winning. - local.Version = local.Version.Update(myID) - local.Version = local.Version.DropOthers(myID) - local.LocalFlags = 0 - local.ModifiedS = 0 - s.Update(protocol.LocalDeviceID, fileList{local}) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 { - t.Fatal("Expected one local need, got", need) - } else if !need[0].IsEquivalent(remote, 0) { - t.Fatalf("Got %v, expected %v", need[0], remote) - } - checkNeed(t, s, protocol.LocalDeviceID, []protocol.FileInfo{remote}) -} - -func TestRemoteInvalidNotAccounted(t *testing.T) { - // Remote files with the invalid bit should not count. - - ldb := newLowlevelMemory(t) - defer ldb.Close() - s := newFileSet(t, "test", ldb) - - files := []protocol.FileInfo{ - {Name: "a", Size: 1234, Sequence: 42, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}}, // valid, should count - {Name: "b", Size: 1234, Sequence: 43, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, RawInvalid: true}, // invalid, doesn't count - } - s.Update(remoteDevice0, files) - - global := globalSize(t, s) - if global.Files != 1 { - t.Error("Expected one file in global size, not", global.Files) - } - if global.Bytes != 1234 { - t.Error("Expected 1234 bytes in global size, not", global.Bytes) - } -} - -func TestNeedWithNewerInvalid(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "default", ldb) - - rem0ID := remoteDevice0.Short() - rem1ID := remoteDevice1.Short() - - // Initial state: file present on rem0 and rem1, but not locally. - file := protocol.FileInfo{Name: "foo"} - file.Version = file.Version.Update(rem0ID) - s.Update(remoteDevice0, fileList{file}) - s.Update(remoteDevice1, fileList{file}) - - need := needList(t, s, protocol.LocalDeviceID) - if len(need) != 1 { - t.Fatal("Locally missing file should be needed") - } - if !need[0].IsEquivalent(file, 0) { - t.Fatalf("Got needed file %v, expected %v", need[0], file) - } - checkNeed(t, s, protocol.LocalDeviceID, []protocol.FileInfo{file}) - - // rem1 sends an invalid file with increased version - inv := file - inv.Version = inv.Version.Update(rem1ID) - inv.RawInvalid = true - s.Update(remoteDevice1, fileList{inv}) - - // We still have an old file, we need the newest valid file - need = needList(t, s, protocol.LocalDeviceID) - if len(need) != 1 { - t.Fatal("Locally missing file should be needed regardless of invalid files") - } - if !need[0].IsEquivalent(file, 0) { - t.Fatalf("Got needed file %v, expected %v", need[0], file) - } - checkNeed(t, s, protocol.LocalDeviceID, []protocol.FileInfo{file}) -} - -func TestNeedAfterDeviceRemove(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - file := "foo" - s := newFileSet(t, "test", ldb) - - fs := fileList{{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}}} - - s.Update(protocol.LocalDeviceID, fs) - - fs[0].Version = fs[0].Version.Update(myID) - - s.Update(remoteDevice0, fs) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 { - t.Fatal("Expected one local need, got", need) - } - - s.Drop(remoteDevice0) - - if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 { - t.Fatal("Expected no local need, got", need) - } - checkNeed(t, s, protocol.LocalDeviceID, nil) -} - -func TestCaseSensitive(t *testing.T) { - // Normal case sensitive lookup should work - - ldb := newLowlevelMemory(t) - defer ldb.Close() - s := newFileSet(t, "test", ldb) - - local := []protocol.FileInfo{ - {Name: filepath.FromSlash("D1/f1"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("F1"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("d1/F1"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("d1/f1"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("f1"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - replace(s, protocol.LocalDeviceID, local) - - gf := globalList(t, s) - if l := len(gf); l != len(local) { - t.Fatalf("Incorrect len %d != %d for global list", l, len(local)) - } - for i := range local { - if gf[i].Name != local[i].Name { - t.Errorf("Incorrect filename;\n%q !=\n%q", - gf[i].Name, local[i].Name) - } - } -} - -func TestSequenceIndex(t *testing.T) { - // This test attempts to verify correct operation of the sequence index. - - // It's a stress test and needs to run for a long time, but we don't - // really have time for that in normal builds. - runtime := time.Minute - if testing.Short() { - runtime = time.Second - } - - // Set up a db and a few files that we will manipulate. - - ldb := newLowlevelMemory(t) - defer ldb.Close() - s := newFileSet(t, "test", ldb) - - local := []protocol.FileInfo{ - {Name: filepath.FromSlash("banana"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("pineapple"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("orange"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("apple"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - {Name: filepath.FromSlash("jackfruit"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}}, - } - - // Start a background routine that makes updates to these files as fast - // as it can. We always update the same files in the same order. - - done := make(chan struct{}) - defer close(done) - - go func() { - for { - select { - case <-done: - return - default: - } - - for i := range local { - local[i].Version = local[i].Version.Update(42) - } - s.Update(protocol.LocalDeviceID, local) - } - }() - - // Start a routine to walk the sequence index and inspect the result. - - seen := make(map[string]protocol.FileInfo) - latest := make([]protocol.FileInfo, 0, len(local)) - var seq int64 - t0 := time.Now() - - for time.Since(t0) < runtime { - // Walk the changes since our last iteration. This should give is - // one instance each of the files that are changed all the time, or - // a subset of those files if we manage to run before a complete - // update has happened since our last iteration. - latest = latest[:0] - snap := snapshot(t, s) - snap.WithHaveSequence(seq+1, func(f protocol.FileInfo) bool { - seen[f.FileName()] = f - latest = append(latest, f) - seq = f.SequenceNo() - return true - }) - snap.Release() - - // Calculate the spread in sequence number. - var max, min int64 - for _, v := range seen { - s := v.SequenceNo() - if max == 0 || max < s { - max = s - } - if min == 0 || min > s { - min = s - } - } - - // We shouldn't see a spread larger than the number of files, as - // that would mean we have missed updates. For example, if we were - // to see the following: - // - // banana N - // pineapple N+1 - // orange N+2 - // apple N+10 - // jackfruit N+11 - // - // that would mean that there have been updates to banana, pineapple - // and orange that we didn't see in this pass. If those files aren't - // updated again, those updates are permanently lost. - if max-min > int64(len(local)) { - for _, v := range seen { - t.Log("seen", v.FileName(), v.SequenceNo()) - } - for _, v := range latest { - t.Log("latest", v.FileName(), v.SequenceNo()) - } - t.Fatal("large spread") - } - time.Sleep(time.Millisecond) - } -} - -func TestIgnoreAfterReceiveOnly(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - file := "foo" - s := newFileSet(t, "test", ldb) - - fs := fileList{{ - Name: file, - Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}, - LocalFlags: protocol.FlagLocalReceiveOnly, - }} - - s.Update(protocol.LocalDeviceID, fs) - - fs[0].LocalFlags = protocol.FlagLocalIgnored - - s.Update(protocol.LocalDeviceID, fs) - - snap := snapshot(t, s) - defer snap.Release() - if f, ok := snap.Get(protocol.LocalDeviceID, file); !ok { - t.Error("File missing in db") - } else if f.IsReceiveOnlyChanged() { - t.Error("File is still receive-only changed") - } else if !f.IsIgnored() { - t.Error("File is not ignored") - } -} - -// https://github.com/syncthing/syncthing/issues/6650 -func TestUpdateWithOneFileTwice(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - file := "foo" - s := newFileSet(t, "test", ldb) - - fs := fileList{{ - Name: file, - Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}}, - Sequence: 1, - }} - - s.Update(protocol.LocalDeviceID, fs) - - fs = append(fs, fs[0]) - for i := range fs { - fs[i].Sequence++ - fs[i].Version = fs[i].Version.Update(myID) - } - fs[1].Sequence++ - fs[1].Version = fs[1].Version.Update(myID) - - s.Update(protocol.LocalDeviceID, fs) - - snap := snapshot(t, s) - defer snap.Release() - count := 0 - snap.WithHaveSequence(0, func(_ protocol.FileInfo) bool { - count++ - return true - }) - if count != 1 { - t.Error("Expected to have one file, got", count) - } -} - -// https://github.com/syncthing/syncthing/issues/6668 -func TestNeedRemoteOnly(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - remote0Have := fileList{ - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)}, - } - s.Update(remoteDevice0, remote0Have) - - need := needSize(t, s, remoteDevice0) - if !need.Equal(db.Counts{}) { - t.Error("Expected nothing needed, got", need) - } -} - -// https://github.com/syncthing/syncthing/issues/6784 -func TestNeedRemoteAfterReset(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - files := fileList{ - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)}, - } - s.Update(protocol.LocalDeviceID, files) - s.Update(remoteDevice0, files) - - need := needSize(t, s, remoteDevice0) - if !need.Equal(db.Counts{}) { - t.Error("Expected nothing needed, got", need) - } - - s.Drop(remoteDevice0) - - need = needSize(t, s, remoteDevice0) - if exp := (db.Counts{Files: 1}); !need.Equal(exp) { - t.Errorf("Expected %v, got %v", exp, need) - } -} - -// https://github.com/syncthing/syncthing/issues/6850 -func TestIgnoreLocalChanged(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - // Add locally changed file - files := fileList{ - protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2), LocalFlags: protocol.FlagLocalReceiveOnly}, - } - s.Update(protocol.LocalDeviceID, files) - - if c := globalSize(t, s).Files; c != 0 { - t.Error("Expected no global file, got", c) - } - if c := localSize(t, s).Files; c != 1 { - t.Error("Expected one local file, got", c) - } - - // Change file to ignored - files[0].LocalFlags = protocol.FlagLocalIgnored - s.Update(protocol.LocalDeviceID, files) - - if c := globalSize(t, s).Files; c != 0 { - t.Error("Expected no global file, got", c) - } - if c := localSize(t, s).Files; c != 0 { - t.Error("Expected no local file, got", c) - } -} - -// Dropping the index ID on Drop is bad, because Drop gets called when receiving -// an Index (as opposed to an IndexUpdate), and we don't want to loose the index -// ID when that happens. -func TestNoIndexIDResetOnDrop(t *testing.T) { - ldb := newLowlevelMemory(t) - defer ldb.Close() - - s := newFileSet(t, "test", ldb) - - s.SetIndexID(remoteDevice0, 1) - s.Drop(remoteDevice0) - if got := s.IndexID(remoteDevice0); got != 1 { - t.Errorf("Expected unchanged (%v), got %v", 1, got) - } -} - -func TestConcurrentIndexID(t *testing.T) { - done := make(chan struct{}) - var ids [2]protocol.IndexID - setID := func(s *db.FileSet, i int) { - ids[i] = s.IndexID(protocol.LocalDeviceID) - done <- struct{}{} - } - - max := 100 - if testing.Short() { - max = 10 - } - for i := 0; i < max; i++ { - ldb := newLowlevelMemory(t) - s := newFileSet(t, "test", ldb) - go setID(s, 0) - go setID(s, 1) - <-done - <-done - ldb.Close() - if ids[0] != ids[1] { - t.Fatalf("IDs differ after %v rounds", i) - } - } -} - -func TestNeedRemoveLastValid(t *testing.T) { - db := newLowlevelMemory(t) - defer db.Close() - - folder := "test" - - fs := newFileSet(t, folder, db) - - files := []protocol.FileInfo{ - {Name: "foo", Version: protocol.Vector{}.Update(myID), Sequence: 1}, - } - fs.Update(remoteDevice0, files) - files[0].Version = files[0].Version.Update(myID) - fs.Update(remoteDevice1, files) - files[0].LocalFlags = protocol.FlagLocalIgnored - fs.Update(protocol.LocalDeviceID, files) - - snap := snapshot(t, fs) - c := snap.NeedSize(remoteDevice0) - if c.Files != 1 { - t.Errorf("Expected 1 needed files initially, got %v", c.Files) - } - snap.Release() - - fs.Drop(remoteDevice1) - - snap = snapshot(t, fs) - c = snap.NeedSize(remoteDevice0) - if c.Files != 0 { - t.Errorf("Expected no needed files, got %v", c.Files) - } - snap.Release() -} - -func replace(fs *db.FileSet, device protocol.DeviceID, files []protocol.FileInfo) { - fs.Drop(device) - fs.Update(device, files) -} - -func localSize(t testing.TB, fs *db.FileSet) db.Counts { - snap := snapshot(t, fs) - defer snap.Release() - return snap.LocalSize() -} - -func globalSize(t testing.TB, fs *db.FileSet) db.Counts { - snap := snapshot(t, fs) - defer snap.Release() - return snap.GlobalSize() -} - -func needSize(t testing.TB, fs *db.FileSet, id protocol.DeviceID) db.Counts { - snap := snapshot(t, fs) - defer snap.Release() - return snap.NeedSize(id) -} - -func receiveOnlyChangedSize(t testing.TB, fs *db.FileSet) db.Counts { - snap := snapshot(t, fs) - defer snap.Release() - return snap.ReceiveOnlyChangedSize() -} - -func filesToCounts(files []protocol.FileInfo) db.Counts { - cp := db.Counts{} - for _, f := range files { - switch { - case f.IsDeleted(): - cp.Deleted++ - case f.IsDirectory() && !f.IsSymlink(): - cp.Directories++ - case f.IsSymlink(): - cp.Symlinks++ - default: - cp.Files++ - } - cp.Bytes += f.FileSize() - } - return cp -} - -func checkNeed(t testing.TB, s *db.FileSet, dev protocol.DeviceID, expected []protocol.FileInfo) { - t.Helper() - counts := needSize(t, s, dev) - if exp := filesToCounts(expected); !exp.Equal(counts) { - t.Errorf("Count incorrect (%v): expected %v, got %v", dev, exp, counts) - } -} - -func newLowlevel(t testing.TB, backend backend.Backend) *db.Lowlevel { - t.Helper() - ll, err := db.NewLowlevel(backend, events.NoopLogger) - if err != nil { - t.Fatal(err) - } - return ll -} - -func newLowlevelMemory(t testing.TB) *db.Lowlevel { - return newLowlevel(t, backend.OpenMemory()) -} - -func newFileSet(t testing.TB, folder string, ll *db.Lowlevel) *db.FileSet { - t.Helper() - fset, err := db.NewFileSet(folder, ll) - if err != nil { - t.Fatal(err) - } - return fset -} - -func snapshot(t testing.TB, fset *db.FileSet) *db.Snapshot { - t.Helper() - snap, err := fset.Snapshot() - if err != nil { - t.Fatal(err) - } - return snap -} diff --git a/lib/db/smallindex_test.go b/lib/db/smallindex_test.go deleted file mode 100644 index c47a685a0..000000000 --- a/lib/db/smallindex_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "testing" -) - -func TestSmallIndex(t *testing.T) { - db := newLowlevelMemory(t) - idx := newSmallIndex(db, []byte{12, 34}) - - // ID zero should be unallocated - if val, ok := idx.Val(0); ok || val != nil { - t.Fatal("Unexpected return for nonexistent ID 0") - } - - // A new key should get ID zero - if id, err := idx.ID([]byte("hello")); err != nil { - t.Fatal(err) - } else if id != 0 { - t.Fatal("Expected 0, not", id) - } - // Looking up ID zero should work - if val, ok := idx.Val(0); !ok || string(val) != "hello" { - t.Fatalf(`Expected true, "hello", not %v, %q`, ok, val) - } - - // Delete the key - idx.Delete([]byte("hello")) - - // Next ID should be one - if id, err := idx.ID([]byte("key2")); err != nil { - t.Fatal(err) - } else if id != 1 { - t.Fatal("Expected 1, not", id) - } - - // Now lets create a new index instance based on what's actually serialized to the database. - idx = newSmallIndex(db, []byte{12, 34}) - - // Status should be about the same as before. - if val, ok := idx.Val(0); ok || val != nil { - t.Fatal("Unexpected return for deleted ID 0") - } - if id, err := idx.ID([]byte("key2")); err != nil { - t.Fatal(err) - } else if id != 1 { - t.Fatal("Expected 1, not", id) - } - - // Setting "hello" again should get us ID 2, not 0 as it was originally. - if id, err := idx.ID([]byte("hello")); err != nil { - t.Fatal(err) - } else if id != 2 { - t.Fatal("Expected 2, not", id) - } -} diff --git a/lib/db/structs.go b/lib/db/structs.go deleted file mode 100644 index 69d602112..000000000 --- a/lib/db/structs.go +++ /dev/null @@ -1,363 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "bytes" - "fmt" - "strings" - - "google.golang.org/protobuf/proto" - - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/lib/protocol" -) - -type CountsSet struct { - Counts []Counts - Created int64 // unix nanos -} - -type Counts struct { - Files int - Directories int - Symlinks int - Deleted int - Bytes int64 - Sequence int64 // zero for the global state - DeviceID protocol.DeviceID // device ID for remote devices, or special values for local/global - LocalFlags uint32 // the local flag for this count bucket -} - -func (c Counts) toWire() *dbproto.Counts { - return &dbproto.Counts{ - Files: int32(c.Files), - Directories: int32(c.Directories), - Symlinks: int32(c.Symlinks), - Deleted: int32(c.Deleted), - Bytes: c.Bytes, - Sequence: c.Sequence, - DeviceId: c.DeviceID[:], - LocalFlags: c.LocalFlags, - } -} - -func countsFromWire(w *dbproto.Counts) Counts { - return Counts{ - Files: int(w.Files), - Directories: int(w.Directories), - Symlinks: int(w.Symlinks), - Deleted: int(w.Deleted), - Bytes: w.Bytes, - Sequence: w.Sequence, - DeviceID: protocol.DeviceID(w.DeviceId), - LocalFlags: w.LocalFlags, - } -} - -func (c Counts) Add(other Counts) Counts { - return Counts{ - Files: c.Files + other.Files, - Directories: c.Directories + other.Directories, - Symlinks: c.Symlinks + other.Symlinks, - Deleted: c.Deleted + other.Deleted, - Bytes: c.Bytes + other.Bytes, - Sequence: c.Sequence + other.Sequence, - DeviceID: protocol.EmptyDeviceID, - LocalFlags: c.LocalFlags | other.LocalFlags, - } -} - -func (c Counts) TotalItems() int { - return c.Files + c.Directories + c.Symlinks + c.Deleted -} - -func (c Counts) String() string { - var flags strings.Builder - if c.LocalFlags&needFlag != 0 { - flags.WriteString("Need") - } - if c.LocalFlags&protocol.FlagLocalIgnored != 0 { - flags.WriteString("Ignored") - } - if c.LocalFlags&protocol.FlagLocalMustRescan != 0 { - flags.WriteString("Rescan") - } - if c.LocalFlags&protocol.FlagLocalReceiveOnly != 0 { - flags.WriteString("Recvonly") - } - if c.LocalFlags&protocol.FlagLocalUnsupported != 0 { - flags.WriteString("Unsupported") - } - if c.LocalFlags != 0 { - flags.WriteString(fmt.Sprintf("(%x)", c.LocalFlags)) - } - if flags.Len() == 0 { - flags.WriteString("---") - } - return fmt.Sprintf("{Device:%v, Files:%d, Dirs:%d, Symlinks:%d, Del:%d, Bytes:%d, Seq:%d, Flags:%s}", c.DeviceID, c.Files, c.Directories, c.Symlinks, c.Deleted, c.Bytes, c.Sequence, flags.String()) -} - -// Equal compares the numbers only, not sequence/dev/flags. -func (c Counts) Equal(o Counts) bool { - return c.Files == o.Files && c.Directories == o.Directories && c.Symlinks == o.Symlinks && c.Deleted == o.Deleted && c.Bytes == o.Bytes -} - -// update brings the VersionList up to date with file. It returns the updated -// VersionList, a device that has the global/newest version, a device that previously -// had the global/newest version, a boolean indicating if the global version has -// changed and if any error occurred (only possible in db interaction). -func vlUpdate(vl *dbproto.VersionList, folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) (*dbproto.FileVersion, *dbproto.FileVersion, *dbproto.FileVersion, bool, bool, bool, error) { - if len(vl.Versions) == 0 { - nv := newFileVersion(device, file.FileVersion(), file.IsInvalid(), file.IsDeleted()) - vl.Versions = append(vl.Versions, nv) - return nv, nil, nil, false, false, true, nil - } - - // Get the current global (before updating) - oldFV, haveOldGlobal := vlGetGlobal(vl) - oldFV = fvCopy(oldFV) - - // Remove ourselves first - removedFV, haveRemoved, _ := vlPop(vl, device) - // Find position and insert the file - err := vlInsert(vl, folder, device, file, t) - if err != nil { - return nil, nil, nil, false, false, false, err - } - - newFV, _ := vlGetGlobal(vl) // We just inserted something above, can't be empty - - if !haveOldGlobal { - return newFV, nil, removedFV, false, haveRemoved, true, nil - } - - globalChanged := true - if fvIsInvalid(oldFV) == fvIsInvalid(newFV) && protocol.VectorFromWire(oldFV.Version).Equal(protocol.VectorFromWire(newFV.Version)) { - globalChanged = false - } - - return newFV, oldFV, removedFV, true, haveRemoved, globalChanged, nil -} - -func vlInsert(vl *dbproto.VersionList, folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) error { - var added bool - var err error - i := 0 - for ; i < len(vl.Versions); i++ { - // Insert our new version - added, err = vlCheckInsertAt(vl, i, folder, device, file, t) - if err != nil { - return err - } - if added { - break - } - } - if i == len(vl.Versions) { - // Append to the end - vl.Versions = append(vl.Versions, newFileVersion(device, file.FileVersion(), file.IsInvalid(), file.IsDeleted())) - } - return nil -} - -func vlInsertAt(vl *dbproto.VersionList, i int, v *dbproto.FileVersion) { - vl.Versions = append(vl.Versions, &dbproto.FileVersion{}) - copy(vl.Versions[i+1:], vl.Versions[i:]) - vl.Versions[i] = v -} - -// pop removes the given device from the VersionList and returns the FileVersion -// before removing the device, whether it was found/removed at all and whether -// the global changed in the process. -func vlPop(vl *dbproto.VersionList, device []byte) (*dbproto.FileVersion, bool, bool) { - invDevice, i, j, ok := vlFindDevice(vl, device) - if !ok { - return nil, false, false - } - globalPos := vlFindGlobal(vl) - - fv := vl.Versions[i] - if fvDeviceCount(fv) == 1 { - vlPopVersionAt(vl, i) - return fv, true, globalPos == i - } - - oldFV := fvCopy(fv) - if invDevice { - vl.Versions[i].InvalidDevices = popDeviceAt(vl.Versions[i].InvalidDevices, j) - return oldFV, true, false - } - vl.Versions[i].Devices = popDeviceAt(vl.Versions[i].Devices, j) - // If the last valid device of the previous global was removed above, - // the global changed. - return oldFV, true, len(vl.Versions[i].Devices) == 0 && globalPos == i -} - -// Get returns a FileVersion that contains the given device and whether it has -// been found at all. -func vlGet(vl *dbproto.VersionList, device []byte) (*dbproto.FileVersion, bool) { - _, i, _, ok := vlFindDevice(vl, device) - if !ok { - return &dbproto.FileVersion{}, false - } - return vl.Versions[i], true -} - -// GetGlobal returns the current global FileVersion. The returned FileVersion -// may be invalid, if all FileVersions are invalid. Returns false only if -// VersionList is empty. -func vlGetGlobal(vl *dbproto.VersionList) (*dbproto.FileVersion, bool) { - i := vlFindGlobal(vl) - if i == -1 { - return nil, false - } - return vl.Versions[i], true -} - -// findGlobal returns the first version that isn't invalid, or if all versions are -// invalid just the first version (i.e. 0) or -1, if there's no versions at all. -func vlFindGlobal(vl *dbproto.VersionList) int { - for i := range vl.Versions { - if !fvIsInvalid(vl.Versions[i]) { - return i - } - } - if len(vl.Versions) == 0 { - return -1 - } - return 0 -} - -// findDevice returns whether the device is in InvalidVersions or Versions and -// in InvalidDevices or Devices (true for invalid), the positions in the version -// and device slices and whether it has been found at all. -func vlFindDevice(vl *dbproto.VersionList, device []byte) (bool, int, int, bool) { - for i, v := range vl.Versions { - if j := deviceIndex(v.Devices, device); j != -1 { - return false, i, j, true - } - if j := deviceIndex(v.InvalidDevices, device); j != -1 { - return true, i, j, true - } - } - return false, -1, -1, false -} - -func vlPopVersionAt(vl *dbproto.VersionList, i int) { - vl.Versions = append(vl.Versions[:i], vl.Versions[i+1:]...) -} - -// checkInsertAt determines if the given device and associated file should be -// inserted into the FileVersion at position i or into a new FileVersion at -// position i. -func vlCheckInsertAt(vl *dbproto.VersionList, i int, folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) (bool, error) { - fv := vl.Versions[i] - ordering := protocol.VectorFromWire(fv.Version).Compare(file.FileVersion()) - if ordering == protocol.Equal { - if !file.IsInvalid() { - fv.Devices = append(fv.Devices, device) - } else { - fv.InvalidDevices = append(fv.InvalidDevices, device) - } - return true, nil - } - existingDevice, _ := fvFirstDevice(fv) - insert, err := shouldInsertBefore(ordering, folder, existingDevice, fvIsInvalid(fv), file, t) - if err != nil { - return false, err - } - if insert { - vlInsertAt(vl, i, newFileVersion(device, file.FileVersion(), file.IsInvalid(), file.IsDeleted())) - return true, nil - } - return false, nil -} - -// shouldInsertBefore determines whether the file comes before an existing -// entry, given the version ordering (existing compared to new one), existing -// device and if the existing version is invalid. -func shouldInsertBefore(ordering protocol.Ordering, folder, existingDevice []byte, existingInvalid bool, file protocol.FileInfo, t readOnlyTransaction) (bool, error) { - switch ordering { - case protocol.Lesser: - // The version at this point in the list is lesser - // ("older") than us. We insert ourselves in front of it. - return true, nil - - case protocol.ConcurrentLesser, protocol.ConcurrentGreater: - // The version in conflict with us. - // Check if we can shortcut due to one being invalid. - if existingInvalid != file.IsInvalid() { - return existingInvalid, nil - } - // We must pull the actual file metadata to determine who wins. - // If we win, we insert ourselves in front of the loser here. - // (The "Lesser" and "Greater" in the condition above is just - // based on the device IDs in the version vector, which is not - // the only thing we use to determine the winner.) - of, ok, err := t.getFile(folder, existingDevice, []byte(file.FileName())) - if err != nil { - return false, err - } - // A surprise missing file entry here is counted as a win for us. - if !ok { - return true, nil - } - if file.WinsConflict(of) { - return true, nil - } - } - return false, nil -} - -func deviceIndex(devices [][]byte, device []byte) int { - for i, dev := range devices { - if bytes.Equal(device, dev) { - return i - } - } - return -1 -} - -func popDeviceAt(devices [][]byte, i int) [][]byte { - return append(devices[:i], devices[i+1:]...) -} - -func newFileVersion(device []byte, version protocol.Vector, invalid, deleted bool) *dbproto.FileVersion { - fv := &dbproto.FileVersion{ - Version: version.ToWire(), - Deleted: deleted, - } - if invalid { - fv.InvalidDevices = [][]byte{device} - } else { - fv.Devices = [][]byte{device} - } - return fv -} - -func fvFirstDevice(fv *dbproto.FileVersion) ([]byte, bool) { - if len(fv.Devices) != 0 { - return fv.Devices[0], true - } - if len(fv.InvalidDevices) != 0 { - return fv.InvalidDevices[0], true - } - return nil, false -} - -func fvIsInvalid(fv *dbproto.FileVersion) bool { - return fv == nil || len(fv.Devices) == 0 -} - -func fvDeviceCount(fv *dbproto.FileVersion) int { - return len(fv.Devices) + len(fv.InvalidDevices) -} - -func fvCopy(fv *dbproto.FileVersion) *dbproto.FileVersion { - return proto.Clone(fv).(*dbproto.FileVersion) -} diff --git a/lib/db/transactions.go b/lib/db/transactions.go deleted file mode 100644 index 9efeff21e..000000000 --- a/lib/db/transactions.go +++ /dev/null @@ -1,1008 +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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "bytes" - "errors" - "fmt" - - "google.golang.org/protobuf/proto" - - "github.com/syncthing/syncthing/internal/gen/bep" - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/events" - "github.com/syncthing/syncthing/lib/osutil" - "github.com/syncthing/syncthing/lib/protocol" - "github.com/syncthing/syncthing/lib/sliceutil" -) - -var ( - errEntryFromGlobalMissing = errors.New("device present in global list but missing as device/fileinfo entry") - errEmptyGlobal = errors.New("no versions in global list") - errEmptyFileVersion = errors.New("no devices in global file version") -) - -// A readOnlyTransaction represents a database snapshot. -type readOnlyTransaction struct { - backend.ReadTransaction - keyer keyer - evLogger events.Logger -} - -func (db *Lowlevel) newReadOnlyTransaction() (readOnlyTransaction, error) { - tran, err := db.NewReadTransaction() - if err != nil { - return readOnlyTransaction{}, err - } - return db.readOnlyTransactionFromBackendTransaction(tran), nil -} - -func (db *Lowlevel) readOnlyTransactionFromBackendTransaction(tran backend.ReadTransaction) readOnlyTransaction { - return readOnlyTransaction{ - ReadTransaction: tran, - keyer: db.keyer, - evLogger: db.evLogger, - } -} - -func (t readOnlyTransaction) close() { - t.Release() -} - -func (t readOnlyTransaction) getFile(folder, device, file []byte) (protocol.FileInfo, bool, error) { - key, err := t.keyer.GenerateDeviceFileKey(nil, folder, device, file) - if err != nil { - return protocol.FileInfo{}, false, err - } - return t.getFileByKey(key) -} - -func (t readOnlyTransaction) getFileByKey(key []byte) (protocol.FileInfo, bool, error) { - f, ok, err := t.getFileTrunc(key, false) - if err != nil || !ok { - return protocol.FileInfo{}, false, err - } - return f, true, nil -} - -func (t readOnlyTransaction) getFileTrunc(key []byte, trunc bool) (protocol.FileInfo, bool, error) { - bs, err := t.Get(key) - if backend.IsNotFound(err) { - return protocol.FileInfo{}, false, nil - } - if err != nil { - return protocol.FileInfo{}, false, err - } - f, err := t.unmarshalTrunc(bs, trunc) - if backend.IsNotFound(err) { - return protocol.FileInfo{}, false, nil - } - if err != nil { - return protocol.FileInfo{}, false, err - } - return f, true, nil -} - -func (t readOnlyTransaction) unmarshalTrunc(bs []byte, trunc bool) (protocol.FileInfo, error) { - if trunc { - var bfi dbproto.FileInfoTruncated - err := proto.Unmarshal(bs, &bfi) - if err != nil { - return protocol.FileInfo{}, err - } - if err := t.fillTruncated(&bfi); err != nil { - return protocol.FileInfo{}, err - } - return protocol.FileInfoFromDBTruncated(&bfi), nil - } - - var bfi bep.FileInfo - err := proto.Unmarshal(bs, &bfi) - if err != nil { - return protocol.FileInfo{}, err - } - if err := t.fillFileInfo(&bfi); err != nil { - return protocol.FileInfo{}, err - } - return protocol.FileInfoFromDB(&bfi), nil -} - -type blocksIndirectionError struct { - err error -} - -func (e *blocksIndirectionError) Error() string { - return fmt.Sprintf("filling Blocks: %v", e.err) -} - -func (e *blocksIndirectionError) Unwrap() error { - return e.err -} - -// fillFileInfo follows the (possible) indirection of blocks and version -// vector and fills it out. -func (t readOnlyTransaction) fillFileInfo(fi *bep.FileInfo) error { - var key []byte - - if len(fi.Blocks) == 0 && len(fi.BlocksHash) != 0 { - // The blocks list is indirected and we need to load it. - key = t.keyer.GenerateBlockListKey(key, fi.BlocksHash) - bs, err := t.Get(key) - if err != nil { - return &blocksIndirectionError{err} - } - var bl dbproto.BlockList - if err := proto.Unmarshal(bs, &bl); err != nil { - return err - } - fi.Blocks = bl.Blocks - } - - if len(fi.VersionHash) != 0 { - key = t.keyer.GenerateVersionKey(key, fi.VersionHash) - bs, err := t.Get(key) - if err != nil { - return fmt.Errorf("filling Version: %w", err) - } - var v bep.Vector - if err := proto.Unmarshal(bs, &v); err != nil { - return err - } - fi.Version = &v - } - - return nil -} - -// fillTruncated follows the (possible) indirection of version vector and -// fills it. -func (t readOnlyTransaction) fillTruncated(fi *dbproto.FileInfoTruncated) error { - var key []byte - - if len(fi.VersionHash) == 0 { - return nil - } - - key = t.keyer.GenerateVersionKey(key, fi.VersionHash) - bs, err := t.Get(key) - if err != nil { - return err - } - var v bep.Vector - if err := proto.Unmarshal(bs, &v); err != nil { - return err - } - fi.Version = &v - return nil -} - -func (t readOnlyTransaction) getGlobalVersions(keyBuf, folder, file []byte) (*dbproto.VersionList, error) { - var err error - keyBuf, err = t.keyer.GenerateGlobalVersionKey(keyBuf, folder, file) - if err != nil { - return nil, err - } - return t.getGlobalVersionsByKey(keyBuf) -} - -func (t readOnlyTransaction) getGlobalVersionsByKey(key []byte) (*dbproto.VersionList, error) { - bs, err := t.Get(key) - if err != nil { - return nil, err - } - - var vl dbproto.VersionList - if err := proto.Unmarshal(bs, &vl); err != nil { - return nil, err - } - - return &vl, nil -} - -func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate bool) ([]byte, protocol.FileInfo, bool, error) { - vl, err := t.getGlobalVersions(keyBuf, folder, file) - if backend.IsNotFound(err) { - return keyBuf, protocol.FileInfo{}, false, nil - } else if err != nil { - return nil, protocol.FileInfo{}, false, err - } - keyBuf, fi, err := t.getGlobalFromVersionList(keyBuf, folder, file, truncate, vl) - return keyBuf, fi, true, err -} - -func (t readOnlyTransaction) getGlobalFromVersionList(keyBuf, folder, file []byte, truncate bool, vl *dbproto.VersionList) ([]byte, protocol.FileInfo, error) { - fv, ok := vlGetGlobal(vl) - if !ok { - return keyBuf, protocol.FileInfo{}, errEmptyGlobal - } - keyBuf, fi, err := t.getGlobalFromFileVersion(keyBuf, folder, file, truncate, fv) - return keyBuf, fi, err -} - -func (t readOnlyTransaction) getGlobalFromFileVersion(keyBuf, folder, file []byte, truncate bool, fv *dbproto.FileVersion) ([]byte, protocol.FileInfo, error) { - dev, ok := fvFirstDevice(fv) - if !ok { - return keyBuf, protocol.FileInfo{}, errEmptyFileVersion - } - keyBuf, err := t.keyer.GenerateDeviceFileKey(keyBuf, folder, dev, file) - if err != nil { - return keyBuf, protocol.FileInfo{}, err - } - fi, ok, err := t.getFileTrunc(keyBuf, truncate) - if err != nil { - return keyBuf, protocol.FileInfo{}, err - } - if !ok { - return keyBuf, protocol.FileInfo{}, errEntryFromGlobalMissing - } - return keyBuf, fi, nil -} - -func (t *readOnlyTransaction) withHave(folder, device, prefix []byte, truncate bool, fn Iterator) error { - if len(prefix) > 0 { - unslashedPrefix := prefix - if bytes.HasSuffix(prefix, []byte{'/'}) { - unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1] - } else { - prefix = append(prefix, '/') - } - - key, err := t.keyer.GenerateDeviceFileKey(nil, folder, device, unslashedPrefix) - if err != nil { - return err - } - if f, ok, err := t.getFileTrunc(key, truncate); err != nil { - return err - } else if ok && !fn(f) { - return nil - } - } - - key, err := t.keyer.GenerateDeviceFileKey(nil, folder, device, prefix) - if err != nil { - return err - } - dbi, err := t.NewPrefixIterator(key) - if err != nil { - return err - } - defer dbi.Release() - - for dbi.Next() { - name := t.keyer.NameFromDeviceFileKey(dbi.Key()) - if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) { - return nil - } - - f, err := t.unmarshalTrunc(dbi.Value(), truncate) - if err != nil { - l.Debugln("unmarshal error:", err) - continue - } - if !fn(f) { - return nil - } - } - return dbi.Error() -} - -func (t *readOnlyTransaction) withHaveSequence(folder []byte, startSeq int64, fn Iterator) error { - first, err := t.keyer.GenerateSequenceKey(nil, folder, startSeq) - if err != nil { - return err - } - last, err := t.keyer.GenerateSequenceKey(nil, folder, maxInt64) - if err != nil { - return err - } - dbi, err := t.NewRangeIterator(first, last) - if err != nil { - return err - } - defer dbi.Release() - - for dbi.Next() { - f, ok, err := t.getFileByKey(dbi.Value()) - if err != nil { - return err - } - if !ok { - l.Debugln("missing file for sequence number", t.keyer.SequenceFromSequenceKey(dbi.Key())) - continue - } - - if shouldDebug() { - if seq := t.keyer.SequenceFromSequenceKey(dbi.Key()); f.Sequence != seq { - l.Debugf("Sequence index corruption (folder %v, file %v): sequence %d != expected %d", string(folder), f.Name, f.Sequence, seq) - } - } - if !fn(f) { - return nil - } - } - return dbi.Error() -} - -func (t *readOnlyTransaction) withGlobal(folder, prefix []byte, truncate bool, fn Iterator) error { - if len(prefix) > 0 { - unslashedPrefix := prefix - if bytes.HasSuffix(prefix, []byte{'/'}) { - unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1] - } else { - prefix = append(prefix, '/') - } - - if _, f, ok, err := t.getGlobal(nil, folder, unslashedPrefix, truncate); err != nil { - return err - } else if ok && !fn(f) { - return nil - } - } - - key, err := t.keyer.GenerateGlobalVersionKey(nil, folder, prefix) - if err != nil { - return err - } - dbi, err := t.NewPrefixIterator(key) - if err != nil { - return err - } - defer dbi.Release() - - var dk []byte - for dbi.Next() { - name := t.keyer.NameFromGlobalVersionKey(dbi.Key()) - if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) { - return nil - } - - var vl dbproto.VersionList - if err := proto.Unmarshal(dbi.Value(), &vl); err != nil { - return err - } - - var f protocol.FileInfo - dk, f, err = t.getGlobalFromVersionList(dk, folder, name, truncate, &vl) - if err != nil { - return err - } - - if !fn(f) { - return nil - } - } - if err != nil { - return err - } - return dbi.Error() -} - -func (t *readOnlyTransaction) withBlocksHash(folder, hash []byte, iterator Iterator) error { - key, err := t.keyer.GenerateBlockListMapKey(nil, folder, hash, nil) - if err != nil { - return err - } - - iter, err := t.NewPrefixIterator(key) - if err != nil { - return err - } - defer iter.Release() - - for iter.Next() { - file := string(t.keyer.NameFromBlockListMapKey(iter.Key())) - f, ok, err := t.getFile(folder, protocol.LocalDeviceID[:], []byte(osutil.NormalizedFilename(file))) - if err != nil { - return err - } - if !ok { - continue - } - f.Name = osutil.NativeFilename(f.Name) - - if !bytes.Equal(f.BlocksHash, hash) { - msg := "Mismatching block map list hashes" - t.evLogger.Log(events.Failure, fmt.Sprintln(msg, "in withBlocksHash")) - l.Warnf("%v: got %x expected %x", msg, f.BlocksHash, hash) - continue - } - - if f.IsDeleted() || f.IsInvalid() || f.IsDirectory() || f.IsSymlink() { - msg := "Found something of unexpected type in block list map" - t.evLogger.Log(events.Failure, fmt.Sprintln(msg, "in withBlocksHash")) - l.Warnf("%v: %s", msg, f) - continue - } - - if !iterator(f) { - break - } - } - - return iter.Error() -} - -func (t *readOnlyTransaction) availability(folder, file []byte) ([]protocol.DeviceID, error) { - vl, err := t.getGlobalVersions(nil, folder, file) - if backend.IsNotFound(err) { - return nil, nil - } - if err != nil { - return nil, err - } - - fv, ok := vlGetGlobal(vl) - if !ok { - return nil, nil - } - devices := make([]protocol.DeviceID, len(fv.Devices)) - for i, dev := range fv.Devices { - n, err := protocol.DeviceIDFromBytes(dev) - if err != nil { - return nil, err - } - devices[i] = n - } - - return devices, nil -} - -func (t *readOnlyTransaction) withNeed(folder, device []byte, truncate bool, fn Iterator) error { - if bytes.Equal(device, protocol.LocalDeviceID[:]) { - return t.withNeedLocal(folder, truncate, fn) - } - return t.withNeedIteratingGlobal(folder, device, truncate, fn) -} - -func (t *readOnlyTransaction) withNeedIteratingGlobal(folder, device []byte, truncate bool, fn Iterator) error { - key, err := t.keyer.GenerateGlobalVersionKey(nil, folder, nil) - if err != nil { - return err - } - dbi, err := t.NewPrefixIterator(key.WithoutName()) - if err != nil { - return err - } - defer dbi.Release() - - var dk []byte - devID, err := protocol.DeviceIDFromBytes(device) - if err != nil { - return err - } - for dbi.Next() { - var vl dbproto.VersionList - if err := proto.Unmarshal(dbi.Value(), &vl); err != nil { - return err - } - - globalFV, ok := vlGetGlobal(&vl) - if !ok { - return errEmptyGlobal - } - haveFV, have := vlGet(&vl, device) - - if !Need(globalFV, have, protocol.VectorFromWire(haveFV.Version)) { - continue - } - - name := t.keyer.NameFromGlobalVersionKey(dbi.Key()) - var gf protocol.FileInfo - dk, gf, err = t.getGlobalFromFileVersion(dk, folder, name, truncate, globalFV) - if err != nil { - return err - } - - if shouldDebug() { - if globalDev, ok := fvFirstDevice(globalFV); ok { - globalID, _ := protocol.DeviceIDFromBytes(globalDev) - l.Debugf("need folder=%q device=%v name=%q have=%v invalid=%v haveV=%v haveDeleted=%v globalV=%v globalDeleted=%v globalDev=%v", folder, devID, name, have, fvIsInvalid(haveFV), haveFV.Version, haveFV.Deleted, gf.FileVersion(), globalFV.Deleted, globalID) - } - } - if !fn(gf) { - return dbi.Error() - } - } - return dbi.Error() -} - -func (t *readOnlyTransaction) withNeedLocal(folder []byte, truncate bool, fn Iterator) error { - key, err := t.keyer.GenerateNeedFileKey(nil, folder, nil) - if err != nil { - return err - } - dbi, err := t.NewPrefixIterator(key.WithoutName()) - if err != nil { - return err - } - defer dbi.Release() - - var keyBuf []byte - var f protocol.FileInfo - var ok bool - for dbi.Next() { - keyBuf, f, ok, err = t.getGlobal(keyBuf, folder, t.keyer.NameFromGlobalVersionKey(dbi.Key()), truncate) - if err != nil { - return err - } - if !ok { - continue - } - if !fn(f) { - return nil - } - } - return dbi.Error() -} - -// A readWriteTransaction is a readOnlyTransaction plus a batch for writes. -// The batch will be committed on close() or by checkFlush() if it exceeds the -// batch size. -type readWriteTransaction struct { - backend.WriteTransaction - readOnlyTransaction - indirectionTracker -} - -type indirectionTracker interface { - recordIndirectionHashesForFile(f *protocol.FileInfo) -} - -func (db *Lowlevel) newReadWriteTransaction(hooks ...backend.CommitHook) (readWriteTransaction, error) { - tran, err := db.NewWriteTransaction(hooks...) - if err != nil { - return readWriteTransaction{}, err - } - return readWriteTransaction{ - WriteTransaction: tran, - readOnlyTransaction: db.readOnlyTransactionFromBackendTransaction(tran), - indirectionTracker: db, - }, nil -} - -func (t readWriteTransaction) Commit() error { - // The readOnlyTransaction must close after commit, because they may be - // backed by the same actual lower level transaction. - defer t.readOnlyTransaction.close() - return t.WriteTransaction.Commit() -} - -func (t readWriteTransaction) close() { - t.readOnlyTransaction.close() - t.WriteTransaction.Release() -} - -// putFile stores a file in the database, taking care of indirected fields. -func (t readWriteTransaction) putFile(fkey []byte, fi protocol.FileInfo) error { - var bkey []byte - - // Always set the blocks hash when there are blocks. - if len(fi.Blocks) > 0 { - fi.BlocksHash = protocol.BlocksHash(fi.Blocks) - } else { - fi.BlocksHash = nil - } - - // Indirect the blocks if the block list is large enough. - if len(fi.Blocks) > blocksIndirectionCutoff { - bkey = t.keyer.GenerateBlockListKey(bkey, fi.BlocksHash) - if _, err := t.Get(bkey); backend.IsNotFound(err) { - // Marshal the block list and save it - blocks := sliceutil.Map(fi.Blocks, protocol.BlockInfo.ToWire) - blocksBs := mustMarshal(&dbproto.BlockList{Blocks: blocks}) - if err := t.Put(bkey, blocksBs); err != nil { - return err - } - } else if err != nil { - return err - } - fi.Blocks = nil - } - - // Indirect the version vector if it's large enough. - if len(fi.Version.Counters) > versionIndirectionCutoff { - fi.VersionHash = protocol.VectorHash(fi.Version) - bkey = t.keyer.GenerateVersionKey(bkey, fi.VersionHash) - if _, err := t.Get(bkey); backend.IsNotFound(err) { - // Marshal the version vector and save it - versionBs := mustMarshal(fi.Version.ToWire()) - if err := t.Put(bkey, versionBs); err != nil { - return err - } - } else if err != nil { - return err - } - fi.Version = protocol.Vector{} - } else { - fi.VersionHash = nil - } - - t.indirectionTracker.recordIndirectionHashesForFile(&fi) - - fiBs := mustMarshal(fi.ToWire(true)) - return t.Put(fkey, fiBs) -} - -// updateGlobal adds this device+version to the version list for the given -// file. If the device is already present in the list, the version is updated. -// If the file does not have an entry in the global list, it is created. -func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, file protocol.FileInfo, meta *metadataTracker) ([]byte, error) { - deviceID, err := protocol.DeviceIDFromBytes(device) - if err != nil { - return nil, err - } - - l.Debugf("update global; folder=%q device=%v file=%q version=%v invalid=%v", folder, deviceID, file.Name, file.Version, file.IsInvalid()) - - fl, err := t.getGlobalVersionsByKey(gk) - if err != nil && !backend.IsNotFound(err) { - return nil, err - } - if fl == nil { - fl = &dbproto.VersionList{} - } - - globalFV, oldGlobalFV, removedFV, haveOldGlobal, haveRemoved, globalChanged, err := vlUpdate(fl, folder, device, file, t.readOnlyTransaction) - if err != nil { - return nil, err - } - - name := []byte(file.Name) - - l.Debugf(`new global for "%v" after update: %v`, file.Name, fl) - if err := t.Put(gk, mustMarshal(fl)); err != nil { - return nil, err - } - - // Only load those from db if actually needed - - var gotGlobal, gotOldGlobal bool - var global, oldGlobal protocol.FileInfo - - // Check the need of the device that was updated - // Must happen before updating global meta: If this is the first - // item from this device, it will be initialized with the global state. - - needBefore := haveOldGlobal && Need(oldGlobalFV, haveRemoved, protocol.VectorFromWire(removedFV.GetVersion())) - needNow := Need(globalFV, true, file.Version) - if needBefore { - if keyBuf, oldGlobal, err = t.getGlobalFromFileVersion(keyBuf, folder, name, true, oldGlobalFV); err != nil { - return nil, err - } - gotOldGlobal = true - meta.removeNeeded(deviceID, oldGlobal) - if !needNow && bytes.Equal(device, protocol.LocalDeviceID[:]) { - if keyBuf, err = t.updateLocalNeed(keyBuf, folder, name, false); err != nil { - return nil, err - } - } - } - if needNow { - keyBuf, global, err = t.getGlobalFromFileVersion(keyBuf, folder, name, true, globalFV) - if err != nil { - return nil, err - } - gotGlobal = true - meta.addNeeded(deviceID, global) - if !needBefore && bytes.Equal(device, protocol.LocalDeviceID[:]) { - if keyBuf, err = t.updateLocalNeed(keyBuf, folder, name, true); err != nil { - return nil, err - } - } - } - - // Update global size counter if necessary - - if !globalChanged { - // Neither the global state nor the needs of any devices, except - // the one updated, changed. - return keyBuf, nil - } - - // Remove the old global from the global size counter - if haveOldGlobal { - if !gotOldGlobal { - if keyBuf, oldGlobal, err = t.getGlobalFromFileVersion(keyBuf, folder, name, true, oldGlobalFV); err != nil { - return nil, err - } - } - // Remove the old global from the global size counter - meta.removeFile(protocol.GlobalDeviceID, oldGlobal) - } - - // Add the new global to the global size counter - if !gotGlobal { - if protocol.VectorFromWire(globalFV.Version).Equal(file.Version) { - // The inserted file is the global file - global = file - } else { - keyBuf, global, err = t.getGlobalFromFileVersion(keyBuf, folder, name, true, globalFV) - if err != nil { - return nil, err - } - } - } - meta.addFile(protocol.GlobalDeviceID, global) - - // check for local (if not already done before) - if !bytes.Equal(device, protocol.LocalDeviceID[:]) { - localFV, haveLocal := vlGet(fl, protocol.LocalDeviceID[:]) - localVersion := protocol.VectorFromWire(localFV.Version) - needBefore := haveOldGlobal && Need(oldGlobalFV, haveLocal, localVersion) - needNow := Need(globalFV, haveLocal, localVersion) - if needBefore { - meta.removeNeeded(protocol.LocalDeviceID, oldGlobal) - if !needNow { - if keyBuf, err = t.updateLocalNeed(keyBuf, folder, name, false); err != nil { - return nil, err - } - } - } - if needNow { - meta.addNeeded(protocol.LocalDeviceID, global) - if !needBefore { - if keyBuf, err = t.updateLocalNeed(keyBuf, folder, name, true); err != nil { - return nil, err - } - } - } - } - - for _, dev := range meta.devices() { - if bytes.Equal(dev[:], device) { - // Already handled above - continue - } - fv, have := vlGet(fl, dev[:]) - fvVersion := protocol.VectorFromWire(fv.Version) - if haveOldGlobal && Need(oldGlobalFV, have, fvVersion) { - meta.removeNeeded(dev, oldGlobal) - } - if Need(globalFV, have, fvVersion) { - meta.addNeeded(dev, global) - } - } - - return keyBuf, nil -} - -func (t readWriteTransaction) updateLocalNeed(keyBuf, folder, name []byte, add bool) ([]byte, error) { - var err error - keyBuf, err = t.keyer.GenerateNeedFileKey(keyBuf, folder, name) - if err != nil { - return nil, err - } - if add { - l.Debugf("local need insert; folder=%q, name=%q", folder, name) - err = t.Put(keyBuf, nil) - } else { - l.Debugf("local need delete; folder=%q, name=%q", folder, name) - err = t.Delete(keyBuf) - } - return keyBuf, err -} - -func Need(global *dbproto.FileVersion, haveLocal bool, localVersion protocol.Vector) bool { - // We never need an invalid file or a file without a valid version (just - // another way of expressing "invalid", really, until we fix that - // part...). - globalVersion := protocol.VectorFromWire(global.Version) - if fvIsInvalid(global) || globalVersion.IsEmpty() { - return false - } - // We don't need a deleted file if we don't have it. - if global.Deleted && !haveLocal { - return false - } - // We don't need the global file if we already have the same version. - if haveLocal && localVersion.GreaterEqual(globalVersion) { - return false - } - return true -} - -// removeFromGlobal removes the device from the global version list for the -// given file. If the version list is empty after this, the file entry is -// removed entirely. -func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device, file []byte, meta *metadataTracker) ([]byte, error) { - deviceID, err := protocol.DeviceIDFromBytes(device) - if err != nil { - return nil, err - } - - l.Debugf("remove from global; folder=%q device=%v file=%q", folder, deviceID, file) - - fl, err := t.getGlobalVersionsByKey(gk) - if backend.IsNotFound(err) { - // We might be called to "remove" a global version that doesn't exist - // if the first update for the file is already marked invalid. - return keyBuf, nil - } else if err != nil { - return nil, err - } - - oldGlobalFV, haveOldGlobal := vlGetGlobal(fl) - oldGlobalFV = fvCopy(oldGlobalFV) - - if !haveOldGlobal { - // Shouldn't ever happen, but doesn't hurt to handle. - t.evLogger.Log(events.Failure, "encountered empty global while removing item") - return keyBuf, t.Delete(gk) - } - - removedFV, haveRemoved, globalChanged := vlPop(fl, device) - if !haveRemoved { - // There is no version for the given device - return keyBuf, nil - } - - var global protocol.FileInfo - var gotGlobal bool - - globalFV, haveGlobal := vlGetGlobal(fl) - // Add potential needs of the removed device - if haveGlobal && !fvIsInvalid(globalFV) && Need(globalFV, false, protocol.Vector{}) && !Need(oldGlobalFV, haveRemoved, protocol.VectorFromWire(removedFV.Version)) { - keyBuf, global, err = t.getGlobalFromVersionList(keyBuf, folder, file, true, fl) - if err != nil { - return nil, err - } - gotGlobal = true - meta.addNeeded(deviceID, global) - if bytes.Equal(protocol.LocalDeviceID[:], device) { - if keyBuf, err = t.updateLocalNeed(keyBuf, folder, file, true); err != nil { - return nil, err - } - } - } - - // Global hasn't changed, abort early - if !globalChanged { - l.Debugf("new global after remove: %v", fl) - if err := t.Put(gk, mustMarshal(fl)); err != nil { - return nil, err - } - return keyBuf, nil - } - - var oldGlobal protocol.FileInfo - keyBuf, oldGlobal, err = t.getGlobalFromFileVersion(keyBuf, folder, file, true, oldGlobalFV) - if err != nil { - return nil, err - } - meta.removeFile(protocol.GlobalDeviceID, oldGlobal) - - // Remove potential device needs - shouldRemoveNeed := func(dev protocol.DeviceID) bool { - fv, have := vlGet(fl, dev[:]) - fvVersion := protocol.VectorFromWire(fv.Version) - if !Need(oldGlobalFV, have, fvVersion) { - return false // Didn't need it before - } - return !haveGlobal || !Need(globalFV, have, fvVersion) - } - if shouldRemoveNeed(protocol.LocalDeviceID) { - meta.removeNeeded(protocol.LocalDeviceID, oldGlobal) - if keyBuf, err = t.updateLocalNeed(keyBuf, folder, file, false); err != nil { - return nil, err - } - } - for _, dev := range meta.devices() { - if bytes.Equal(dev[:], device) { // Was the previous global - continue - } - if shouldRemoveNeed(dev) { - meta.removeNeeded(dev, oldGlobal) - } - } - - // Nothing left, i.e. nothing to add to the global counter below. - if len(fl.Versions) == 0 { - if err := t.Delete(gk); err != nil { - return nil, err - } - return keyBuf, nil - } - - // Add to global - if !gotGlobal { - keyBuf, global, err = t.getGlobalFromVersionList(keyBuf, folder, file, true, fl) - if err != nil { - return nil, err - } - } - meta.addFile(protocol.GlobalDeviceID, global) - - l.Debugf(`new global for "%s" after remove: %v`, file, fl) - if err := t.Put(gk, mustMarshal(fl)); err != nil { - return nil, err - } - - return keyBuf, nil -} - -func (t readWriteTransaction) deleteKeyPrefix(prefix []byte) error { - return t.deleteKeyPrefixMatching(prefix, func([]byte) bool { return true }) -} - -func (t readWriteTransaction) deleteKeyPrefixMatching(prefix []byte, match func(key []byte) bool) error { - dbi, err := t.NewPrefixIterator(prefix) - if err != nil { - return err - } - defer dbi.Release() - for dbi.Next() { - if !match(dbi.Key()) { - continue - } - if err := t.Delete(dbi.Key()); err != nil { - return err - } - } - return dbi.Error() -} - -func (t *readWriteTransaction) withAllFolderTruncated(folder []byte, fn func(device []byte, f protocol.FileInfo) bool) error { - key, err := t.keyer.GenerateDeviceFileKey(nil, folder, nil, nil) - if err != nil { - return err - } - dbi, err := t.NewPrefixIterator(key.WithoutNameAndDevice()) - if err != nil { - return err - } - defer dbi.Release() - - var gk, keyBuf []byte - for dbi.Next() { - device, ok := t.keyer.DeviceFromDeviceFileKey(dbi.Key()) - if !ok { - // Not having the device in the index is bad. Clear it. - if err := t.Delete(dbi.Key()); err != nil { - return err - } - continue - } - - f, err := t.unmarshalTrunc(dbi.Value(), true) - if err != nil { - return err - } - - switch f.Name { - case "", ".", "..", "/": // A few obviously invalid filenames - l.Infof("Dropping invalid filename %q from database", f.Name) - name := []byte(f.Name) - gk, err = t.keyer.GenerateGlobalVersionKey(gk, folder, name) - if err != nil { - return err - } - keyBuf, err = t.removeFromGlobal(gk, keyBuf, folder, device, name, nil) - if err != nil { - return err - } - if err := t.Delete(dbi.Key()); err != nil { - return err - } - continue - } - - if !fn(device, f) { - return nil - } - } - return dbi.Error() -} - -func mustMarshal(f proto.Message) []byte { - bs, err := proto.Marshal(f) - if err != nil { - panic(err) - } - return bs -} diff --git a/lib/db/util_test.go b/lib/db/util_test.go deleted file mode 100644 index cc7d7d6ce..000000000 --- a/lib/db/util_test.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. - -package db - -import ( - "encoding/json" - "errors" - "io" - "os" - "testing" - - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/events" - "github.com/syncthing/syncthing/lib/protocol" -) - -// writeJSONS serializes the database to a JSON stream that can be checked -// in to the repo and used for tests. -func writeJSONS(w io.Writer, db backend.Backend) { - it, err := db.NewPrefixIterator(nil) - if err != nil { - panic(err) - } - defer it.Release() - enc := json.NewEncoder(w) - for it.Next() { - err := enc.Encode(map[string][]byte{ - "k": it.Key(), - "v": it.Value(), - }) - if err != nil { - panic(err) - } - } -} - -// we know this function isn't generally used, nonetheless we want it in -// here and the linter to not complain. -var _ = writeJSONS - -// openJSONS reads a JSON stream file into a backend DB -func openJSONS(file string) (backend.Backend, error) { - fd, err := os.Open(file) - if err != nil { - return nil, err - } - dec := json.NewDecoder(fd) - - db := backend.OpenMemory() - - for { - var row map[string][]byte - - err := dec.Decode(&row) - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - - if err := db.Put(row["k"], row["v"]); err != nil { - return nil, err - } - } - - return db, nil -} - -func newLowlevel(t testing.TB, backend backend.Backend) *Lowlevel { - t.Helper() - ll, err := NewLowlevel(backend, events.NoopLogger) - if err != nil { - t.Fatal(err) - } - return ll -} - -func newLowlevelMemory(t testing.TB) *Lowlevel { - return newLowlevel(t, backend.OpenMemory()) -} - -func newFileSet(t testing.TB, folder string, db *Lowlevel) *FileSet { - t.Helper() - fset, err := NewFileSet(folder, db) - if err != nil { - t.Fatal(err) - } - return fset -} - -func snapshot(t testing.TB, fset *FileSet) *Snapshot { - t.Helper() - snap, err := fset.Snapshot() - if err != nil { - t.Fatal(err) - } - return snap -} - -// The following commented tests were used to generate jsons files to stdout for -// future tests and are kept here for reference (reuse). - -// TestGenerateIgnoredFilesDB generates a database with files with invalid flags, -// local and remote, in the format used in 0.14.48. -// func TestGenerateIgnoredFilesDB(t *testing.T) { -// db := OpenMemory() -// fs := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db) -// fs.Update(protocol.LocalDeviceID, []protocol.FileInfo{ -// { // invalid (ignored) file -// Name: "foo", -// Type: protocol.FileInfoTypeFile, -// Invalid: true, -// Version: protocol.Vector{Counters: []protocol.Counter{{ID: 1, Value: 1000}}}, -// }, -// { // regular file -// Name: "bar", -// Type: protocol.FileInfoTypeFile, -// Version: protocol.Vector{Counters: []protocol.Counter{{ID: 1, Value: 1001}}}, -// }, -// }) -// fs.Update(protocol.DeviceID{42}, []protocol.FileInfo{ -// { // invalid file -// Name: "baz", -// Type: protocol.FileInfoTypeFile, -// Invalid: true, -// Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1000}}}, -// }, -// { // regular file -// Name: "quux", -// Type: protocol.FileInfoTypeFile, -// Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1002}}}, -// }, -// }) -// writeJSONS(os.Stdout, db.DB) -// } - -// TestGenerateUpdate0to3DB generates a database with files with invalid flags, prefixed -// by a slash and other files to test database migration from version 0 to 3, in the -// format used in 0.14.45. -// func TestGenerateUpdate0to3DB(t *testing.T) { -// db := OpenMemory() -// fs := newFileSet(t, update0to3Folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db) -// for devID, files := range haveUpdate0to3 { -// fs.Update(devID, files) -// } -// writeJSONS(os.Stdout, db.DB) -// } - -// func TestGenerateUpdateTo10(t *testing.T) { -// db := newLowlevelMemory(t) -// defer db.Close() - -// if err := UpdateSchema(db); err != nil { -// t.Fatal(err) -// } - -// fs := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), db) - -// files := []protocol.FileInfo{ -// {Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Deleted: true, Sequence: 1}, -// {Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(2), Sequence: 2}, -// {Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Deleted: true, Sequence: 3}, -// } -// fs.Update(protocol.LocalDeviceID, files) -// files[1].Version = files[1].Version.Update(remoteDevice0.Short()) -// files[1].Deleted = true -// files[2].Version = files[2].Version.Update(remoteDevice0.Short()) -// files[2].Blocks = genBlocks(1) -// files[2].Deleted = false -// fs.Update(remoteDevice0, files) - -// fd, err := os.Create("./testdata/v1.4.0-updateTo10.json") -// if err != nil { -// panic(err) -// } -// defer fd.Close() -// writeJSONS(fd, db) -// } - -func TestFileInfoBatchError(t *testing.T) { - // Verify behaviour of the flush function returning an error. - - var errReturn error - var called int - b := NewFileInfoBatch(func([]protocol.FileInfo) error { - called += 1 - return errReturn - }) - - // Flush should work when the flush function error is nil - b.Append(protocol.FileInfo{Name: "test"}) - if err := b.Flush(); err != nil { - t.Fatalf("expected nil, got %v", err) - } - if called != 1 { - t.Fatalf("expected 1, got %d", called) - } - - // Flush should fail with an error retur - errReturn = errors.New("problem") - b.Append(protocol.FileInfo{Name: "test"}) - if err := b.Flush(); err != errReturn { - t.Fatalf("expected %v, got %v", errReturn, err) - } - if called != 2 { - t.Fatalf("expected 2, got %d", called) - } - - // Flush function should not be called again when it's already errored, - // same error should be returned by Flush() - if err := b.Flush(); err != errReturn { - t.Fatalf("expected %v, got %v", errReturn, err) - } - if called != 2 { - t.Fatalf("expected 2, got %d", called) - } - - // Reset should clear the error (and the file list) - errReturn = nil - b.Reset() - b.Append(protocol.FileInfo{Name: "test"}) - if err := b.Flush(); err != nil { - t.Fatalf("expected nil, got %v", err) - } - if called != 3 { - t.Fatalf("expected 3, got %d", called) - } -} diff --git a/lib/fs/filesystem_test.go b/lib/fs/filesystem_test.go index b0b239a5b..08e45ff50 100644 --- a/lib/fs/filesystem_test.go +++ b/lib/fs/filesystem_test.go @@ -189,7 +189,7 @@ func TestRepro9677MissingMtimeFS(t *testing.T) { testTime := time.Unix(1723491493, 123456789) // Create a file with an mtime FS entry - firstFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB)) + firstFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB, "")) // Create a file, set its mtime and check that we get the expected mtime when stat-ing. file, err := firstFS.Create(name) @@ -231,6 +231,6 @@ func TestRepro9677MissingMtimeFS(t *testing.T) { // be without mtime, even if requested: NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}) - newFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB)) + newFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB, "")) checkMtime(newFS) } diff --git a/lib/fs/mtimefs.go b/lib/fs/mtimefs.go index ba1d1ecc5..3f4e07211 100644 --- a/lib/fs/mtimefs.go +++ b/lib/fs/mtimefs.go @@ -7,21 +7,21 @@ package fs import ( - "errors" "time" ) // The database is where we store the virtual mtimes type database interface { - Bytes(key string) (data []byte, ok bool, err error) - PutBytes(key string, data []byte) error - Delete(key string) error + GetMtime(folder, name string) (ondisk, virtual time.Time) + PutMtime(folder, name string, ondisk, virtual time.Time) error + DeleteMtime(folder, name string) error } type mtimeFS struct { Filesystem chtimes func(string, time.Time, time.Time) error db database + folderID string caseInsensitive bool } @@ -34,16 +34,18 @@ func WithCaseInsensitivity(v bool) MtimeFSOption { } type optionMtime struct { - db database - options []MtimeFSOption + db database + folderID string + options []MtimeFSOption } // NewMtimeOption makes any filesystem provide nanosecond mtime precision, // regardless of what shenanigans the underlying filesystem gets up to. -func NewMtimeOption(db database, options ...MtimeFSOption) Option { +func NewMtimeOption(db database, folderID string, options ...MtimeFSOption) Option { return &optionMtime{ - db: db, - options: options, + db: db, + folderID: folderID, + options: options, } } @@ -52,6 +54,7 @@ func (o *optionMtime) apply(fs Filesystem) Filesystem { Filesystem: fs, chtimes: fs.Chtimes, // for mocking it out in the tests db: o.db, + folderID: o.folderID, } for _, opt := range o.options { opt(f) @@ -84,14 +87,11 @@ func (f *mtimeFS) Stat(name string) (FileInfo, error) { return nil, err } - mtimeMapping, err := f.load(name) - if err != nil { - return nil, err - } - if mtimeMapping.Real.Equal(info.ModTime()) { + ondisk, virtual := f.load(name) + if ondisk.Equal(info.ModTime()) { info = mtimeFileInfo{ FileInfo: info, - mtime: mtimeMapping.Virtual, + mtime: virtual, } } @@ -104,14 +104,11 @@ func (f *mtimeFS) Lstat(name string) (FileInfo, error) { return nil, err } - mtimeMapping, err := f.load(name) - if err != nil { - return nil, err - } - if mtimeMapping.Real.Equal(info.ModTime()) { + ondisk, virtual := f.load(name) + if ondisk.Equal(info.ModTime()) { info = mtimeFileInfo{ FileInfo: info, - mtime: mtimeMapping.Virtual, + mtime: virtual, } } @@ -150,43 +147,27 @@ func (*mtimeFS) wrapperType() filesystemWrapperType { return filesystemWrapperTypeMtime } -func (f *mtimeFS) save(name string, real, virtual time.Time) { +func (f *mtimeFS) save(name string, ondisk, virtual time.Time) { if f.caseInsensitive { name = UnicodeLowercaseNormalized(name) } - if real.Equal(virtual) { + if ondisk.Equal(virtual) { // If the virtual time and the real on disk time are equal we don't // need to store anything. - f.db.Delete(name) + _ = f.db.DeleteMtime(f.folderID, name) return } - mtime := MtimeMapping{ - Real: real, - Virtual: virtual, - } - bs, _ := mtime.Marshal() // Can't fail - f.db.PutBytes(name, bs) + _ = f.db.PutMtime(f.folderID, name, ondisk, virtual) } -func (f *mtimeFS) load(name string) (MtimeMapping, error) { +func (f *mtimeFS) load(name string) (ondisk, virtual time.Time) { if f.caseInsensitive { name = UnicodeLowercaseNormalized(name) } - data, exists, err := f.db.Bytes(name) - if err != nil { - return MtimeMapping{}, err - } else if !exists { - return MtimeMapping{}, nil - } - - var mtime MtimeMapping - if err := mtime.Unmarshal(data); err != nil { - return MtimeMapping{}, err - } - return mtime, nil + return f.db.GetMtime(f.folderID, name) } // The mtimeFileInfo is an os.FileInfo that lies about the ModTime(). @@ -211,14 +192,11 @@ func (f mtimeFile) Stat() (FileInfo, error) { return nil, err } - mtimeMapping, err := f.fs.load(f.Name()) - if err != nil { - return nil, err - } - if mtimeMapping.Real.Equal(info.ModTime()) { + ondisk, virtual := f.fs.load(f.Name()) + if ondisk.Equal(info.ModTime()) { info = mtimeFileInfo{ FileInfo: info, - mtime: mtimeMapping.Virtual, + mtime: virtual, } } @@ -230,38 +208,14 @@ func (f mtimeFile) unwrap() File { return f.File } -// MtimeMapping represents the mapping as stored in the database -type MtimeMapping struct { - // "Real" is the on disk timestamp - Real time.Time `json:"real"` - // "Virtual" is what want the timestamp to be - Virtual time.Time `json:"virtual"` -} - -func (t *MtimeMapping) Marshal() ([]byte, error) { - bs0, _ := t.Real.MarshalBinary() - bs1, _ := t.Virtual.MarshalBinary() - return append(bs0, bs1...), nil -} - -func (t *MtimeMapping) Unmarshal(bs []byte) error { - if err := t.Real.UnmarshalBinary(bs[:len(bs)/2]); err != nil { - return err - } - if err := t.Virtual.UnmarshalBinary(bs[len(bs)/2:]); err != nil { - return err - } - return nil -} - -func GetMtimeMapping(fs Filesystem, file string) (MtimeMapping, error) { +func GetMtimeMapping(fs Filesystem, file string) (ondisk, virtual time.Time) { fs, ok := unwrapFilesystem(fs, filesystemWrapperTypeMtime) if !ok { - return MtimeMapping{}, errors.New("failed to unwrap") + return time.Time{}, time.Time{} } mtimeFs, ok := fs.(*mtimeFS) if !ok { - return MtimeMapping{}, errors.New("unwrapping failed") + return time.Time{}, time.Time{} } return mtimeFs.load(file) } diff --git a/lib/fs/mtimefs_test.go b/lib/fs/mtimefs_test.go index 22f02b383..cce2acef6 100644 --- a/lib/fs/mtimefs_test.go +++ b/lib/fs/mtimefs_test.go @@ -226,20 +226,20 @@ func TestMtimeFSInsensitive(t *testing.T) { // The mapStore is a simple database -type mapStore map[string][]byte +type mapStore map[string][2]time.Time -func (s mapStore) PutBytes(key string, data []byte) error { - s[key] = data +func (s mapStore) PutMtime(_, name string, real, virtual time.Time) error { + s[name] = [2]time.Time{real, virtual} return nil } -func (s mapStore) Bytes(key string) (data []byte, ok bool, err error) { - data, ok = s[key] - return +func (s mapStore) GetMtime(_, name string) (real, virtual time.Time) { + v := s[name] + return v[0], v[1] } -func (s mapStore) Delete(key string) error { - delete(s, key) +func (s mapStore) DeleteMtime(_, name string) error { + delete(s, name) return nil } @@ -260,7 +260,7 @@ func newMtimeFS(path string, db database, options ...MtimeFSOption) *mtimeFS { } func newMtimeFSWithWalk(path string, db database, options ...MtimeFSOption) (*mtimeFS, *walkFilesystem) { - fs := NewFilesystem(FilesystemTypeBasic, path, NewMtimeOption(db, options...)) + fs := NewFilesystem(FilesystemTypeBasic, path, NewMtimeOption(db, "", options...)) wfs, _ := unwrapFilesystem(fs, filesystemWrapperTypeWalk) mfs, _ := unwrapFilesystem(fs, filesystemWrapperTypeMtime) return mfs.(*mtimeFS), wfs.(*walkFilesystem) diff --git a/lib/locations/locations.go b/lib/locations/locations.go index 197c686eb..3c38c7511 100644 --- a/lib/locations/locations.go +++ b/lib/locations/locations.go @@ -22,17 +22,18 @@ type LocationEnum string // Use strings as keys to make printout and serialization of the locations map // more meaningful. const ( - ConfigFile LocationEnum = "config" - CertFile LocationEnum = "certFile" - KeyFile LocationEnum = "keyFile" - HTTPSCertFile LocationEnum = "httpsCertFile" - HTTPSKeyFile LocationEnum = "httpsKeyFile" - Database LocationEnum = "database" - LogFile LocationEnum = "logFile" - PanicLog LocationEnum = "panicLog" - AuditLog LocationEnum = "auditLog" - GUIAssets LocationEnum = "guiAssets" - DefFolder LocationEnum = "defFolder" + ConfigFile LocationEnum = "config" + CertFile LocationEnum = "certFile" + KeyFile LocationEnum = "keyFile" + HTTPSCertFile LocationEnum = "httpsCertFile" + HTTPSKeyFile LocationEnum = "httpsKeyFile" + LegacyDatabase LocationEnum = "legacyDatabase" + Database LocationEnum = "database" + LogFile LocationEnum = "logFile" + PanicLog LocationEnum = "panicLog" + AuditLog LocationEnum = "auditLog" + GUIAssets LocationEnum = "guiAssets" + DefFolder LocationEnum = "defFolder" ) type BaseDirEnum string @@ -46,14 +47,15 @@ const ( // User's home directory, *not* --home flag UserHomeBaseDir BaseDirEnum = "userHome" - LevelDBDir = "index-v0.14.0.db" + levelDBDir = "index-v0.14.0.db" + databaseName = "index-v2.db" configFileName = "config.xml" defaultStateDir = ".local/state/syncthing" oldDefaultConfigDir = ".config/syncthing" ) // Platform dependent directories -var baseDirs = make(map[BaseDirEnum]string, 3) +var baseDirs = make(map[BaseDirEnum]string) func init() { userHome := userHomeDir() @@ -113,17 +115,18 @@ func GetBaseDir(baseDir BaseDirEnum) string { // Use the variables from baseDirs here var locationTemplates = map[LocationEnum]string{ - ConfigFile: "${config}/config.xml", - CertFile: "${config}/cert.pem", - KeyFile: "${config}/key.pem", - HTTPSCertFile: "${config}/https-cert.pem", - HTTPSKeyFile: "${config}/https-key.pem", - Database: "${data}/" + LevelDBDir, - LogFile: "${data}/syncthing.log", // --logfile on Windows - PanicLog: "${data}/panic-%{timestamp}.log", - AuditLog: "${data}/audit-%{timestamp}.log", - GUIAssets: "${config}/gui", - DefFolder: "${userHome}/Sync", + ConfigFile: "${config}/config.xml", + CertFile: "${config}/cert.pem", + KeyFile: "${config}/key.pem", + HTTPSCertFile: "${config}/https-cert.pem", + HTTPSKeyFile: "${config}/https-key.pem", + LegacyDatabase: "${data}/" + levelDBDir, + Database: "${data}/" + databaseName, + LogFile: "${data}/syncthing.log", // --logfile on Windows + PanicLog: "${data}/panic-%{timestamp}.log", + AuditLog: "${data}/audit-%{timestamp}.log", + GUIAssets: "${config}/gui", + DefFolder: "${userHome}/Sync", } var locations = make(map[LocationEnum]string) @@ -242,7 +245,8 @@ func unixDataDir(userHome, configDir, xdgDataHome, xdgStateHome string, fileExis // If a database exists at the config location, use that. This is the // most common case for both legacy (~/.config/syncthing) and current // (~/.local/state/syncthing) setups. - if fileExists(filepath.Join(configDir, LevelDBDir)) { + if fileExists(filepath.Join(configDir, databaseName)) || + fileExists(filepath.Join(configDir, levelDBDir)) { return configDir } @@ -251,14 +255,16 @@ func unixDataDir(userHome, configDir, xdgDataHome, xdgStateHome string, fileExis // but that's not what we did previously, so we retain the old behavior. if xdgDataHome != "" { candidate := filepath.Join(xdgDataHome, "syncthing") - if fileExists(filepath.Join(candidate, LevelDBDir)) { + if fileExists(filepath.Join(candidate, databaseName)) || + fileExists(filepath.Join(candidate, levelDBDir)) { return candidate } } // Legacy: if a database exists under ~/.config/syncthing, use that candidate := filepath.Join(userHome, oldDefaultConfigDir) - if fileExists(filepath.Join(candidate, LevelDBDir)) { + if fileExists(filepath.Join(candidate, databaseName)) || + fileExists(filepath.Join(candidate, levelDBDir)) { return candidate } diff --git a/lib/model/fakeconns_test.go b/lib/model/fakeconns_test.go index 831198276..524aa62d5 100644 --- a/lib/model/fakeconns_test.go +++ b/lib/model/fakeconns_test.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/syncthing/syncthing/internal/timeutil" "github.com/syncthing/syncthing/lib/protocol" protocolmocks "github.com/syncthing/syncthing/lib/protocol/mocks" "github.com/syncthing/syncthing/lib/rand" @@ -81,7 +82,7 @@ func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol Name: name, Type: ftype, Version: version, - Sequence: time.Now().UnixNano(), + Sequence: timeutil.StrictlyMonotonicNanos(), LocalFlags: localFlags, } switch ftype { @@ -108,15 +109,6 @@ func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol f.fileData[name] = data } -func (f *fakeConnection) addFileWithLocalFlags(name string, ftype protocol.FileInfoType, localFlags uint32) { - f.mut.Lock() - defer f.mut.Unlock() - - var version protocol.Vector - version = version.Update(f.id.Short()) - f.addFileLocked(name, 0, ftype, nil, version, localFlags) -} - func (f *fakeConnection) addFile(name string, flags uint32, ftype protocol.FileInfoType, data []byte) { f.mut.Lock() defer f.mut.Unlock() @@ -148,7 +140,7 @@ func (f *fakeConnection) deleteFile(name string) { fi.Deleted = true fi.ModifiedS = time.Now().Unix() fi.Version = fi.Version.Update(f.id.Short()) - fi.Sequence = time.Now().UnixNano() + fi.Sequence = timeutil.StrictlyMonotonicNanos() fi.Blocks = nil f.files = append(append(f.files[:i], f.files[i+1:]...), fi) diff --git a/lib/db/util.go b/lib/model/fileinfobatch.go similarity index 99% rename from lib/db/util.go rename to lib/model/fileinfobatch.go index 9251f3fdc..923988489 100644 --- a/lib/db/util.go +++ b/lib/model/fileinfobatch.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package db +package model import ( "github.com/syncthing/syncthing/lib/protocol" diff --git a/lib/model/fileinfobatch_test.go b/lib/model/fileinfobatch_test.go new file mode 100644 index 000000000..2ed7d9752 --- /dev/null +++ b/lib/model/fileinfobatch_test.go @@ -0,0 +1,64 @@ +// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/. + +package model + +import ( + "errors" + "testing" + + "github.com/syncthing/syncthing/lib/protocol" +) + +func TestFileInfoBatchError(t *testing.T) { + // Verify behaviour of the flush function returning an error. + + var errReturn error + var called int + b := NewFileInfoBatch(func([]protocol.FileInfo) error { + called += 1 + return errReturn + }) + + // Flush should work when the flush function error is nil + b.Append(protocol.FileInfo{Name: "test"}) + if err := b.Flush(); err != nil { + t.Fatalf("expected nil, got %v", err) + } + if called != 1 { + t.Fatalf("expected 1, got %d", called) + } + + // Flush should fail with an error retur + errReturn = errors.New("problem") + b.Append(protocol.FileInfo{Name: "test"}) + if err := b.Flush(); err != errReturn { + t.Fatalf("expected %v, got %v", errReturn, err) + } + if called != 2 { + t.Fatalf("expected 2, got %d", called) + } + + // Flush function should not be called again when it's already errored, + // same error should be returned by Flush() + if err := b.Flush(); err != errReturn { + t.Fatalf("expected %v, got %v", errReturn, err) + } + if called != 2 { + t.Fatalf("expected 2, got %d", called) + } + + // Reset should clear the error (and the file list) + errReturn = nil + b.Reset() + b.Append(protocol.FileInfo{Name: "test"}) + if err := b.Flush(); err != nil { + t.Fatalf("expected nil, got %v", err) + } + if called != 3 { + t.Fatalf("expected 3, got %d", called) + } +} diff --git a/lib/model/folder.go b/lib/model/folder.go index f8b8c501f..05a5b1032 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -15,8 +15,9 @@ import ( "sort" "time" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/ignore" @@ -46,7 +47,7 @@ type folder struct { model *model shortID protocol.ShortID - fset *db.FileSet + db db.DB ignores *ignore.Matcher mtimefs fs.Filesystem modTimeWindow time.Duration @@ -96,18 +97,18 @@ type puller interface { pull() (bool, error) // true when successful and should not be retried } -func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *semaphore.Semaphore, ver versioner.Versioner) folder { +func newFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *semaphore.Semaphore, ver versioner.Versioner) folder { f := folder{ stateTracker: newStateTracker(cfg.ID, evLogger), FolderConfiguration: cfg, - FolderStatisticsReference: stats.NewFolderStatisticsReference(model.db, cfg.ID), + FolderStatisticsReference: stats.NewFolderStatisticsReference(db.NewTyped(model.sdb, "folderstats/"+cfg.ID)), ioLimiter: ioLimiter, model: model, shortID: model.shortID, - fset: fset, + db: model.sdb, ignores: ignores, - mtimefs: cfg.Filesystem(fset), + mtimefs: cfg.Filesystem(fs.NewMtimeOption(model.sdb, cfg.ID)), modTimeWindow: cfg.ModTimeWindow(), done: make(chan struct{}), @@ -367,17 +368,11 @@ func (f *folder) pull() (success bool, err error) { }() // If there is nothing to do, don't even enter sync-waiting state. - abort := true - snap, err := f.dbSnapshot() + needCount, err := f.db.CountNeed(f.folderID, protocol.LocalDeviceID) if err != nil { return false, err } - snap.WithNeed(protocol.LocalDeviceID, func(intf protocol.FileInfo) bool { - abort = false - return false - }) - snap.Release() - if abort { + if needCount.TotalItems() == 0 { // Clears pull failures on items that were needed before, but aren't anymore. f.errorsMut.Lock() f.pullErrors = nil @@ -484,15 +479,10 @@ func (f *folder) scanSubdirs(subDirs []string) error { // Clean the list of subitems to ensure that we start at a known // directory, and don't scan subdirectories of things we've already // scanned. - snap, err := f.dbSnapshot() - if err != nil { - return err - } subDirs = unifySubs(subDirs, func(file string) bool { - _, ok := snap.Get(protocol.LocalDeviceID, file) - return ok + _, ok, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file) + return err == nil && ok }) - snap.Release() f.setState(FolderScanning) f.clearScanErrors(subDirs) @@ -546,7 +536,7 @@ const maxToRemove = 1000 type scanBatch struct { f *folder - updateBatch *db.FileInfoBatch + updateBatch *FileInfoBatch toRemove []string } @@ -555,7 +545,7 @@ func (f *folder) newScanBatch() *scanBatch { f: f, toRemove: make([]string, 0, maxToRemove), } - b.updateBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error { + b.updateBatch = NewFileInfoBatch(func(fs []protocol.FileInfo) error { if err := b.f.getHealthErrorWithoutIgnores(); err != nil { l.Debugf("Stopping scan of folder %s due to: %s", b.f.Description(), err) return err @@ -570,46 +560,56 @@ func (b *scanBatch) Remove(item string) { b.toRemove = append(b.toRemove, item) } -func (b *scanBatch) flushToRemove() { +func (b *scanBatch) flushToRemove() error { if len(b.toRemove) > 0 { - b.f.fset.RemoveLocalItems(b.toRemove) + if err := b.f.db.DropFilesNamed(b.f.folderID, protocol.LocalDeviceID, b.toRemove); err != nil { + return err + } b.toRemove = b.toRemove[:0] } + return nil } func (b *scanBatch) Flush() error { - b.flushToRemove() + if err := b.flushToRemove(); err != nil { + return err + } return b.updateBatch.Flush() } func (b *scanBatch) FlushIfFull() error { if len(b.toRemove) >= maxToRemove { - b.flushToRemove() + if err := b.flushToRemove(); err != nil { + return err + } } return b.updateBatch.FlushIfFull() } // Update adds the fileinfo to the batch for updating, and does a few checks. // It returns false if the checks result in the file not going to be updated or removed. -func (b *scanBatch) Update(fi protocol.FileInfo, snap *db.Snapshot) bool { +func (b *scanBatch) Update(fi protocol.FileInfo) (bool, error) { // Check for a "virtual" parent directory of encrypted files. We don't track // it, but check if anything still exists within and delete it otherwise. if b.f.Type == config.FolderTypeReceiveEncrypted && fi.IsDirectory() && protocol.IsEncryptedParent(fs.PathComponents(fi.Name)) { if names, err := b.f.mtimefs.DirNames(fi.Name); err == nil && len(names) == 0 { b.f.mtimefs.Remove(fi.Name) } - return false + return false, nil } // Resolve receive-only items which are identical with the global state or // the global item is our own receive-only item. - switch gf, ok := snap.GetGlobal(fi.Name); { + switch gf, ok, err := b.f.db.GetGlobalFile(b.f.folderID, fi.Name); { + case err != nil: + return false, err case !ok: case gf.IsReceiveOnlyChanged(): if fi.IsDeleted() { // Our item is deleted and the global item is our own receive only // file. No point in keeping track of that. b.Remove(fi.Name) - return true + l.Debugf("%v scanning: deleting deleted receive-only local-changed file: %v", b.f, fi) + return true, nil } case (b.f.Type == config.FolderTypeReceiveOnly || b.f.Type == config.FolderTypeReceiveEncrypted) && gf.IsEquivalentOptional(fi, protocol.FileInfoComparison{ @@ -621,20 +621,15 @@ func (b *scanBatch) Update(fi protocol.FileInfo, snap *db.Snapshot) bool { IgnoreXattrs: !b.f.SyncXattrs && !b.f.SendXattrs, }): // What we have locally is equivalent to the global file. - l.Debugf("%v scanning: Merging identical locally changed item with global", b.f, fi) + l.Debugf("%v scanning: Merging identical locally changed item with global: %v", b.f, fi) fi = gf } b.updateBatch.Append(fi) - return true + return true, nil } func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (int, error) { changes := 0 - snap, err := f.dbSnapshot() - if err != nil { - return changes, err - } - defer snap.Release() // If we return early e.g. due to a folder health error, the scan needs // to be cancelled. @@ -646,7 +641,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i Subs: subDirs, Matcher: f.ignores, TempLifetime: time.Duration(f.model.cfg.Options().KeepTemporariesH) * time.Hour, - CurrentFiler: cFiler{snap}, + CurrentFiler: cFiler{db: f.db, folder: f.folderID}, Filesystem: f.mtimefs, IgnorePerms: f.IgnorePerms, AutoNormalize: f.AutoNormalize, @@ -683,15 +678,19 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i return changes, err } - if batch.Update(res.File, snap) { + if ok, err := batch.Update(res.File); err != nil { + return 0, err + } else if ok { changes++ } switch f.Type { case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted: default: - if nf, ok := f.findRename(snap, res.File, alreadyUsedOrExisting); ok { - if batch.Update(nf, snap) { + if nf, ok := f.findRename(res.File, alreadyUsedOrExisting); ok { + if ok, err := batch.Update(nf); err != nil { + return 0, err + } else if ok { changes++ } } @@ -705,25 +704,22 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch var toIgnore []protocol.FileInfo ignoredParent := "" changes := 0 - snap, err := f.dbSnapshot() - if err != nil { - return 0, err - } - defer snap.Release() +outer: for _, sub := range subDirs { - var iterError error + for fi, err := range itererr.Zip(f.db.AllLocalFilesWithPrefix(f.folderID, protocol.LocalDeviceID, sub)) { + if err != nil { + return changes, err + } - snap.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi protocol.FileInfo) bool { select { case <-f.ctx.Done(): - return false + break outer default: } if err := batch.FlushIfFull(); err != nil { - iterError = err - return false + return 0, err } if ignoredParent != "" && !fs.IsParent(fi.Name, ignoredParent) { @@ -731,12 +727,13 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch l.Debugln("marking file as ignored", file) nf := file nf.SetIgnored() - if batch.Update(nf, snap) { + if ok, err := batch.Update(nf); err != nil { + return 0, err + } else if ok { changes++ } if err := batch.FlushIfFull(); err != nil { - iterError = err - return false + return 0, err } } toIgnore = toIgnore[:0] @@ -745,7 +742,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch switch ignored := f.ignores.Match(fi.Name).IsIgnored(); { case fi.IsIgnored() && ignored: - return true + continue case !fi.IsIgnored() && ignored: // File was not ignored at last pass but has been ignored. if fi.IsDirectory() { @@ -756,13 +753,15 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch // this path as the "highest" ignored parent ignoredParent = fi.Name } - return true + continue } l.Debugln("marking file as ignored", fi) nf := fi nf.SetIgnored() - if batch.Update(nf, snap) { + if ok, err := batch.Update(nf); err != nil { + return 0, err + } else if ok { changes++ } @@ -781,7 +780,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch toIgnore = toIgnore[:0] ignoredParent = "" } - return true + continue } nf := fi nf.SetDeleted(f.shortID) @@ -793,13 +792,17 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch nf.Version = protocol.Vector{} } l.Debugln("marking file as deleted", nf) - if batch.Update(nf, snap) { + if ok, err := batch.Update(nf); err != nil { + return 0, err + } else if ok { changes++ } case fi.IsDeleted() && fi.IsReceiveOnlyChanged(): switch f.Type { case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted: - switch gf, ok := snap.GetGlobal(fi.Name); { + switch gf, ok, err := f.db.GetGlobalFile(f.folderID, fi.Name); { + case err != nil: + return 0, err case !ok: case gf.IsReceiveOnlyChanged(): l.Debugln("removing deleted, receive-only item that is globally receive-only from db", fi) @@ -810,7 +813,9 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch // pretend it is a normal deleted file (nobody cares about that). l.Debugf("%v scanning: Marking globally deleted item as not locally changed: %v", f, fi.Name) fi.LocalFlags &^= protocol.FlagLocalReceiveOnly - if batch.Update(fi, snap) { + if ok, err := batch.Update(fi); err != nil { + return 0, err + } else if ok { changes++ } } @@ -819,14 +824,14 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch // deleted and just the folder type/local flags changed. fi.LocalFlags &^= protocol.FlagLocalReceiveOnly l.Debugln("removing receive-only flag on deleted item", fi) - if batch.Update(fi, snap) { + if ok, err := batch.Update(fi); err != nil { + return 0, err + } else if ok { changes++ } } } - - return true - }) + } select { case <-f.ctx.Done(): @@ -834,30 +839,28 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch default: } - if iterError == nil && len(toIgnore) > 0 { + if len(toIgnore) > 0 { for _, file := range toIgnore { l.Debugln("marking file as ignored", file) nf := file nf.SetIgnored() - if batch.Update(nf, snap) { + if ok, err := batch.Update(nf); err != nil { + return 0, err + } else if ok { changes++ } - if iterError = batch.FlushIfFull(); iterError != nil { - break + if err := batch.FlushIfFull(); err != nil { + return 0, err } } toIgnore = toIgnore[:0] } - - if iterError != nil { - return changes, iterError - } } return changes, nil } -func (f *folder) findRename(snap *db.Snapshot, file protocol.FileInfo, alreadyUsedOrExisting map[string]struct{}) (protocol.FileInfo, bool) { +func (f *folder) findRename(file protocol.FileInfo, alreadyUsedOrExisting map[string]struct{}) (protocol.FileInfo, bool) { if len(file.Blocks) == 0 || file.Size == 0 { return protocol.FileInfo{}, false } @@ -865,49 +868,58 @@ func (f *folder) findRename(snap *db.Snapshot, file protocol.FileInfo, alreadyUs found := false nf := protocol.FileInfo{} - snap.WithBlocksHash(file.BlocksHash, func(fi protocol.FileInfo) bool { +loop: + for fi, err := range itererr.Zip(f.db.AllLocalFilesWithBlocksHash(f.folderID, file.BlocksHash)) { + if err != nil { + return protocol.FileInfo{}, false + } + select { case <-f.ctx.Done(): - return false + break loop default: } if fi.Name == file.Name { alreadyUsedOrExisting[fi.Name] = struct{}{} - return true + continue } if _, ok := alreadyUsedOrExisting[fi.Name]; ok { - return true + continue } if fi.ShouldConflict() { - return true + continue } if f.ignores.Match(fi.Name).IsIgnored() { - return true + continue } // Only check the size. // No point checking block equality, as that uses BlocksHash comparison if that is set (which it will be). // No point checking BlocksHash comparison as WithBlocksHash already does that. if file.Size != fi.Size { - return true + continue } alreadyUsedOrExisting[fi.Name] = struct{}{} if !osutil.IsDeleted(f.mtimefs, fi.Name) { - return true + continue } - nf = fi + var ok bool + nf, ok, err = f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, fi.Name) + if err != nil || !ok || nf.Sequence != fi.Sequence { + continue + } nf.SetDeleted(f.shortID) nf.LocalFlags = f.localFlags found = true - return false - }) + break + } return nf, found } @@ -1216,20 +1228,26 @@ func (f *folder) ScheduleForceRescan(path string) { } } -func (f *folder) updateLocalsFromScanning(fs []protocol.FileInfo) { - f.updateLocals(fs) - +func (f *folder) updateLocalsFromScanning(fs []protocol.FileInfo) error { + if err := f.updateLocals(fs); err != nil { + return err + } f.emitDiskChangeEvents(fs, events.LocalChangeDetected) + return nil } -func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) { - f.updateLocals(fs) - +func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) error { + if err := f.updateLocals(fs); err != nil { + return err + } f.emitDiskChangeEvents(fs, events.RemoteChangeDetected) + return nil } -func (f *folder) updateLocals(fs []protocol.FileInfo) { - f.fset.Update(protocol.LocalDeviceID, fs) +func (f *folder) updateLocals(fs []protocol.FileInfo) error { + if err := f.db.Update(f.folderID, protocol.LocalDeviceID, fs); err != nil { + return err + } filenames := make([]string, len(fs)) f.forcedRescanPathsMut.Lock() @@ -1240,7 +1258,10 @@ func (f *folder) updateLocals(fs []protocol.FileInfo) { } f.forcedRescanPathsMut.Unlock() - seq := f.fset.Sequence(protocol.LocalDeviceID) + seq, err := f.db.GetDeviceSequence(f.folderID, protocol.LocalDeviceID) + if err != nil { + return err + } f.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{ "folder": f.ID, "items": len(fs), @@ -1248,6 +1269,7 @@ func (f *folder) updateLocals(fs []protocol.FileInfo) { "sequence": seq, "version": seq, // legacy for sequence }) + return nil } func (f *folder) emitDiskChangeEvents(fs []protocol.FileInfo, typeOfEvent events.EventType) { @@ -1294,23 +1316,19 @@ func (f *folder) handleForcedRescans() error { return nil } - batch := db.NewFileInfoBatch(func(fs []protocol.FileInfo) error { - f.fset.Update(protocol.LocalDeviceID, fs) - return nil + batch := NewFileInfoBatch(func(fs []protocol.FileInfo) error { + return f.db.Update(f.folderID, protocol.LocalDeviceID, fs) }) - snap, err := f.dbSnapshot() - if err != nil { - return err - } - defer snap.Release() - for _, path := range paths { if err := batch.FlushIfFull(); err != nil { return err } - fi, ok := snap.Get(protocol.LocalDeviceID, path) + fi, ok, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, path) + if err != nil { + return err + } if !ok { continue } @@ -1318,23 +1336,13 @@ func (f *folder) handleForcedRescans() error { batch.Append(fi) } - if err = batch.Flush(); err != nil { + if err := batch.Flush(); err != nil { return err } return f.scanSubdirs(paths) } -// dbSnapshots gets a snapshot from the fileset, and wraps any error -// in a svcutil.FatalErr. -func (f *folder) dbSnapshot() (*db.Snapshot, error) { - snap, err := f.fset.Snapshot() - if err != nil { - return nil, svcutil.AsFatalErr(err, svcutil.ExitError) - } - return snap, nil -} - // The exists function is expected to return true for all known paths // (excluding "" and ".") func unifySubs(dirs []string, exists func(dir string) bool) []string { @@ -1370,10 +1378,15 @@ func unifySubs(dirs []string, exists func(dir string) bool) []string { } type cFiler struct { - *db.Snapshot + db db.DB + folder string } // Implements scanner.CurrentFiler func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) { - return cf.Get(protocol.LocalDeviceID, file) + fi, ok, err := cf.db.GetDeviceFile(cf.folder, protocol.LocalDeviceID, file) + if err != nil || !ok { + return protocol.FileInfo{}, false + } + return fi, true } diff --git a/lib/model/folder_recvenc.go b/lib/model/folder_recvenc.go index 2892be116..69b1c5771 100644 --- a/lib/model/folder_recvenc.go +++ b/lib/model/folder_recvenc.go @@ -10,8 +10,8 @@ import ( "fmt" "sort" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/ignore" @@ -28,8 +28,8 @@ type receiveEncryptedFolder struct { *sendReceiveFolder } -func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { - f := &receiveEncryptedFolder{newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)} +func newReceiveEncryptedFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { + f := &receiveEncryptedFolder{newSendReceiveFolder(model, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)} f.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files return f } @@ -44,30 +44,27 @@ func (f *receiveEncryptedFolder) revert() error { f.setState(FolderScanning) defer f.setState(FolderIdle) - batch := db.NewFileInfoBatch(func(fs []protocol.FileInfo) error { + batch := NewFileInfoBatch(func(fs []protocol.FileInfo) error { f.updateLocalsFromScanning(fs) return nil }) - snap, err := f.dbSnapshot() - if err != nil { - return err - } - defer snap.Release() - var iterErr error var dirs []string - snap.WithHaveTruncated(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { - if iterErr = batch.FlushIfFull(); iterErr != nil { - return false + for fi, err := range itererr.Zip(f.db.AllLocalFiles(f.folderID, protocol.LocalDeviceID)) { + if err != nil { + return err + } + if err := batch.FlushIfFull(); err != nil { + return err } if !fi.IsReceiveOnlyChanged() || fi.IsDeleted() { - return true + continue } if fi.IsDirectory() { dirs = append(dirs, fi.Name) - return true + continue } if err := f.inWritableDir(f.mtimefs.Remove, fi.Name); err != nil && !fs.IsNotExist(err) { @@ -84,15 +81,10 @@ func (f *receiveEncryptedFolder) revert() error { // deleted, it will not show up as an unexpected file in the UI // anymore. batch.Append(fi) - - return true - }) - - f.revertHandleDirs(dirs, snap) - - if iterErr != nil { - return iterErr } + + f.revertHandleDirs(dirs) + if err := batch.Flush(); err != nil { return err } @@ -103,7 +95,7 @@ func (f *receiveEncryptedFolder) revert() error { return nil } -func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string, snap *db.Snapshot) { +func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string) { if len(dirs) == 0 { return } @@ -114,7 +106,7 @@ func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string, snap *db.Snapsh sort.Sort(sort.Reverse(sort.StringSlice(dirs))) for _, dir := range dirs { - if err := f.deleteDirOnDisk(dir, snap, scanChan); err != nil { + if err := f.deleteDirOnDisk(dir, scanChan); err != nil { f.newScanError(dir, fmt.Errorf("deleting unexpected dir: %w", err)) } scanChan <- dir diff --git a/lib/model/folder_recvonly.go b/lib/model/folder_recvonly.go index 501a61e34..535483c55 100644 --- a/lib/model/folder_recvonly.go +++ b/lib/model/folder_recvonly.go @@ -10,8 +10,8 @@ import ( "sort" "time" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/ignore" "github.com/syncthing/syncthing/lib/protocol" @@ -57,8 +57,8 @@ type receiveOnlyFolder struct { *sendReceiveFolder } -func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { - sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder) +func newReceiveOnlyFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { + sr := newSendReceiveFolder(model, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder) sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files return &receiveOnlyFolder{sr} } @@ -83,30 +83,31 @@ func (f *receiveOnlyFolder) revert() error { scanChan: scanChan, } - batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error { + batch := NewFileInfoBatch(func(files []protocol.FileInfo) error { f.updateLocalsFromScanning(files) return nil }) - snap, err := f.dbSnapshot() - if err != nil { - return err - } - defer snap.Release() - snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { + + for fi, err := range itererr.Zip(f.db.AllLocalFiles(f.folderID, protocol.LocalDeviceID)) { + if err != nil { + return err + } if !fi.IsReceiveOnlyChanged() { // We're only interested in files that have changed locally in // receive only mode. - return true + continue } fi.LocalFlags &^= protocol.FlagLocalReceiveOnly - switch gf, ok := snap.GetGlobal(fi.Name); { + switch gf, ok, err := f.db.GetGlobalFile(f.folderID, fi.Name); { + case err != nil: + return err case !ok: - msg := "Unexpected global file that we have locally" + msg := "Unexpectedly missing global file that we have locally" l.Debugf("%v revert: %v: %v", f, msg, fi.Name) f.evLogger.Log(events.Failure, msg) - return true + continue case gf.IsReceiveOnlyChanged(): // The global file is our own. A revert then means to delete it. // We'll delete files directly, directories get queued and @@ -115,13 +116,13 @@ func (f *receiveOnlyFolder) revert() error { fi.Version = protocol.Vector{} // if this file ever resurfaces anywhere we want our delete to be strictly older break } - handled, err := delQueue.handle(fi, snap) + l.Debugf("Revert: deleting %s: %v\n", fi.Name, err) + handled, err := delQueue.handle(fi) if err != nil { - l.Infof("Revert: deleting %s: %v\n", fi.Name, err) - return true // continue + continue } if !handled { - return true // continue + continue } fi.SetDeleted(f.shortID) fi.Version = protocol.Vector{} // if this file ever resurfaces anywhere we want our delete to be strictly older @@ -144,13 +145,13 @@ func (f *receiveOnlyFolder) revert() error { batch.Append(fi) _ = batch.FlushIfFull() - - return true - }) - _ = batch.Flush() + } + if err := batch.Flush(); err != nil { + return err + } // Handle any queued directories - deleted, err := delQueue.flush(snap) + deleted, err := delQueue.flush() if err != nil { l.Infoln("Revert:", err) } @@ -179,15 +180,15 @@ func (f *receiveOnlyFolder) revert() error { // directories for last. type deleteQueue struct { handler interface { - deleteItemOnDisk(item protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) error - deleteDirOnDisk(dir string, snap *db.Snapshot, scanChan chan<- string) error + deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) error + deleteDirOnDisk(dir string, scanChan chan<- string) error } ignores *ignore.Matcher dirs []string scanChan chan<- string } -func (q *deleteQueue) handle(fi protocol.FileInfo, snap *db.Snapshot) (bool, error) { +func (q *deleteQueue) handle(fi protocol.FileInfo) (bool, error) { // Things that are ignored but not marked deletable are not processed. ign := q.ignores.Match(fi.Name) if ign.IsIgnored() && !ign.IsDeletable() { @@ -201,11 +202,11 @@ func (q *deleteQueue) handle(fi protocol.FileInfo, snap *db.Snapshot) (bool, err } // Kill it. - err := q.handler.deleteItemOnDisk(fi, snap, q.scanChan) + err := q.handler.deleteItemOnDisk(fi, q.scanChan) return true, err } -func (q *deleteQueue) flush(snap *db.Snapshot) ([]string, error) { +func (q *deleteQueue) flush() ([]string, error) { // Process directories from the leaves inward. sort.Sort(sort.Reverse(sort.StringSlice(q.dirs))) @@ -213,7 +214,7 @@ func (q *deleteQueue) flush(snap *db.Snapshot) ([]string, error) { var deleted []string for _, dir := range q.dirs { - if err := q.handler.deleteDirOnDisk(dir, snap, q.scanChan); err == nil { + if err := q.handler.deleteDirOnDisk(dir, q.scanChan); err == nil { deleted = append(deleted, dir) } else if firstError == nil { firstError = err diff --git a/lib/model/folder_recvonly_test.go b/lib/model/folder_recvonly_test.go index 729b68ebb..3abf6bf08 100644 --- a/lib/model/folder_recvonly_test.go +++ b/lib/model/folder_recvonly_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" @@ -28,7 +29,7 @@ func TestRecvOnlyRevertDeletes(t *testing.T) { m, f, wcfgCancel := setupROFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() defer cleanupModel(m) conn := addFakeConn(m, device1, f.ID) @@ -46,9 +47,11 @@ func TestRecvOnlyRevertDeletes(t *testing.T) { // Send and index update for the known stuff must(t, m.Index(conn, &protocol.Index{Folder: "ro", Files: knownFiles})) - f.updateLocalsFromScanning(knownFiles) + if err := f.updateLocalsFromScanning(knownFiles); err != nil { + t.Fatal(err) + } - size := globalSize(t, m, "ro") + size := mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) } @@ -59,15 +62,15 @@ func TestRecvOnlyRevertDeletes(t *testing.T) { // We should now have two files and two directories, with global state unchanged. - size = globalSize(t, m, "ro") + size = mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { - t.Fatalf("Global: expected 2 files and 2 directories: %+v", size) + t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 2 || size.Directories != 2 { t.Fatalf("Local: expected 2 files and 2 directories: %+v", size) } - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories == 0 { t.Fatalf("ROChanged: expected something: %+v", size) } @@ -92,11 +95,11 @@ func TestRecvOnlyRevertDeletes(t *testing.T) { // We should now have one file and directory again. - size = globalSize(t, m, "ro") + size = mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Global: expected 1 files and 1 directories: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Local: expected 1 files and 1 directories: %+v", size) } @@ -110,7 +113,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) { m, f, wcfgCancel := setupROFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() defer cleanupModel(m) conn := addFakeConn(m, device1, f.ID) @@ -131,19 +134,19 @@ func TestRecvOnlyRevertNeeds(t *testing.T) { // Everything should be in sync. - size := globalSize(t, m, "ro") + size := mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Local: expected 1 file and 1 directory: %+v", size) } - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files+size.Directories > 0 { t.Fatalf("Need: expected nothing: %+v", size) } - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories > 0 { t.Fatalf("ROChanged: expected nothing: %+v", size) } @@ -159,20 +162,20 @@ func TestRecvOnlyRevertNeeds(t *testing.T) { // We now have a newer file than the rest of the cluster. Global state should reflect this. - size = globalSize(t, m, "ro") + size = mustV(m.GlobalSize("ro")) const sizeOfDir = 128 if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) { t.Fatalf("Global: expected no change due to the new file: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) { t.Fatalf("Local: expected the new file to be reflected: %+v", size) } - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files+size.Directories > 0 { t.Fatalf("Need: expected nothing: %+v", size) } - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories == 0 { t.Fatalf("ROChanged: expected something: %+v", size) } @@ -181,15 +184,15 @@ func TestRecvOnlyRevertNeeds(t *testing.T) { m.Revert("ro") - size = globalSize(t, m, "ro") + size = mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) { t.Fatalf("Global: expected the global size to revert: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) { t.Fatalf("Local: expected the local size to remain: %+v", size) } - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Bytes != int64(len(oldData)) { t.Fatalf("Local: expected to need the old file data: %+v", size) } @@ -200,7 +203,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) { m, f, wcfgCancel := setupROFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() defer cleanupModel(m) conn := addFakeConn(m, device1, f.ID) @@ -221,19 +224,19 @@ func TestRecvOnlyUndoChanges(t *testing.T) { // Everything should be in sync. - size := globalSize(t, m, "ro") + size := mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Local: expected 1 file and 1 directory: %+v", size) } - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files+size.Directories > 0 { t.Fatalf("Need: expected nothing: %+v", size) } - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories > 0 { t.Fatalf("ROChanged: expected nothing: %+v", size) } @@ -246,7 +249,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) { must(t, m.ScanFolder("ro")) - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files != 2 { t.Fatalf("Receive only: expected 2 files: %+v", size) } @@ -259,7 +262,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) { must(t, m.ScanFolder("ro")) - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories+size.Deleted != 0 { t.Fatalf("Receive only: expected all zero: %+v", size) } @@ -270,7 +273,7 @@ func TestRecvOnlyDeletedRemoteDrop(t *testing.T) { m, f, wcfgCancel := setupROFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() defer cleanupModel(m) conn := addFakeConn(m, device1, f.ID) @@ -291,19 +294,19 @@ func TestRecvOnlyDeletedRemoteDrop(t *testing.T) { // Everything should be in sync. - size := globalSize(t, m, "ro") + size := mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Local: expected 1 file and 1 directory: %+v", size) } - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files+size.Directories > 0 { t.Fatalf("Need: expected nothing: %+v", size) } - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories > 0 { t.Fatalf("ROChanged: expected nothing: %+v", size) } @@ -314,17 +317,17 @@ func TestRecvOnlyDeletedRemoteDrop(t *testing.T) { must(t, m.ScanFolder("ro")) - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Deleted != 1 { t.Fatalf("Receive only: expected 1 deleted: %+v", size) } // Drop the remote - f.fset.Drop(device1) + f.db.DropAllFiles("ro", device1) must(t, m.ScanFolder("ro")) - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Deleted != 0 { t.Fatalf("Receive only: expected no deleted: %+v", size) } @@ -335,7 +338,7 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) { m, f, wcfgCancel := setupROFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() defer cleanupModel(m) conn := addFakeConn(m, device1, f.ID) @@ -356,19 +359,19 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) { // Everything should be in sync. - size := globalSize(t, m, "ro") + size := mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Local: expected 1 file and 1 directory: %+v", size) } - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files+size.Directories > 0 { t.Fatalf("Need: expected nothing: %+v", size) } - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories > 0 { t.Fatalf("ROChanged: expected nothing: %+v", size) } @@ -382,7 +385,7 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) { must(t, m.ScanFolder("ro")) - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files != 2 { t.Fatalf("Receive only: expected 2 files: %+v", size) } @@ -390,17 +393,17 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) { // Do the same changes on the remote files := make([]protocol.FileInfo, 0, 2) - snap := fsetSnapshot(t, f.fset) - snap.WithHave(protocol.LocalDeviceID, func(f protocol.FileInfo) bool { + for f, err := range itererr.Zip(f.db.AllLocalFiles("ro", protocol.LocalDeviceID)) { + if err != nil { + t.Fatal(err) + } if f.Name != file && f.Name != knownFile { - return true + continue } f.LocalFlags = 0 f.Version = protocol.Vector{}.Update(device1.Short()) files = append(files, f) - return true - }) - snap.Release() + } must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: "ro", Files: files})) // Ensure the pull to resolve conflicts (content identical) happened @@ -409,7 +412,10 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) { return nil })) - size = receiveOnlyChangedSize(t, m, "ro") + size, err := m.ReceiveOnlySize("ro") + if err != nil { + t.Fatal(err) + } if size.Files+size.Directories+size.Deleted != 0 { t.Fatalf("Receive only: expected all zero: %+v", size) } @@ -424,7 +430,7 @@ func TestRecvOnlyRevertOwnID(t *testing.T) { m, f, wcfgCancel := setupROFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() defer cleanupModel(m) conn := addFakeConn(m, device1, f.ID) @@ -484,7 +490,7 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) { m, f, wcfgCancel := setupROFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() defer cleanupModel(m) conn := addFakeConn(m, device1, f.ID) @@ -505,19 +511,19 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) { // Everything should be in sync. - size := globalSize(t, m, "ro") + size := mustV(m.GlobalSize("ro")) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) } - size = localSize(t, m, "ro") + size = mustV(m.LocalSize("ro", protocol.LocalDeviceID)) if size.Files != 1 || size.Directories != 1 { t.Fatalf("Local: expected 1 file and 1 directory: %+v", size) } - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files+size.Directories > 0 { t.Fatalf("Need: expected nothing: %+v", size) } - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files+size.Directories > 0 { t.Fatalf("ROChanged: expected nothing: %+v", size) } @@ -528,7 +534,7 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) { must(t, m.ScanFolder("ro")) - size = receiveOnlyChangedSize(t, m, "ro") + size = mustV(m.ReceiveOnlySize("ro")) if size.Files != 1 { t.Fatalf("Receive only: expected 1 file: %+v", size) } @@ -541,7 +547,7 @@ func TestRecvOnlyLocalChangeDoesNotCauseConflict(t *testing.T) { must(t, m.ScanFolder("ro")) - size = needSizeLocal(t, m, "ro") + size = mustV(m.NeedSize("ro", protocol.LocalDeviceID)) if size.Files != 0 { t.Fatalf("Need: expected nothing: %+v", size) } @@ -577,7 +583,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi ModifiedS: fi.ModTime().Unix(), ModifiedNs: int32(fi.ModTime().Nanosecond()), Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}}, - Sequence: 42, + Sequence: 43, Blocks: blocks, }, } diff --git a/lib/model/folder_sendonly.go b/lib/model/folder_sendonly.go index 4d5d36b19..228452d2e 100644 --- a/lib/model/folder_sendonly.go +++ b/lib/model/folder_sendonly.go @@ -7,8 +7,8 @@ package model import ( + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/ignore" "github.com/syncthing/syncthing/lib/protocol" @@ -24,9 +24,9 @@ type sendOnlyFolder struct { folder } -func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { +func newSendOnlyFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { f := &sendOnlyFolder{ - folder: newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, nil), + folder: newFolder(model, ignores, cfg, evLogger, ioLimiter, nil), } f.folder.puller = f return f @@ -38,36 +38,36 @@ func (*sendOnlyFolder) PullErrors() []FileError { // pull checks need for files that only differ by metadata (no changes on disk) func (f *sendOnlyFolder) pull() (bool, error) { - batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error { + batch := NewFileInfoBatch(func(files []protocol.FileInfo) error { f.updateLocalsFromPulling(files) return nil }) - snap, err := f.dbSnapshot() - if err != nil { - return false, err - } - defer snap.Release() - snap.WithNeed(protocol.LocalDeviceID, func(file protocol.FileInfo) bool { - batch.FlushIfFull() + for file, err := range itererr.Zip(f.db.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) { + if err != nil { + return false, err + } + if err := batch.FlushIfFull(); err != nil { + return false, err + } if f.ignores.Match(file.FileName()).IsIgnored() { file.SetIgnored() batch.Append(file) l.Debugln(f, "Handling ignored file", file) - return true + continue } - curFile, ok := snap.Get(protocol.LocalDeviceID, file.FileName()) + curFile, ok, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.FileName()) + if err != nil { + return false, err + } if !ok { - if file.IsInvalid() { - // Global invalid file just exists for need accounting + if file.IsInvalid() || file.IsDeleted() { + // Accept the file for accounting purposes batch.Append(file) - } else if file.IsDeleted() { - l.Debugln("Should never get a deleted file as needed when we don't have it") - f.evLogger.Log(events.Failure, "got deleted file that doesn't exist locally as needed when pulling on send-only") } - return true + continue } if !file.IsEquivalentOptional(curFile, protocol.FileInfoComparison{ @@ -76,14 +76,12 @@ func (f *sendOnlyFolder) pull() (bool, error) { IgnoreOwnership: !f.SyncOwnership, IgnoreXattrs: !f.SyncXattrs, }) { - return true + continue } batch.Append(file) l.Debugln(f, "Merging versions of identical file", file) - - return true - }) + } batch.Flush() @@ -100,25 +98,31 @@ func (f *sendOnlyFolder) override() error { f.setState(FolderScanning) defer f.setState(FolderIdle) - batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error { + batch := NewFileInfoBatch(func(files []protocol.FileInfo) error { f.updateLocalsFromScanning(files) return nil }) - snap, err := f.dbSnapshot() - if err != nil { - return err - } - defer snap.Release() - snap.WithNeed(protocol.LocalDeviceID, func(need protocol.FileInfo) bool { - _ = batch.FlushIfFull() - have, ok := snap.Get(protocol.LocalDeviceID, need.Name) + for need, err := range itererr.Zip(f.db.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) { + if err != nil { + return err + } + if err := batch.FlushIfFull(); err != nil { + return err + } + + have, haveOk, err := f.db.GetDeviceFile(f.folderID, protocol.LocalDeviceID, need.Name) + if err != nil { + return err + } + // Don't override files that are in a bad state (ignored, // unsupported, must rescan, ...). - if ok && have.IsInvalid() { - return true + if haveOk && have.IsInvalid() { + continue } - if !ok || have.Name != need.Name { + + if !haveOk || have.Name != need.Name { // We are missing the file need.SetDeleted(f.shortID) } else { @@ -128,7 +132,6 @@ func (f *sendOnlyFolder) override() error { } need.Sequence = 0 batch.Append(need) - return true - }) + } return batch.Flush() } diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 8f6544126..5011cf9cd 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -19,9 +19,9 @@ import ( "strings" "time" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/ignore" @@ -129,9 +129,9 @@ type sendReceiveFolder struct { tempPullErrors map[string]string // pull errors that might be just transient } -func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { +func newSendReceiveFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *semaphore.Semaphore) service { f := &sendReceiveFolder{ - folder: newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, ver), + folder: newFolder(model, ignores, cfg, evLogger, ioLimiter, ver), queue: newJobQueue(), blockPullReorderer: newBlockPullReorderer(cfg.BlockPullOrder, model.id, cfg.DeviceIDs()), writeLimiter: semaphore.New(cfg.MaxConcurrentWrites), @@ -240,12 +240,6 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error) f.tempPullErrors = make(map[string]string) f.errorsMut.Unlock() - snap, err := f.dbSnapshot() - if err != nil { - return 0, err - } - defer snap.Release() - pullChan := make(chan pullBlockState) copyChan := make(chan copyBlocksState) finisherChan := make(chan *sharedPullerState) @@ -277,18 +271,18 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error) pullWg.Add(1) go func() { // pullerRoutine finishes when pullChan is closed - f.pullerRoutine(snap, pullChan, finisherChan) + f.pullerRoutine(pullChan, finisherChan) pullWg.Done() }() doneWg.Add(1) // finisherRoutine finishes when finisherChan is closed go func() { - f.finisherRoutine(snap, finisherChan, dbUpdateChan, scanChan) + f.finisherRoutine(finisherChan, dbUpdateChan, scanChan) doneWg.Done() }() - changed, fileDeletions, dirDeletions, err := f.processNeeded(snap, dbUpdateChan, copyChan, scanChan) + changed, fileDeletions, dirDeletions, err := f.processNeeded(dbUpdateChan, copyChan, scanChan) // Signal copy and puller routines that we are done with the in data for // this iteration. Wait for them to finish. @@ -303,7 +297,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error) doneWg.Wait() if err == nil { - f.processDeletions(fileDeletions, dirDeletions, snap, dbUpdateChan, scanChan) + f.processDeletions(fileDeletions, dirDeletions, dbUpdateChan, scanChan) } // Wait for db updates and scan scheduling to complete @@ -315,7 +309,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error) return changed, err } -func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) { +func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) { changed := 0 var dirDeletions []protocol.FileInfo fileDeletions := map[string]protocol.FileInfo{} @@ -325,16 +319,20 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- // Regular files to pull goes into the file queue, everything else // (directories, symlinks and deletes) goes into the "process directly" // pile. - snap.WithNeed(protocol.LocalDeviceID, func(file protocol.FileInfo) bool { +loop: + for file, err := range itererr.Zip(f.model.sdb.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, f.Order, 0, 0)) { + if err != nil { + return changed, nil, nil, err + } select { case <-f.ctx.Done(): - return false + break loop default: } if f.IgnoreDelete && file.IsDeleted() { l.Debugln(f, "ignore file deletion (config)", file.FileName()) - return true + continue } changed++ @@ -366,9 +364,12 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- // files to delete inside them before we get to that point. dirDeletions = append(dirDeletions, file) } else if file.IsSymlink() { - f.deleteFile(file, snap, dbUpdateChan, scanChan) + f.deleteFile(file, dbUpdateChan, scanChan) } else { - df, ok := snap.Get(protocol.LocalDeviceID, file.Name) + df, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + if err != nil { + return changed, nil, nil, err + } // Local file can be already deleted, but with a lower version // number, hence the deletion coming in again as part of // WithNeed, furthermore, the file can simply be of the wrong @@ -384,7 +385,10 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- } case file.Type == protocol.FileInfoTypeFile: - curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name) + curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + if err != nil { + return changed, nil, nil, err + } if hasCurFile && file.BlocksEqual(curFile) { // We are supposed to copy the entire file, and then fetch nothing. We // are only updating metadata, so we don't actually *need* to make the @@ -396,7 +400,7 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- } case (build.IsWindows || build.IsAndroid) && file.IsSymlink(): - if err := f.handleSymlinkCheckExisting(file, snap, scanChan); err != nil { + if err := f.handleSymlinkCheckExisting(file, scanChan); err != nil { f.newPullError(file.Name, fmt.Errorf("handling unsupported symlink: %w", err)) break } @@ -407,22 +411,20 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- case file.IsDirectory() && !file.IsSymlink(): l.Debugln(f, "Handling directory", file.Name) if f.checkParent(file.Name, scanChan) { - f.handleDir(file, snap, dbUpdateChan, scanChan) + f.handleDir(file, dbUpdateChan, scanChan) } case file.IsSymlink(): l.Debugln(f, "Handling symlink", file.Name) if f.checkParent(file.Name, scanChan) { - f.handleSymlink(file, snap, dbUpdateChan, scanChan) + f.handleSymlink(file, dbUpdateChan, scanChan) } default: l.Warnln(file) panic("unhandleable item type, can't happen") } - - return true - }) + } select { case <-f.ctx.Done(): @@ -430,23 +432,6 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- default: } - // Now do the file queue. Reorder it according to configuration. - - switch f.Order { - case config.PullOrderRandom: - f.queue.Shuffle() - case config.PullOrderAlphabetic: - // The queue is already in alphabetic order. - case config.PullOrderSmallestFirst: - f.queue.SortSmallestFirst() - case config.PullOrderLargestFirst: - f.queue.SortLargestFirst() - case config.PullOrderOldestFirst: - f.queue.SortOldestFirst() - case config.PullOrderNewestFirst: - f.queue.SortNewestFirst() - } - // Process the file queue. nextFile: @@ -462,7 +447,10 @@ nextFile: break } - fi, ok := snap.GetGlobal(fileName) + fi, ok, err := f.model.sdb.GetGlobalFile(f.folderID, fileName) + if err != nil { + return changed, nil, nil, err + } if !ok { // File is no longer in the index. Mark it as done and drop it. f.queue.Done(fileName) @@ -489,7 +477,7 @@ nextFile: // desired state with the delete bit set is in the deletion // map. desired := fileDeletions[candidate.Name] - if err := f.renameFile(candidate, desired, fi, snap, dbUpdateChan, scanChan); err != nil { + if err := f.renameFile(candidate, desired, fi, dbUpdateChan, scanChan); err != nil { l.Debugf("rename shortcut for %s failed: %s", fi.Name, err.Error()) // Failed to rename, try next one. continue @@ -502,9 +490,11 @@ nextFile: continue nextFile } - devices := f.model.fileAvailability(f.FolderConfiguration, snap, fi) + devices := f.model.fileAvailability(f.FolderConfiguration, fi) if len(devices) > 0 { - f.handleFile(fi, snap, copyChan) + if err := f.handleFile(fi, copyChan); err != nil { + f.newPullError(fileName, err) + } continue } f.newPullError(fileName, errNotAvailable) @@ -524,7 +514,7 @@ func popCandidate(buckets map[string][]protocol.FileInfo, key string) (protocol. return cands[0], true } -func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { for _, file := range fileDeletions { select { case <-f.ctx.Done(): @@ -532,7 +522,7 @@ func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.F default: } - f.deleteFile(file, snap, dbUpdateChan, scanChan) + f.deleteFile(file, dbUpdateChan, scanChan) } // Process in reverse order to delete depth first @@ -545,12 +535,12 @@ func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.F dir := dirDeletions[len(dirDeletions)-i-1] l.Debugln(f, "Deleting dir", dir.Name) - f.deleteDir(dir, snap, dbUpdateChan, scanChan) + f.deleteDir(dir, dbUpdateChan, scanChan) } } // handleDir creates or updates the given directory -func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -578,7 +568,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot, } if shouldDebug() { - curFile, _ := snap.Get(protocol.LocalDeviceID, file.Name) + curFile, _, _ := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) l.Debugf("need dir\n\t%v\n\t%v", file, curFile) } @@ -589,7 +579,11 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot, // that don't result in a conflict. case err == nil && !info.IsDir(): // Check that it is what we have in the database. - curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name) + curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + if err != nil { + f.newPullError(file.Name, fmt.Errorf("handling dir: %w", err)) + return + } if err := f.scanIfItemChanged(file.Name, info, curFile, hasCurFile, false, scanChan); err != nil { f.newPullError(file.Name, fmt.Errorf("handling dir: %w", err)) return @@ -606,7 +600,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot, return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, curFile.Name) } else { - err = f.deleteItemOnDisk(curFile, snap, scanChan) + err = f.deleteItemOnDisk(curFile, scanChan) } if err != nil { f.newPullError(file.Name, err) @@ -715,7 +709,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo } // handleSymlink creates or updates the given symlink -func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -738,8 +732,8 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps }() if shouldDebug() { - curFile, _ := snap.Get(protocol.LocalDeviceID, file.Name) - l.Debugf("need symlink\n\t%v\n\t%v", file, curFile) + curFile, ok, _ := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + l.Debugf("need symlink\n\t%v\n\t%v", file, curFile, ok) } if len(file.SymlinkTarget) == 0 { @@ -749,7 +743,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps return } - if err = f.handleSymlinkCheckExisting(file, snap, scanChan); err != nil { + if err = f.handleSymlinkCheckExisting(file, scanChan); err != nil { f.newPullError(file.Name, fmt.Errorf("handling symlink: %w", err)) return } @@ -770,7 +764,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps } } -func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) error { +func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, scanChan chan<- string) error { // If there is already something under that name, we need to handle that. info, err := f.mtimefs.Lstat(file.Name) if err != nil { @@ -780,7 +774,10 @@ func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, s return err } // Check that it is what we have in the database. - curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name) + curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + if err != nil { + return err + } if err := f.scanIfItemChanged(file.Name, info, curFile, hasCurFile, false, scanChan); err != nil { return err } @@ -796,12 +793,12 @@ func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, s return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, curFile.Name) } else { - return f.deleteItemOnDisk(curFile, snap, scanChan) + return f.deleteItemOnDisk(curFile, scanChan) } } // deleteDir attempts to remove a directory that was deleted on a remote -func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -826,7 +823,10 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot, }) }() - cur, hasCur := snap.Get(protocol.LocalDeviceID, file.Name) + cur, hasCur, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + if err != nil { + return + } if err = f.checkToBeDeleted(file, cur, hasCur, scanChan); err != nil { if fs.IsNotExist(err) || fs.IsErrCaseConflict(err) { @@ -836,7 +836,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot, return } - if err = f.deleteDirOnDisk(file.Name, snap, scanChan); err != nil { + if err = f.deleteDirOnDisk(file.Name, scanChan); err != nil { return } @@ -844,8 +844,12 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot, } // deleteFile attempts to delete the given file -func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { - cur, hasCur := snap.Get(protocol.LocalDeviceID, file.Name) +func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { + cur, hasCur, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + if err != nil { + f.newPullError(file.Name, fmt.Errorf("delete file: %w", err)) + return + } f.deleteFileWithCurrent(file, cur, hasCur, dbUpdateChan, scanChan) } @@ -924,7 +928,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h // renameFile attempts to rename an existing file to a destination // and set the right attributes on it. -func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { +func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -966,7 +970,10 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn return err } // Check that the target corresponds to what we have in the DB - curTarget, ok := snap.Get(protocol.LocalDeviceID, target.Name) + curTarget, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, target.Name) + if err != nil { + return err + } switch stat, serr := f.mtimefs.Lstat(target.Name); { case serr != nil: var caseErr *fs.ErrCaseConflict @@ -1040,7 +1047,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn // of the source and the creation of the target temp file. Fix-up the metadata, // update the local index of the target file and rename from temp to real name. - if err = f.performFinish(target, curTarget, true, tempName, snap, dbUpdateChan, scanChan); err != nil { + if err = f.performFinish(target, curTarget, true, tempName, dbUpdateChan, scanChan); err != nil { return err } @@ -1085,8 +1092,11 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn // handleFile queues the copies and pulls as necessary for a single new or // changed file. -func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot, copyChan chan<- copyBlocksState) { - curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name) +func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState) error { + curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) + if err != nil { + return err + } have, _ := blockDiff(curFile.Blocks, file.Blocks) @@ -1130,6 +1140,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot have: len(have), } copyChan <- cs + return nil } func (f *sendReceiveFolder) reuseBlocks(blocks []protocol.BlockInfo, reused []int, file protocol.FileInfo, tempName string) ([]protocol.BlockInfo, []int) { @@ -1284,7 +1295,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch // Hope that it's usually in the same folder, so start with that one. folders := []string{f.folderID} for folder, cfg := range f.model.cfg.Folders() { - folderFilesystems[folder] = cfg.Filesystem(nil) + folderFilesystems[folder] = cfg.Filesystem() if folder != f.folderID { folders = append(folders, folder) } @@ -1333,49 +1344,61 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) - found := f.model.finder.Iterate(folders, block.Hash, func(folder, path string, index int32) bool { - ffs := folderFilesystems[folder] - fd, err := ffs.Open(path) + found := false + for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(block.Hash)) { if err != nil { - return false + break } - defer fd.Close() - - srcOffset := int64(state.file.BlockSize()) * int64(index) - _, err = fd.ReadAt(buf, srcOffset) - if err != nil { - return false - } - - // Hash is not SHA256 as it's an encrypted hash token. In that - // case we can't verify the block integrity so we'll take it on - // trust. (The other side can and will verify.) - if f.Type != config.FolderTypeReceiveEncrypted { - if err := f.verifyBuffer(buf, block); err != nil { - l.Debugln("Finder failed to verify buffer", err) - return false + it, errFn := f.model.sdb.AllLocalFilesWithBlocksHashAnyFolder(e.BlocklistHash) + for folderID, fi := range it { + ffs := folderFilesystems[folderID] + fd, err := ffs.Open(fi.Name) + if err != nil { + continue } - } + defer fd.Close() - if f.CopyRangeMethod != config.CopyRangeMethodStandard { - err = f.withLimiter(func() error { - dstFd.mut.Lock() - defer dstFd.mut.Unlock() - return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, srcOffset, block.Offset, int64(block.Size)) - }) - } else { - err = f.limitedWriteAt(dstFd, buf, block.Offset) + _, err = fd.ReadAt(buf, e.Offset) + if err != nil { + fd.Close() + continue + } + + // Hash is not SHA256 as it's an encrypted hash token. In that + // case we can't verify the block integrity so we'll take it on + // trust. (The other side can and will verify.) + if f.Type != config.FolderTypeReceiveEncrypted { + if err := f.verifyBuffer(buf, block); err != nil { + l.Debugln("Finder failed to verify buffer", err) + continue + } + } + + if f.CopyRangeMethod != config.CopyRangeMethodStandard { + err = f.withLimiter(func() error { + dstFd.mut.Lock() + defer dstFd.mut.Unlock() + return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, e.Offset, block.Offset, int64(block.Size)) + }) + } else { + err = f.limitedWriteAt(dstFd, buf, block.Offset) + } + if err != nil { + state.fail(fmt.Errorf("dst write: %w", err)) + break + } + if fi.Name == state.file.Name { + state.copiedFromOrigin(block.Size) + } else { + state.copiedFromElsewhere(block.Size) + } + found = true + break } - if err != nil { - state.fail(fmt.Errorf("dst write: %w", err)) + if err := errFn(); err != nil { + l.Warnln(err) } - if path == state.file.Name { - state.copiedFromOrigin(block.Size) - } else { - state.copiedFromElsewhere(block.Size) - } - return true - }) + } if state.failed() != nil { break @@ -1410,7 +1433,7 @@ func (*sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) err return nil } -func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlockState, out chan<- *sharedPullerState) { +func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) { requestLimiter := semaphore.New(f.PullerMaxPendingKiB * 1024) wg := sync.NewWaitGroup() @@ -1441,13 +1464,13 @@ func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlock defer wg.Done() defer requestLimiter.Give(bytes) - f.pullBlock(state, snap, out) + f.pullBlock(state, out) }() } wg.Wait() } -func (f *sendReceiveFolder) pullBlock(state pullBlockState, snap *db.Snapshot, out chan<- *sharedPullerState) { +func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPullerState) { // Get an fd to the temporary file. Technically we don't need it until // after fetching the block, but if we run into an error here there is // no point in issuing the request to the network. @@ -1466,7 +1489,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, snap *db.Snapshot, o } var lastError error - candidates := f.model.blockAvailability(f.FolderConfiguration, snap, state.file, state.block) + candidates := f.model.blockAvailability(f.FolderConfiguration, state.file, state.block) loop: for { select { @@ -1531,7 +1554,7 @@ loop: out <- state.sharedPullerState } -func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCurFile bool, tempName string, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { +func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCurFile bool, tempName string, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { // Set the correct permission bits on the new file if !f.IgnorePerms && !file.NoPermissions { if err := f.mtimefs.Chmod(tempName, fs.FileMode(file.Permissions&0o777)); err != nil { @@ -1562,7 +1585,7 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, curFile.Name) } else { - err = f.deleteItemOnDisk(curFile, snap, scanChan) + err = f.deleteItemOnDisk(curFile, scanChan) } if err != nil { return fmt.Errorf("moving for conflict: %w", err) @@ -1585,7 +1608,7 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu return nil } -func (f *sendReceiveFolder) finisherRoutine(snap *db.Snapshot, in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { for state := range in { if closed, err := state.finalClose(); closed { l.Debugln(f, "closing", state.file.Name) @@ -1593,7 +1616,7 @@ func (f *sendReceiveFolder) finisherRoutine(snap *db.Snapshot, in <-chan *shared f.queue.Done(state.file.Name) if err == nil { - err = f.performFinish(state.file, state.curFile, state.hasCurFile, state.tempName, snap, dbUpdateChan, scanChan) + err = f.performFinish(state.file, state.curFile, state.hasCurFile, state.tempName, dbUpdateChan, scanChan) } if err != nil { @@ -1646,7 +1669,7 @@ func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) { var lastFile protocol.FileInfo tick := time.NewTicker(maxBatchTime) defer tick.Stop() - batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error { + batch := NewFileInfoBatch(func(files []protocol.FileInfo) error { // sync directories for dir := range changedDirs { delete(changedDirs, dir) @@ -1830,7 +1853,7 @@ func (f *sendReceiveFolder) newPullError(path string, err error) { } // deleteItemOnDisk deletes the file represented by old that is about to be replaced by new. -func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) (err error) { +func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) (err error) { defer func() { if err != nil { err = fmt.Errorf("%s: %w", contextRemovingOldItem, err) @@ -1841,7 +1864,7 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Sn case item.IsDirectory(): // Directories aren't archived and need special treatment due // to potential children. - return f.deleteDirOnDisk(item.Name, snap, scanChan) + return f.deleteDirOnDisk(item.Name, scanChan) case !item.IsSymlink() && f.versioner != nil: // If we should use versioning, let the versioner archive the @@ -1857,12 +1880,12 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Sn // deleteDirOnDisk attempts to delete a directory. It checks for files/dirs inside // the directory and removes them if possible or returns an error if it fails -func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanChan chan<- string) error { +func (f *sendReceiveFolder) deleteDirOnDisk(dir string, scanChan chan<- string) error { if err := osutil.TraversesSymlink(f.mtimefs, filepath.Dir(dir)); err != nil { return err } - if err := f.deleteDirOnDiskHandleChildren(dir, snap, scanChan); err != nil { + if err := f.deleteDirOnDiskHandleChildren(dir, scanChan); err != nil { return err } @@ -1882,7 +1905,7 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC return err } -func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, snap *db.Snapshot, scanChan chan<- string) error { +func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, scanChan chan<- string) error { var dirsToDelete []string var hasIgnored, hasKnown, hasToBeScanned, hasReceiveOnlyChanged bool var delErr error @@ -1909,7 +1932,7 @@ func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, snap *db.S hasIgnored = true return nil } - cf, ok := snap.Get(protocol.LocalDeviceID, path) + cf, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, path) switch { case !ok || cf.IsDeleted(): // Something appeared in the dir that we either are not diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index ee013467b..bd1376262 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" @@ -149,7 +150,7 @@ func TestHandleFile(t *testing.T) { copyChan := make(chan copyBlocksState, 1) - f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan) + f.handleFile(requiredFile, copyChan) // Receive the results toCopy := <-copyChan @@ -189,13 +190,13 @@ func TestHandleFileWithTemp(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t, existingFile) defer wcfgCancel() - if _, err := prepareTmpFile(f.Filesystem(nil)); err != nil { + if _, err := prepareTmpFile(f.Filesystem()); err != nil { t.Fatal(err) } copyChan := make(chan copyBlocksState, 1) - f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan) + f.handleFile(requiredFile, copyChan) // Receive the results toCopy := <-copyChan @@ -239,7 +240,7 @@ func TestCopierFinder(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t, existingFile) defer wcfgCancel() - if _, err := prepareTmpFile(f.Filesystem(nil)); err != nil { + if _, err := prepareTmpFile(f.Filesystem()); err != nil { t.Fatal(err) } @@ -251,7 +252,7 @@ func TestCopierFinder(t *testing.T) { go f.copierRoutine(copyChan, pullChan, finisherChan) defer close(copyChan) - f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan) + f.handleFile(requiredFile, copyChan) timeout := time.After(10 * time.Second) pulls := make([]pullBlockState, 4) @@ -272,8 +273,9 @@ func TestCopierFinder(t *testing.T) { defer cleanupSharedPullerState(finish) select { - case <-pullChan: - t.Fatal("Pull channel has data to be read") + case v := <-pullChan: + t.Logf("%+v\n", v) + t.Fatal("Pull channel had data to be read") case <-finisherChan: t.Fatal("Finisher channel has data to be read") default: @@ -299,7 +301,7 @@ func TestCopierFinder(t *testing.T) { } // Verify that the fetched blocks have actually been written to the temp file - blks, err := scanner.HashFile(context.TODO(), f.ID, f.Filesystem(nil), tempFile, protocol.MinBlockSize, nil) + blks, err := scanner.HashFile(context.TODO(), f.ID, f.Filesystem(), tempFile, protocol.MinBlockSize, nil) if err != nil { t.Log(err) } @@ -313,10 +315,6 @@ func TestCopierFinder(t *testing.T) { // Test that updating a file removes its old blocks from the blockmap func TestCopierCleanup(t *testing.T) { - iterFn := func(folder, file string, index int32) bool { - return true - } - // Create a file file := setupFile("test", []int{0}) file.Size = 1 @@ -328,11 +326,11 @@ func TestCopierCleanup(t *testing.T) { // Update index (removing old blocks) f.updateLocalsFromScanning([]protocol.FileInfo{file}) - if m.finder.Iterate(folders, blocks[0].Hash, iterFn) { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[0].Hash)); err != nil || len(vals) > 0 { t.Error("Unexpected block found") } - if !m.finder.Iterate(folders, blocks[1].Hash, iterFn) { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[1].Hash)); err != nil || len(vals) == 0 { t.Error("Expected block not found") } @@ -341,11 +339,11 @@ func TestCopierCleanup(t *testing.T) { // Update index (removing old blocks) f.updateLocalsFromScanning([]protocol.FileInfo{file}) - if !m.finder.Iterate(folders, blocks[0].Hash, iterFn) { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[0].Hash)); err != nil || len(vals) == 0 { t.Error("Unexpected block found") } - if m.finder.Iterate(folders, blocks[1].Hash, iterFn) { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[1].Hash)); err != nil || len(vals) > 0 { t.Error("Expected block not found") } } @@ -371,10 +369,9 @@ func TestDeregisterOnFailInCopy(t *testing.T) { finisherBufferChan := make(chan *sharedPullerState, 1) finisherChan := make(chan *sharedPullerState) dbUpdateChan := make(chan dbUpdateJob, 1) - snap := fsetSnapshot(t, f.fset) copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan) - go f.finisherRoutine(snap, finisherChan, dbUpdateChan, make(chan string)) + go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string)) defer func() { close(copyChan) @@ -384,7 +381,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) { close(finisherChan) }() - f.handleFile(file, snap, copyChan) + f.handleFile(file, copyChan) // Receive a block at puller, to indicate that at least a single copier // loop has been performed. @@ -471,16 +468,15 @@ func TestDeregisterOnFailInPull(t *testing.T) { finisherBufferChan := make(chan *sharedPullerState) finisherChan := make(chan *sharedPullerState) dbUpdateChan := make(chan dbUpdateJob, 1) - snap := fsetSnapshot(t, f.fset) copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan) pullWg := sync.NewWaitGroup() pullWg.Add(1) go func() { - f.pullerRoutine(snap, pullChan, finisherBufferChan) + f.pullerRoutine(pullChan, finisherBufferChan) pullWg.Done() }() - go f.finisherRoutine(snap, finisherChan, dbUpdateChan, make(chan string)) + go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string)) defer func() { // Unblock copier and puller go func() { @@ -495,7 +491,7 @@ func TestDeregisterOnFailInPull(t *testing.T) { close(finisherChan) }() - f.handleFile(file, snap, copyChan) + f.handleFile(file, copyChan) // Receive at finisher, we should error out as puller has nowhere to pull // from. @@ -558,7 +554,7 @@ func TestDeregisterOnFailInPull(t *testing.T) { func TestIssue3164(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() ignDir := filepath.Join("issue3164", "oktodelete") subDir := filepath.Join(ignDir, "foobar") @@ -577,7 +573,7 @@ func TestIssue3164(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) - f.deleteDir(file, fsetSnapshot(t, f.fset), dbUpdateChan, make(chan string)) + f.deleteDir(file, dbUpdateChan, make(chan string)) if _, err := ffs.Stat("issue3164"); !fs.IsNotExist(err) { t.Fatal(err) @@ -648,7 +644,7 @@ func TestDiffEmpty(t *testing.T) { func TestDeleteIgnorePerms(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() f.IgnorePerms = true name := "deleteIgnorePerms" @@ -692,9 +688,6 @@ func TestCopyOwner(t *testing.T) { f.folder.FolderConfiguration = newFolderConfiguration(m.cfg, f.ID, f.Label, config.FilesystemTypeFake, "/TestCopyOwner") f.folder.FolderConfiguration.CopyOwnershipFromParent = true - f.fset = newFileSet(t, f.ID, m.db) - f.mtimefs = f.Filesystem(f.fset) - // Create a parent dir with a certain owner/group. f.mtimefs.Mkdir("foo", 0o755) @@ -712,7 +705,7 @@ func TestCopyOwner(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) scanChan := make(chan string) defer close(dbUpdateChan) - f.handleDir(dir, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan) + f.handleDir(dir, dbUpdateChan, scanChan) select { case <-dbUpdateChan: // empty the channel for later case toScan := <-scanChan: @@ -742,17 +735,16 @@ func TestCopyOwner(t *testing.T) { // but it's the way data is passed around. When the database update // comes the finisher is done. - snap := fsetSnapshot(t, f.fset) finisherChan := make(chan *sharedPullerState) copierChan, copyWg := startCopier(f, nil, finisherChan) - go f.finisherRoutine(snap, finisherChan, dbUpdateChan, nil) + go f.finisherRoutine(finisherChan, dbUpdateChan, nil) defer func() { close(copierChan) copyWg.Wait() close(finisherChan) }() - f.handleFile(file, snap, copierChan) + f.handleFile(file, copierChan) <-dbUpdateChan info, err = f.mtimefs.Lstat("foo/bar/baz") @@ -771,7 +763,7 @@ func TestCopyOwner(t *testing.T) { SymlinkTarget: []byte("over the rainbow"), } - f.handleSymlink(symlink, snap, dbUpdateChan, scanChan) + f.handleSymlink(symlink, dbUpdateChan, scanChan) select { case <-dbUpdateChan: case toScan := <-scanChan: @@ -792,7 +784,7 @@ func TestCopyOwner(t *testing.T) { func TestSRConflictReplaceFileByDir(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() name := "foo" @@ -810,7 +802,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) scanChan := make(chan string, 1) - f.handleDir(file, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan) + f.handleDir(file, dbUpdateChan, scanChan) if confls := existingConflicts(name, ffs); len(confls) != 1 { t.Fatal("Expected one conflict, got", len(confls)) @@ -824,7 +816,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) { func TestSRConflictReplaceFileByLink(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() name := "foo" @@ -843,7 +835,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) scanChan := make(chan string, 1) - f.handleSymlink(file, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan) + f.handleSymlink(file, dbUpdateChan, scanChan) if confls := existingConflicts(name, ffs); len(confls) != 1 { t.Fatal("Expected one conflict, got", len(confls)) @@ -857,7 +849,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) { func TestDeleteBehindSymlink(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() link := "link" linkFile := filepath.Join(link, "file") @@ -873,7 +865,7 @@ func TestDeleteBehindSymlink(t *testing.T) { fi.Version = fi.Version.Update(device1.Short()) scanChan := make(chan string, 1) dbUpdateChan := make(chan dbUpdateJob, 1) - f.deleteFile(fi, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan) + f.deleteFile(fi, dbUpdateChan, scanChan) select { case f := <-scanChan: t.Fatalf("Received %v on scanChan", f) @@ -903,7 +895,7 @@ func TestPullCtxCancel(t *testing.T) { var cancel context.CancelFunc f.ctx, cancel = context.WithCancel(context.Background()) - go f.pullerRoutine(fsetSnapshot(t, f.fset), pullChan, finisherChan) + go f.pullerRoutine(pullChan, finisherChan) defer close(pullChan) emptyState := func() pullBlockState { @@ -938,7 +930,7 @@ func TestPullCtxCancel(t *testing.T) { func TestPullDeleteUnscannedDir(t *testing.T) { _, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() dir := "foobar" must(t, ffs.MkdirAll(dir, 0o777)) @@ -949,7 +941,7 @@ func TestPullDeleteUnscannedDir(t *testing.T) { scanChan := make(chan string, 1) dbUpdateChan := make(chan dbUpdateJob, 1) - f.deleteDir(fi, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan) + f.deleteDir(fi, dbUpdateChan, scanChan) if _, err := ffs.Stat(dir); fs.IsNotExist(err) { t.Error("directory has been deleted") @@ -967,7 +959,7 @@ func TestPullDeleteUnscannedDir(t *testing.T) { func TestPullCaseOnlyPerformFinish(t *testing.T) { m, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() name := "foo" contents := []byte("contents") @@ -976,16 +968,17 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) { var cur protocol.FileInfo hasCur := false - snap := dbSnapshot(t, m, f.ID) - defer snap.Release() - snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool { + it, errFn := m.LocalFiles(f.ID, protocol.LocalDeviceID) + for i := range it { if hasCur { t.Fatal("got more than one file") } cur = i hasCur = true - return true - }) + } + if err := errFn(); err != nil { + t.Fatal(err) + } if !hasCur { t.Fatal("file is missing") } @@ -999,7 +992,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) { scanChan := make(chan string, 1) dbUpdateChan := make(chan dbUpdateJob, 1) - err := f.performFinish(remote, cur, hasCur, temp, snap, dbUpdateChan, scanChan) + err := f.performFinish(remote, cur, hasCur, temp, dbUpdateChan, scanChan) select { case <-dbUpdateChan: // boring case sensitive filesystem @@ -1029,7 +1022,7 @@ func TestPullCaseOnlySymlink(t *testing.T) { func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) { m, f, wcfgCancel := setupSendReceiveFolder(t) defer wcfgCancel() - ffs := f.Filesystem(nil) + ffs := f.Filesystem() name := "foo" if dir { @@ -1041,16 +1034,17 @@ func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) { must(t, f.scanSubdirs(nil)) var cur protocol.FileInfo hasCur := false - snap := dbSnapshot(t, m, f.ID) - defer snap.Release() - snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool { + it, errFn := m.LocalFiles(f.ID, protocol.LocalDeviceID) + for i := range it { if hasCur { t.Fatal("got more than one file") } cur = i hasCur = true - return true - }) + } + if err := errFn(); err != nil { + t.Fatal(err) + } if !hasCur { t.Fatal("file is missing") } @@ -1063,9 +1057,9 @@ func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) { remote.Name = strings.ToUpper(cur.Name) if dir { - f.handleDir(remote, snap, dbUpdateChan, scanChan) + f.handleDir(remote, dbUpdateChan, scanChan) } else { - f.handleSymlink(remote, snap, dbUpdateChan, scanChan) + f.handleSymlink(remote, dbUpdateChan, scanChan) } select { @@ -1100,7 +1094,7 @@ func TestPullTempFileCaseConflict(t *testing.T) { fd.Close() } - f.handleFile(file, fsetSnapshot(t, f.fset), copyChan) + f.handleFile(file, copyChan) cs := <-copyChan if _, err := cs.tempFile(); err != nil { @@ -1142,9 +1136,7 @@ func TestPullCaseOnlyRename(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 2) scanChan := make(chan string, 2) - snap := fsetSnapshot(t, f.fset) - defer snap.Release() - if err := f.renameFile(cur, deleted, confl, snap, dbUpdateChan, scanChan); err != nil { + if err := f.renameFile(cur, deleted, confl, dbUpdateChan, scanChan); err != nil { t.Error(err) } } @@ -1219,9 +1211,7 @@ func TestPullDeleteCaseConflict(t *testing.T) { t.Error("Missing db update for file") } - snap := fsetSnapshot(t, f.fset) - defer snap.Release() - f.deleteDir(fi, snap, dbUpdateChan, scanChan) + f.deleteDir(fi, dbUpdateChan, scanChan) select { case <-dbUpdateChan: default: @@ -1249,7 +1239,7 @@ func TestPullDeleteIgnoreChildDir(t *testing.T) { scanChan := make(chan string, 2) - err := f.deleteDirOnDisk(parent, fsetSnapshot(t, f.fset), scanChan) + err := f.deleteDirOnDisk(parent, scanChan) if err == nil { t.Error("no error") } diff --git a/lib/model/folder_summary.go b/lib/model/folder_summary.go index b1d1e77d8..ad71fb158 100644 --- a/lib/model/folder_summary.go +++ b/lib/model/folder_summary.go @@ -17,8 +17,8 @@ import ( "github.com/thejerf/suture/v4" + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/svcutil" @@ -127,16 +127,12 @@ func (c *folderSummaryService) Summary(folder string) (*FolderSummary, error) { var remoteSeq map[protocol.DeviceID]int64 errors, err := c.model.FolderErrors(folder) if err == nil { - var snap *db.Snapshot - if snap, err = c.model.DBSnapshot(folder); err == nil { - global = snap.GlobalSize() - local = snap.LocalSize() - need = snap.NeedSize(protocol.LocalDeviceID) - ro = snap.ReceiveOnlyChangedSize() - ourSeq = snap.Sequence(protocol.LocalDeviceID) - remoteSeq = snap.RemoteSequences() - snap.Release() - } + global, _ = c.model.GlobalSize(folder) + local, _ = c.model.LocalSize(folder, protocol.LocalDeviceID) + need, _ = c.model.NeedSize(folder, protocol.LocalDeviceID) + ro, _ = c.model.ReceiveOnlySize(folder) + ourSeq, _ = c.model.Sequence(folder, protocol.LocalDeviceID) + remoteSeq, _ = c.model.RemoteSequences(folder) } // For API backwards compatibility (SyncTrayzor needs it) an empty folder // summary is returned for not running folders, an error might actually be diff --git a/lib/model/indexhandler.go b/lib/model/indexhandler.go index 23fe49ac6..49c33666e 100644 --- a/lib/model/indexhandler.go +++ b/lib/model/indexhandler.go @@ -8,12 +8,14 @@ package model import ( "context" + "errors" "fmt" "sync" "time" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/svcutil" @@ -45,13 +47,19 @@ type indexHandler struct { cond *sync.Cond paused bool - fset *db.FileSet + sdb db.DB runner service } -func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, folder config.FolderConfiguration, fset *db.FileSet, runner service, startInfo *clusterConfigDeviceInfo, evLogger events.Logger) *indexHandler { - myIndexID := fset.IndexID(protocol.LocalDeviceID) - mySequence := fset.Sequence(protocol.LocalDeviceID) +func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, folder config.FolderConfiguration, sdb db.DB, runner service, startInfo *clusterConfigDeviceInfo, evLogger events.Logger) (*indexHandler, error) { + myIndexID, err := sdb.GetIndexID(folder.ID, protocol.LocalDeviceID) + if err != nil { + return nil, err + } + mySequence, err := sdb.GetDeviceSequence(folder.ID, protocol.LocalDeviceID) + if err != nil { + return nil, err + } var startSequence int64 // This is the other side's description of what it knows @@ -91,14 +99,14 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f // otherwise we drop our old index data and expect to get a // completely new set. - theirIndexID := fset.IndexID(conn.DeviceID()) + theirIndexID, _ := sdb.GetIndexID(folder.ID, conn.DeviceID()) if startInfo.remote.IndexID == 0 { // They're not announcing an index ID. This means they // do not support delta indexes and we should clear any // information we have from them before accepting their // index, which will presumably be a full index. l.Debugf("Device %v folder %s does not announce an index ID", conn.DeviceID().Short(), folder.Description()) - fset.Drop(conn.DeviceID()) + sdb.DropAllFiles(folder.ID, conn.DeviceID()) } else if startInfo.remote.IndexID != theirIndexID { // The index ID we have on file is not what they're // announcing. They must have reset their database and @@ -106,8 +114,8 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f // information we have and remember this new index ID // instead. l.Infof("Device %v folder %s has a new index ID (%v)", conn.DeviceID().Short(), folder.Description(), startInfo.remote.IndexID) - fset.Drop(conn.DeviceID()) - fset.SetIndexID(conn.DeviceID(), startInfo.remote.IndexID) + sdb.DropAllFiles(folder.ID, conn.DeviceID()) + sdb.SetIndexID(folder.ID, conn.DeviceID(), startInfo.remote.IndexID) } return &indexHandler{ @@ -119,27 +127,27 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f sentPrevSequence: startSequence, evLogger: evLogger, - fset: fset, + sdb: sdb, runner: runner, cond: sync.NewCond(new(sync.Mutex)), - } + }, nil } -// waitForFileset waits for the handler to resume and fetches the current fileset. -func (s *indexHandler) waitForFileset(ctx context.Context) (*db.FileSet, error) { +// waitWhilePaused waits for the handler to resume +func (s *indexHandler) waitWhilePaused(ctx context.Context) error { s.cond.L.Lock() defer s.cond.L.Unlock() for s.paused { select { case <-ctx.Done(): - return nil, ctx.Err() + return ctx.Err() default: s.cond.Wait() } } - return s.fset, nil + return nil } func (s *indexHandler) Serve(ctx context.Context) (err error) { @@ -162,11 +170,10 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) { }() // We need to send one index, regardless of whether there is something to send or not - fset, err := s.waitForFileset(ctx) - if err != nil { + if err := s.waitWhilePaused(ctx); err != nil { return err } - err = s.sendIndexTo(ctx, fset) + err = s.sendIndexTo(ctx) // Subscribe to LocalIndexUpdated (we have new information to send) and // DeviceDisconnected (it might be us who disconnected, so we should @@ -179,8 +186,7 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) { defer ticker.Stop() for err == nil { - fset, err = s.waitForFileset(ctx) - if err != nil { + if err := s.waitWhilePaused(ctx); err != nil { return err } @@ -188,7 +194,11 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) { // currently in the database, wait for the local index to update. The // local index may update for other folders than the one we are // sending for. - if fset.Sequence(protocol.LocalDeviceID) <= s.localPrevSequence { + seq, err := s.sdb.GetDeviceSequence(s.folder, protocol.LocalDeviceID) + if err != nil { + return err + } + if seq <= s.localPrevSequence { select { case <-ctx.Done(): return ctx.Err() @@ -198,7 +208,7 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) { continue } - err = s.sendIndexTo(ctx, fset) + err = s.sendIndexTo(ctx) // Wait a short amount of time before entering the next loop. If there // are continuous changes happening to the local index, this gives us @@ -215,10 +225,9 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) { // resume might be called because the folder was actually resumed, or just // because the folder config changed (and thus the runner and potentially fset). -func (s *indexHandler) resume(fset *db.FileSet, runner service) { +func (s *indexHandler) resume(runner service) { s.cond.L.Lock() s.paused = false - s.fset = fset s.runner = runner s.cond.Broadcast() s.cond.L.Unlock() @@ -230,7 +239,6 @@ func (s *indexHandler) pause() { s.evLogger.Log(events.Failure, "index handler got paused while already paused") } s.paused = true - s.fset = nil s.runner = nil s.cond.Broadcast() s.cond.L.Unlock() @@ -238,9 +246,9 @@ func (s *indexHandler) pause() { // sendIndexTo sends file infos with a sequence number higher than prevSequence and // returns the highest sent sequence number. -func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error { +func (s *indexHandler) sendIndexTo(ctx context.Context) error { initial := s.localPrevSequence == 0 - batch := db.NewFileInfoBatch(nil) + batch := NewFileInfoBatch(nil) var batchError error batch.SetFlushFunc(func(fs []protocol.FileInfo) error { select { @@ -284,21 +292,26 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error return nil }) - var err error var f protocol.FileInfo - snap, err := fset.Snapshot() - if err != nil { - return svcutil.AsFatalErr(err, svcutil.ExitError) - } - defer snap.Release() previousWasDelete := false - snap.WithHaveSequence(s.localPrevSequence+1, func(fi protocol.FileInfo) bool { + + t0 := time.Now() + for fi, err := range itererr.Zip(s.sdb.AllLocalFilesBySequence(s.folder, protocol.LocalDeviceID, s.localPrevSequence+1, 5000)) { + if err != nil { + return err + } // This is to make sure that renames (which is an add followed by a delete) land in the same batch. // Even if the batch is full, we allow a last delete to slip in, we do this by making sure that // the batch ends with a non-delete, or that the last item in the batch is already a delete if batch.Full() && (!fi.IsDeleted() || previousWasDelete) { - if err = batch.Flush(); err != nil { - return false + if err := batch.Flush(); err != nil { + return err + } + if time.Since(t0) > 5*time.Second { + // minor hack -- avoid very long running read transactions + // during index transmission, to help prevent excessive + // growth of database WAL file + break } } @@ -307,6 +320,7 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error "sequence": fi.SequenceNo(), "start": s.localPrevSequence + 1, }) + return errors.New("database misbehaved") } if f.Sequence > 0 && fi.SequenceNo() <= f.Sequence { @@ -315,27 +329,17 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error "start": s.localPrevSequence + 1, "previous": f.Sequence, }) - // Abort this round of index sending - the next one will pick - // up from the last successful one with the repeaired db. - defer func() { - if fixed, dbErr := fset.RepairSequence(); dbErr != nil { - l.Warnln("Failed repairing sequence entries:", dbErr) - panic("Failed repairing sequence entries") - } else { - s.evLogger.Log(events.Failure, "detected and repaired non-increasing sequence") - l.Infof("Repaired %v sequence entries in database", fixed) - } - }() - return false + return errors.New("database misbehaved") } f = fi + s.localPrevSequence = f.Sequence // If this is a folder receiving encrypted files only, we // mustn't ever send locally changed file infos. Those aren't // encrypted and thus would be a protocol error at the remote. if s.folderIsReceiveEncrypted && fi.IsReceiveOnlyChanged() { - return true + continue } f = prepareFileInfoForIndex(f) @@ -343,23 +347,11 @@ func (s *indexHandler) sendIndexTo(ctx context.Context, fset *db.FileSet) error previousWasDelete = f.IsDeleted() batch.Append(f) - return true - }) - if err != nil { - return err } - if err := batch.Flush(); err != nil { return err } - // Use the sequence of the snapshot we iterated as a starting point for the - // next run. Previously we used the sequence of the last file we sent, - // however it's possible that a higher sequence exists, just doesn't need to - // be sent (e.g. in a receive-only folder, when a local change was - // reverted). No point trying to send nothing again. - s.localPrevSequence = snap.Sequence(protocol.LocalDeviceID) - return nil } @@ -368,7 +360,6 @@ func (s *indexHandler) receive(fs []protocol.FileInfo, update bool, op string, p s.cond.L.Lock() paused := s.paused - fset := s.fset runner := s.runner s.cond.L.Unlock() @@ -382,13 +373,19 @@ func (s *indexHandler) receive(fs []protocol.FileInfo, update bool, op string, p s.downloads.Update(s.folder, makeForgetUpdate(fs)) if !update { - fset.Drop(deviceID) + if err := s.sdb.DropAllFiles(s.folder, deviceID); err != nil { + return err + } } l.Debugf("Received %d files for %s from %s, prevSeq=%d, lastSeq=%d", len(fs), s.folder, deviceID.Short(), prevSequence, lastSequence) // Verify that the previous sequence number matches what we expected - if exp := fset.Sequence(deviceID); prevSequence > 0 && prevSequence != exp { + exp, err := s.sdb.GetDeviceSequence(s.folder, deviceID) + if err != nil { + return err + } + if prevSequence > 0 && prevSequence != exp { s.logSequenceAnomaly("index update with unexpected sequence", map[string]any{ "prevSeq": prevSequence, "lastSeq": lastSequence, @@ -444,8 +441,13 @@ func (s *indexHandler) receive(fs []protocol.FileInfo, update bool, op string, p }) } - fset.Update(deviceID, fs) - seq := fset.Sequence(deviceID) + if err := s.sdb.Update(s.folder, deviceID, fs); err != nil { + return err + } + seq, err := s.sdb.GetDeviceSequence(s.folder, deviceID) + if err != nil { + return err + } // Check that the sequence we get back is what we put in... if lastSequence > 0 && len(fs) > 0 && seq != lastSequence { @@ -508,6 +510,7 @@ func (s *indexHandler) String() string { type indexHandlerRegistry struct { evLogger events.Logger conn protocol.Connection + sdb db.DB downloads *deviceDownloadState indexHandlers *serviceMap[string, *indexHandler] startInfos map[string]*clusterConfigDeviceInfo @@ -517,14 +520,14 @@ type indexHandlerRegistry struct { type indexHandlerFolderState struct { cfg config.FolderConfiguration - fset *db.FileSet runner service } -func newIndexHandlerRegistry(conn protocol.Connection, downloads *deviceDownloadState, evLogger events.Logger) *indexHandlerRegistry { +func newIndexHandlerRegistry(conn protocol.Connection, sdb db.DB, downloads *deviceDownloadState, evLogger events.Logger) *indexHandlerRegistry { r := &indexHandlerRegistry{ evLogger: evLogger, conn: conn, + sdb: sdb, downloads: downloads, indexHandlers: newServiceMap[string, *indexHandler](evLogger), startInfos: make(map[string]*clusterConfigDeviceInfo), @@ -544,15 +547,19 @@ func (r *indexHandlerRegistry) Serve(ctx context.Context) error { return r.indexHandlers.Serve(ctx) } -func (r *indexHandlerRegistry) startLocked(folder config.FolderConfiguration, fset *db.FileSet, runner service, startInfo *clusterConfigDeviceInfo) { +func (r *indexHandlerRegistry) startLocked(folder config.FolderConfiguration, runner service, startInfo *clusterConfigDeviceInfo) error { r.indexHandlers.RemoveAndWait(folder.ID, 0) delete(r.startInfos, folder.ID) - is := newIndexHandler(r.conn, r.downloads, folder, fset, runner, startInfo, r.evLogger) + is, err := newIndexHandler(r.conn, r.downloads, folder, r.sdb, runner, startInfo, r.evLogger) + if err != nil { + return err + } r.indexHandlers.Add(folder.ID, is) // This new connection might help us get in sync. runner.SchedulePull() + return nil } // AddIndexInfo starts an index handler for given folder, unless it is paused. @@ -572,7 +579,7 @@ func (r *indexHandlerRegistry) AddIndexInfo(folder string, startInfo *clusterCon r.startInfos[folder] = startInfo return } - r.startLocked(folderState.cfg, folderState.fset, folderState.runner, startInfo) + _ = r.startLocked(folderState.cfg, folderState.runner, startInfo) // XXX error handling... } // Remove stops a running index handler or removes one pending to be started. @@ -612,7 +619,7 @@ func (r *indexHandlerRegistry) RemoveAllExcept(except map[string]remoteFolderSta // RegisterFolderState must be called whenever something about the folder // changes. The exception being if the folder is removed entirely, then call // Remove. The fset and runner arguments may be nil, if given folder is paused. -func (r *indexHandlerRegistry) RegisterFolderState(folder config.FolderConfiguration, fset *db.FileSet, runner service) { +func (r *indexHandlerRegistry) RegisterFolderState(folder config.FolderConfiguration, runner service) { if !folder.SharedWith(r.conn.DeviceID()) { r.Remove(folder.ID) return @@ -622,7 +629,7 @@ func (r *indexHandlerRegistry) RegisterFolderState(folder config.FolderConfigura if folder.Paused { r.folderPausedLocked(folder.ID) } else { - r.folderRunningLocked(folder, fset, runner) + r.folderRunningLocked(folder, runner) } r.mut.Unlock() } @@ -643,10 +650,9 @@ func (r *indexHandlerRegistry) folderPausedLocked(folder string) { // folderRunningLocked resumes an already running index handler or starts it, if it // was added while paused. // It is a noop if the folder isn't known. -func (r *indexHandlerRegistry) folderRunningLocked(folder config.FolderConfiguration, fset *db.FileSet, runner service) { +func (r *indexHandlerRegistry) folderRunningLocked(folder config.FolderConfiguration, runner service) { r.folderStates[folder.ID] = &indexHandlerFolderState{ cfg: folder, - fset: fset, runner: runner, } @@ -656,12 +662,12 @@ func (r *indexHandlerRegistry) folderRunningLocked(folder config.FolderConfigura r.indexHandlers.RemoveAndWait(folder.ID, 0) l.Debugf("Removed index handler for device %v and folder %v in resume", r.conn.DeviceID().Short(), folder.ID) } - r.startLocked(folder, fset, runner, info) + _ = r.startLocked(folder, runner, info) // XXX error handling... delete(r.startInfos, folder.ID) l.Debugf("Started index handler for device %v and folder %v in resume", r.conn.DeviceID().Short(), folder.ID) } else if isOk { l.Debugf("Resuming index handler for device %v and folder %v", r.conn.DeviceID().Short(), folder) - is.resume(fset, runner) + is.resume(runner) } else { l.Debugf("Not resuming index handler for device %v and folder %v as none is paused and there is no start info", r.conn.DeviceID().Short(), folder.ID) } diff --git a/lib/model/indexhandler_test.go b/lib/model/indexhandler_test.go index e8602e235..4e3981f63 100644 --- a/lib/model/indexhandler_test.go +++ b/lib/model/indexhandler_test.go @@ -13,7 +13,7 @@ import ( "sync" "testing" - "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/model/mocks" "github.com/syncthing/syncthing/lib/protocol" protomock "github.com/syncthing/syncthing/lib/protocol/mocks" @@ -63,7 +63,7 @@ func TestIndexhandlerConcurrency(t *testing.T) { return nil }) - b1 := db.NewFileInfoBatch(func(fs []protocol.FileInfo) error { + b1 := model.NewFileInfoBatch(func(fs []protocol.FileInfo) error { return c1.IndexUpdate(ctx, &protocol.IndexUpdate{Folder: "foo", Files: fs}) }) sentEntries := 0 diff --git a/lib/model/mocks/model.go b/lib/model/mocks/model.go index 6aa17ef60..cdceb850e 100644 --- a/lib/model/mocks/model.go +++ b/lib/model/mocks/model.go @@ -3,12 +3,12 @@ package mocks import ( "context" + "iter" "net" "sync" "time" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/fs" + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/stats" @@ -23,6 +23,19 @@ type Model struct { arg1 protocol.Connection arg2 protocol.Hello } + AllGlobalFilesStub func(string) (iter.Seq[db.FileMetadata], func() error) + allGlobalFilesMutex sync.RWMutex + allGlobalFilesArgsForCall []struct { + arg1 string + } + allGlobalFilesReturns struct { + result1 iter.Seq[db.FileMetadata] + result2 func() error + } + allGlobalFilesReturnsOnCall map[int]struct { + result1 iter.Seq[db.FileMetadata] + result2 func() error + } AvailabilityStub func(string, protocol.FileInfo, protocol.BlockInfo) ([]model.Availability, error) availabilityMutex sync.RWMutex availabilityArgsForCall []struct { @@ -144,19 +157,6 @@ type Model struct { result2 []string result3 error } - DBSnapshotStub func(string) (*db.Snapshot, error) - dBSnapshotMutex sync.RWMutex - dBSnapshotArgsForCall []struct { - arg1 string - } - dBSnapshotReturns struct { - result1 *db.Snapshot - result2 error - } - dBSnapshotReturnsOnCall map[int]struct { - result1 *db.Snapshot - result2 error - } DelayScanStub func(string, time.Duration) delayScanMutex sync.RWMutex delayScanArgsForCall []struct { @@ -259,20 +259,6 @@ type Model struct { result1 map[string][]versioner.FileVersion result2 error } - GetMtimeMappingStub func(string, string) (fs.MtimeMapping, error) - getMtimeMappingMutex sync.RWMutex - getMtimeMappingArgsForCall []struct { - arg1 string - arg2 string - } - getMtimeMappingReturns struct { - result1 fs.MtimeMapping - result2 error - } - getMtimeMappingReturnsOnCall map[int]struct { - result1 fs.MtimeMapping - result2 error - } GlobalDirectoryTreeStub func(string, string, int, bool) ([]*model.TreeEntry, error) globalDirectoryTreeMutex sync.RWMutex globalDirectoryTreeArgsForCall []struct { @@ -289,6 +275,19 @@ type Model struct { result1 []*model.TreeEntry result2 error } + GlobalSizeStub func(string) (db.Counts, error) + globalSizeMutex sync.RWMutex + globalSizeArgsForCall []struct { + arg1 string + } + globalSizeReturns struct { + result1 db.Counts + result2 error + } + globalSizeReturnsOnCall map[int]struct { + result1 db.Counts + result2 error + } IndexStub func(protocol.Connection, *protocol.Index) error indexMutex sync.RWMutex indexArgsForCall []struct { @@ -343,6 +342,49 @@ type Model struct { result1 []protocol.FileInfo result2 error } + LocalFilesStub func(string, protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) + localFilesMutex sync.RWMutex + localFilesArgsForCall []struct { + arg1 string + arg2 protocol.DeviceID + } + localFilesReturns struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + } + localFilesReturnsOnCall map[int]struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + } + LocalFilesSequencedStub func(string, protocol.DeviceID, int64) (iter.Seq[protocol.FileInfo], func() error) + localFilesSequencedMutex sync.RWMutex + localFilesSequencedArgsForCall []struct { + arg1 string + arg2 protocol.DeviceID + arg3 int64 + } + localFilesSequencedReturns struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + } + localFilesSequencedReturnsOnCall map[int]struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + } + LocalSizeStub func(string, protocol.DeviceID) (db.Counts, error) + localSizeMutex sync.RWMutex + localSizeArgsForCall []struct { + arg1 string + arg2 protocol.DeviceID + } + localSizeReturns struct { + result1 db.Counts + result2 error + } + localSizeReturnsOnCall map[int]struct { + result1 db.Counts + result2 error + } NeedFolderFilesStub func(string, int, int) ([]protocol.FileInfo, []protocol.FileInfo, []protocol.FileInfo, error) needFolderFilesMutex sync.RWMutex needFolderFilesArgsForCall []struct { @@ -362,6 +404,20 @@ type Model struct { result3 []protocol.FileInfo result4 error } + NeedSizeStub func(string, protocol.DeviceID) (db.Counts, error) + needSizeMutex sync.RWMutex + needSizeArgsForCall []struct { + arg1 string + arg2 protocol.DeviceID + } + needSizeReturns struct { + result1 db.Counts + result2 error + } + needSizeReturnsOnCall map[int]struct { + result1 db.Counts + result2 error + } OnHelloStub func(protocol.DeviceID, net.Addr, protocol.Hello) error onHelloMutex sync.RWMutex onHelloArgsForCall []struct { @@ -405,6 +461,19 @@ type Model struct { result1 map[string]db.PendingFolder result2 error } + ReceiveOnlySizeStub func(string) (db.Counts, error) + receiveOnlySizeMutex sync.RWMutex + receiveOnlySizeArgsForCall []struct { + arg1 string + } + receiveOnlySizeReturns struct { + result1 db.Counts + result2 error + } + receiveOnlySizeReturnsOnCall map[int]struct { + result1 db.Counts + result2 error + } RemoteNeedFolderFilesStub func(string, protocol.DeviceID, int, int) ([]protocol.FileInfo, error) remoteNeedFolderFilesMutex sync.RWMutex remoteNeedFolderFilesArgsForCall []struct { @@ -421,6 +490,19 @@ type Model struct { result1 []protocol.FileInfo result2 error } + RemoteSequencesStub func(string) (map[protocol.DeviceID]int64, error) + remoteSequencesMutex sync.RWMutex + remoteSequencesArgsForCall []struct { + arg1 string + } + remoteSequencesReturns struct { + result1 map[protocol.DeviceID]int64 + result2 error + } + remoteSequencesReturnsOnCall map[int]struct { + result1 map[protocol.DeviceID]int64 + result2 error + } RequestStub func(protocol.Connection, *protocol.Request) (protocol.RequestResponse, error) requestMutex sync.RWMutex requestArgsForCall []struct { @@ -519,6 +601,20 @@ type Model struct { scanFoldersReturnsOnCall map[int]struct { result1 map[string]error } + SequenceStub func(string, protocol.DeviceID) (int64, error) + sequenceMutex sync.RWMutex + sequenceArgsForCall []struct { + arg1 string + arg2 protocol.DeviceID + } + sequenceReturns struct { + result1 int64 + result2 error + } + sequenceReturnsOnCall map[int]struct { + result1 int64 + result2 error + } ServeStub func(context.Context) error serveMutex sync.RWMutex serveArgsForCall []struct { @@ -612,6 +708,70 @@ func (fake *Model) AddConnectionArgsForCall(i int) (protocol.Connection, protoco return argsForCall.arg1, argsForCall.arg2 } +func (fake *Model) AllGlobalFiles(arg1 string) (iter.Seq[db.FileMetadata], func() error) { + fake.allGlobalFilesMutex.Lock() + ret, specificReturn := fake.allGlobalFilesReturnsOnCall[len(fake.allGlobalFilesArgsForCall)] + fake.allGlobalFilesArgsForCall = append(fake.allGlobalFilesArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.AllGlobalFilesStub + fakeReturns := fake.allGlobalFilesReturns + fake.recordInvocation("AllGlobalFiles", []interface{}{arg1}) + fake.allGlobalFilesMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) AllGlobalFilesCallCount() int { + fake.allGlobalFilesMutex.RLock() + defer fake.allGlobalFilesMutex.RUnlock() + return len(fake.allGlobalFilesArgsForCall) +} + +func (fake *Model) AllGlobalFilesCalls(stub func(string) (iter.Seq[db.FileMetadata], func() error)) { + fake.allGlobalFilesMutex.Lock() + defer fake.allGlobalFilesMutex.Unlock() + fake.AllGlobalFilesStub = stub +} + +func (fake *Model) AllGlobalFilesArgsForCall(i int) string { + fake.allGlobalFilesMutex.RLock() + defer fake.allGlobalFilesMutex.RUnlock() + argsForCall := fake.allGlobalFilesArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *Model) AllGlobalFilesReturns(result1 iter.Seq[db.FileMetadata], result2 func() error) { + fake.allGlobalFilesMutex.Lock() + defer fake.allGlobalFilesMutex.Unlock() + fake.AllGlobalFilesStub = nil + fake.allGlobalFilesReturns = struct { + result1 iter.Seq[db.FileMetadata] + result2 func() error + }{result1, result2} +} + +func (fake *Model) AllGlobalFilesReturnsOnCall(i int, result1 iter.Seq[db.FileMetadata], result2 func() error) { + fake.allGlobalFilesMutex.Lock() + defer fake.allGlobalFilesMutex.Unlock() + fake.AllGlobalFilesStub = nil + if fake.allGlobalFilesReturnsOnCall == nil { + fake.allGlobalFilesReturnsOnCall = make(map[int]struct { + result1 iter.Seq[db.FileMetadata] + result2 func() error + }) + } + fake.allGlobalFilesReturnsOnCall[i] = struct { + result1 iter.Seq[db.FileMetadata] + result2 func() error + }{result1, result2} +} + func (fake *Model) Availability(arg1 string, arg2 protocol.FileInfo, arg3 protocol.BlockInfo) ([]model.Availability, error) { fake.availabilityMutex.Lock() ret, specificReturn := fake.availabilityReturnsOnCall[len(fake.availabilityArgsForCall)] @@ -1188,70 +1348,6 @@ func (fake *Model) CurrentIgnoresReturnsOnCall(i int, result1 []string, result2 }{result1, result2, result3} } -func (fake *Model) DBSnapshot(arg1 string) (*db.Snapshot, error) { - fake.dBSnapshotMutex.Lock() - ret, specificReturn := fake.dBSnapshotReturnsOnCall[len(fake.dBSnapshotArgsForCall)] - fake.dBSnapshotArgsForCall = append(fake.dBSnapshotArgsForCall, struct { - arg1 string - }{arg1}) - stub := fake.DBSnapshotStub - fakeReturns := fake.dBSnapshotReturns - fake.recordInvocation("DBSnapshot", []interface{}{arg1}) - fake.dBSnapshotMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1, ret.result2 - } - return fakeReturns.result1, fakeReturns.result2 -} - -func (fake *Model) DBSnapshotCallCount() int { - fake.dBSnapshotMutex.RLock() - defer fake.dBSnapshotMutex.RUnlock() - return len(fake.dBSnapshotArgsForCall) -} - -func (fake *Model) DBSnapshotCalls(stub func(string) (*db.Snapshot, error)) { - fake.dBSnapshotMutex.Lock() - defer fake.dBSnapshotMutex.Unlock() - fake.DBSnapshotStub = stub -} - -func (fake *Model) DBSnapshotArgsForCall(i int) string { - fake.dBSnapshotMutex.RLock() - defer fake.dBSnapshotMutex.RUnlock() - argsForCall := fake.dBSnapshotArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *Model) DBSnapshotReturns(result1 *db.Snapshot, result2 error) { - fake.dBSnapshotMutex.Lock() - defer fake.dBSnapshotMutex.Unlock() - fake.DBSnapshotStub = nil - fake.dBSnapshotReturns = struct { - result1 *db.Snapshot - result2 error - }{result1, result2} -} - -func (fake *Model) DBSnapshotReturnsOnCall(i int, result1 *db.Snapshot, result2 error) { - fake.dBSnapshotMutex.Lock() - defer fake.dBSnapshotMutex.Unlock() - fake.DBSnapshotStub = nil - if fake.dBSnapshotReturnsOnCall == nil { - fake.dBSnapshotReturnsOnCall = make(map[int]struct { - result1 *db.Snapshot - result2 error - }) - } - fake.dBSnapshotReturnsOnCall[i] = struct { - result1 *db.Snapshot - result2 error - }{result1, result2} -} - func (fake *Model) DelayScan(arg1 string, arg2 time.Duration) { fake.delayScanMutex.Lock() fake.delayScanArgsForCall = append(fake.delayScanArgsForCall, struct { @@ -1771,71 +1867,6 @@ func (fake *Model) GetFolderVersionsReturnsOnCall(i int, result1 map[string][]ve }{result1, result2} } -func (fake *Model) GetMtimeMapping(arg1 string, arg2 string) (fs.MtimeMapping, error) { - fake.getMtimeMappingMutex.Lock() - ret, specificReturn := fake.getMtimeMappingReturnsOnCall[len(fake.getMtimeMappingArgsForCall)] - fake.getMtimeMappingArgsForCall = append(fake.getMtimeMappingArgsForCall, struct { - arg1 string - arg2 string - }{arg1, arg2}) - stub := fake.GetMtimeMappingStub - fakeReturns := fake.getMtimeMappingReturns - fake.recordInvocation("GetMtimeMapping", []interface{}{arg1, arg2}) - fake.getMtimeMappingMutex.Unlock() - if stub != nil { - return stub(arg1, arg2) - } - if specificReturn { - return ret.result1, ret.result2 - } - return fakeReturns.result1, fakeReturns.result2 -} - -func (fake *Model) GetMtimeMappingCallCount() int { - fake.getMtimeMappingMutex.RLock() - defer fake.getMtimeMappingMutex.RUnlock() - return len(fake.getMtimeMappingArgsForCall) -} - -func (fake *Model) GetMtimeMappingCalls(stub func(string, string) (fs.MtimeMapping, error)) { - fake.getMtimeMappingMutex.Lock() - defer fake.getMtimeMappingMutex.Unlock() - fake.GetMtimeMappingStub = stub -} - -func (fake *Model) GetMtimeMappingArgsForCall(i int) (string, string) { - fake.getMtimeMappingMutex.RLock() - defer fake.getMtimeMappingMutex.RUnlock() - argsForCall := fake.getMtimeMappingArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 -} - -func (fake *Model) GetMtimeMappingReturns(result1 fs.MtimeMapping, result2 error) { - fake.getMtimeMappingMutex.Lock() - defer fake.getMtimeMappingMutex.Unlock() - fake.GetMtimeMappingStub = nil - fake.getMtimeMappingReturns = struct { - result1 fs.MtimeMapping - result2 error - }{result1, result2} -} - -func (fake *Model) GetMtimeMappingReturnsOnCall(i int, result1 fs.MtimeMapping, result2 error) { - fake.getMtimeMappingMutex.Lock() - defer fake.getMtimeMappingMutex.Unlock() - fake.GetMtimeMappingStub = nil - if fake.getMtimeMappingReturnsOnCall == nil { - fake.getMtimeMappingReturnsOnCall = make(map[int]struct { - result1 fs.MtimeMapping - result2 error - }) - } - fake.getMtimeMappingReturnsOnCall[i] = struct { - result1 fs.MtimeMapping - result2 error - }{result1, result2} -} - func (fake *Model) GlobalDirectoryTree(arg1 string, arg2 string, arg3 int, arg4 bool) ([]*model.TreeEntry, error) { fake.globalDirectoryTreeMutex.Lock() ret, specificReturn := fake.globalDirectoryTreeReturnsOnCall[len(fake.globalDirectoryTreeArgsForCall)] @@ -1903,6 +1934,70 @@ func (fake *Model) GlobalDirectoryTreeReturnsOnCall(i int, result1 []*model.Tree }{result1, result2} } +func (fake *Model) GlobalSize(arg1 string) (db.Counts, error) { + fake.globalSizeMutex.Lock() + ret, specificReturn := fake.globalSizeReturnsOnCall[len(fake.globalSizeArgsForCall)] + fake.globalSizeArgsForCall = append(fake.globalSizeArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.GlobalSizeStub + fakeReturns := fake.globalSizeReturns + fake.recordInvocation("GlobalSize", []interface{}{arg1}) + fake.globalSizeMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) GlobalSizeCallCount() int { + fake.globalSizeMutex.RLock() + defer fake.globalSizeMutex.RUnlock() + return len(fake.globalSizeArgsForCall) +} + +func (fake *Model) GlobalSizeCalls(stub func(string) (db.Counts, error)) { + fake.globalSizeMutex.Lock() + defer fake.globalSizeMutex.Unlock() + fake.GlobalSizeStub = stub +} + +func (fake *Model) GlobalSizeArgsForCall(i int) string { + fake.globalSizeMutex.RLock() + defer fake.globalSizeMutex.RUnlock() + argsForCall := fake.globalSizeArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *Model) GlobalSizeReturns(result1 db.Counts, result2 error) { + fake.globalSizeMutex.Lock() + defer fake.globalSizeMutex.Unlock() + fake.GlobalSizeStub = nil + fake.globalSizeReturns = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + +func (fake *Model) GlobalSizeReturnsOnCall(i int, result1 db.Counts, result2 error) { + fake.globalSizeMutex.Lock() + defer fake.globalSizeMutex.Unlock() + fake.GlobalSizeStub = nil + if fake.globalSizeReturnsOnCall == nil { + fake.globalSizeReturnsOnCall = make(map[int]struct { + result1 db.Counts + result2 error + }) + } + fake.globalSizeReturnsOnCall[i] = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + func (fake *Model) Index(arg1 protocol.Connection, arg2 *protocol.Index) error { fake.indexMutex.Lock() ret, specificReturn := fake.indexReturnsOnCall[len(fake.indexArgsForCall)] @@ -2160,6 +2255,202 @@ func (fake *Model) LocalChangedFolderFilesReturnsOnCall(i int, result1 []protoco }{result1, result2} } +func (fake *Model) LocalFiles(arg1 string, arg2 protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { + fake.localFilesMutex.Lock() + ret, specificReturn := fake.localFilesReturnsOnCall[len(fake.localFilesArgsForCall)] + fake.localFilesArgsForCall = append(fake.localFilesArgsForCall, struct { + arg1 string + arg2 protocol.DeviceID + }{arg1, arg2}) + stub := fake.LocalFilesStub + fakeReturns := fake.localFilesReturns + fake.recordInvocation("LocalFiles", []interface{}{arg1, arg2}) + fake.localFilesMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) LocalFilesCallCount() int { + fake.localFilesMutex.RLock() + defer fake.localFilesMutex.RUnlock() + return len(fake.localFilesArgsForCall) +} + +func (fake *Model) LocalFilesCalls(stub func(string, protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error)) { + fake.localFilesMutex.Lock() + defer fake.localFilesMutex.Unlock() + fake.LocalFilesStub = stub +} + +func (fake *Model) LocalFilesArgsForCall(i int) (string, protocol.DeviceID) { + fake.localFilesMutex.RLock() + defer fake.localFilesMutex.RUnlock() + argsForCall := fake.localFilesArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *Model) LocalFilesReturns(result1 iter.Seq[protocol.FileInfo], result2 func() error) { + fake.localFilesMutex.Lock() + defer fake.localFilesMutex.Unlock() + fake.LocalFilesStub = nil + fake.localFilesReturns = struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + }{result1, result2} +} + +func (fake *Model) LocalFilesReturnsOnCall(i int, result1 iter.Seq[protocol.FileInfo], result2 func() error) { + fake.localFilesMutex.Lock() + defer fake.localFilesMutex.Unlock() + fake.LocalFilesStub = nil + if fake.localFilesReturnsOnCall == nil { + fake.localFilesReturnsOnCall = make(map[int]struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + }) + } + fake.localFilesReturnsOnCall[i] = struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + }{result1, result2} +} + +func (fake *Model) LocalFilesSequenced(arg1 string, arg2 protocol.DeviceID, arg3 int64) (iter.Seq[protocol.FileInfo], func() error) { + fake.localFilesSequencedMutex.Lock() + ret, specificReturn := fake.localFilesSequencedReturnsOnCall[len(fake.localFilesSequencedArgsForCall)] + fake.localFilesSequencedArgsForCall = append(fake.localFilesSequencedArgsForCall, struct { + arg1 string + arg2 protocol.DeviceID + arg3 int64 + }{arg1, arg2, arg3}) + stub := fake.LocalFilesSequencedStub + fakeReturns := fake.localFilesSequencedReturns + fake.recordInvocation("LocalFilesSequenced", []interface{}{arg1, arg2, arg3}) + fake.localFilesSequencedMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) LocalFilesSequencedCallCount() int { + fake.localFilesSequencedMutex.RLock() + defer fake.localFilesSequencedMutex.RUnlock() + return len(fake.localFilesSequencedArgsForCall) +} + +func (fake *Model) LocalFilesSequencedCalls(stub func(string, protocol.DeviceID, int64) (iter.Seq[protocol.FileInfo], func() error)) { + fake.localFilesSequencedMutex.Lock() + defer fake.localFilesSequencedMutex.Unlock() + fake.LocalFilesSequencedStub = stub +} + +func (fake *Model) LocalFilesSequencedArgsForCall(i int) (string, protocol.DeviceID, int64) { + fake.localFilesSequencedMutex.RLock() + defer fake.localFilesSequencedMutex.RUnlock() + argsForCall := fake.localFilesSequencedArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *Model) LocalFilesSequencedReturns(result1 iter.Seq[protocol.FileInfo], result2 func() error) { + fake.localFilesSequencedMutex.Lock() + defer fake.localFilesSequencedMutex.Unlock() + fake.LocalFilesSequencedStub = nil + fake.localFilesSequencedReturns = struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + }{result1, result2} +} + +func (fake *Model) LocalFilesSequencedReturnsOnCall(i int, result1 iter.Seq[protocol.FileInfo], result2 func() error) { + fake.localFilesSequencedMutex.Lock() + defer fake.localFilesSequencedMutex.Unlock() + fake.LocalFilesSequencedStub = nil + if fake.localFilesSequencedReturnsOnCall == nil { + fake.localFilesSequencedReturnsOnCall = make(map[int]struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + }) + } + fake.localFilesSequencedReturnsOnCall[i] = struct { + result1 iter.Seq[protocol.FileInfo] + result2 func() error + }{result1, result2} +} + +func (fake *Model) LocalSize(arg1 string, arg2 protocol.DeviceID) (db.Counts, error) { + fake.localSizeMutex.Lock() + ret, specificReturn := fake.localSizeReturnsOnCall[len(fake.localSizeArgsForCall)] + fake.localSizeArgsForCall = append(fake.localSizeArgsForCall, struct { + arg1 string + arg2 protocol.DeviceID + }{arg1, arg2}) + stub := fake.LocalSizeStub + fakeReturns := fake.localSizeReturns + fake.recordInvocation("LocalSize", []interface{}{arg1, arg2}) + fake.localSizeMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) LocalSizeCallCount() int { + fake.localSizeMutex.RLock() + defer fake.localSizeMutex.RUnlock() + return len(fake.localSizeArgsForCall) +} + +func (fake *Model) LocalSizeCalls(stub func(string, protocol.DeviceID) (db.Counts, error)) { + fake.localSizeMutex.Lock() + defer fake.localSizeMutex.Unlock() + fake.LocalSizeStub = stub +} + +func (fake *Model) LocalSizeArgsForCall(i int) (string, protocol.DeviceID) { + fake.localSizeMutex.RLock() + defer fake.localSizeMutex.RUnlock() + argsForCall := fake.localSizeArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *Model) LocalSizeReturns(result1 db.Counts, result2 error) { + fake.localSizeMutex.Lock() + defer fake.localSizeMutex.Unlock() + fake.LocalSizeStub = nil + fake.localSizeReturns = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + +func (fake *Model) LocalSizeReturnsOnCall(i int, result1 db.Counts, result2 error) { + fake.localSizeMutex.Lock() + defer fake.localSizeMutex.Unlock() + fake.LocalSizeStub = nil + if fake.localSizeReturnsOnCall == nil { + fake.localSizeReturnsOnCall = make(map[int]struct { + result1 db.Counts + result2 error + }) + } + fake.localSizeReturnsOnCall[i] = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + func (fake *Model) NeedFolderFiles(arg1 string, arg2 int, arg3 int) ([]protocol.FileInfo, []protocol.FileInfo, []protocol.FileInfo, error) { fake.needFolderFilesMutex.Lock() ret, specificReturn := fake.needFolderFilesReturnsOnCall[len(fake.needFolderFilesArgsForCall)] @@ -2232,6 +2523,71 @@ func (fake *Model) NeedFolderFilesReturnsOnCall(i int, result1 []protocol.FileIn }{result1, result2, result3, result4} } +func (fake *Model) NeedSize(arg1 string, arg2 protocol.DeviceID) (db.Counts, error) { + fake.needSizeMutex.Lock() + ret, specificReturn := fake.needSizeReturnsOnCall[len(fake.needSizeArgsForCall)] + fake.needSizeArgsForCall = append(fake.needSizeArgsForCall, struct { + arg1 string + arg2 protocol.DeviceID + }{arg1, arg2}) + stub := fake.NeedSizeStub + fakeReturns := fake.needSizeReturns + fake.recordInvocation("NeedSize", []interface{}{arg1, arg2}) + fake.needSizeMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) NeedSizeCallCount() int { + fake.needSizeMutex.RLock() + defer fake.needSizeMutex.RUnlock() + return len(fake.needSizeArgsForCall) +} + +func (fake *Model) NeedSizeCalls(stub func(string, protocol.DeviceID) (db.Counts, error)) { + fake.needSizeMutex.Lock() + defer fake.needSizeMutex.Unlock() + fake.NeedSizeStub = stub +} + +func (fake *Model) NeedSizeArgsForCall(i int) (string, protocol.DeviceID) { + fake.needSizeMutex.RLock() + defer fake.needSizeMutex.RUnlock() + argsForCall := fake.needSizeArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *Model) NeedSizeReturns(result1 db.Counts, result2 error) { + fake.needSizeMutex.Lock() + defer fake.needSizeMutex.Unlock() + fake.NeedSizeStub = nil + fake.needSizeReturns = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + +func (fake *Model) NeedSizeReturnsOnCall(i int, result1 db.Counts, result2 error) { + fake.needSizeMutex.Lock() + defer fake.needSizeMutex.Unlock() + fake.NeedSizeStub = nil + if fake.needSizeReturnsOnCall == nil { + fake.needSizeReturnsOnCall = make(map[int]struct { + result1 db.Counts + result2 error + }) + } + fake.needSizeReturnsOnCall[i] = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + func (fake *Model) OnHello(arg1 protocol.DeviceID, arg2 net.Addr, arg3 protocol.Hello) error { fake.onHelloMutex.Lock() ret, specificReturn := fake.onHelloReturnsOnCall[len(fake.onHelloArgsForCall)] @@ -2447,6 +2803,70 @@ func (fake *Model) PendingFoldersReturnsOnCall(i int, result1 map[string]db.Pend }{result1, result2} } +func (fake *Model) ReceiveOnlySize(arg1 string) (db.Counts, error) { + fake.receiveOnlySizeMutex.Lock() + ret, specificReturn := fake.receiveOnlySizeReturnsOnCall[len(fake.receiveOnlySizeArgsForCall)] + fake.receiveOnlySizeArgsForCall = append(fake.receiveOnlySizeArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.ReceiveOnlySizeStub + fakeReturns := fake.receiveOnlySizeReturns + fake.recordInvocation("ReceiveOnlySize", []interface{}{arg1}) + fake.receiveOnlySizeMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) ReceiveOnlySizeCallCount() int { + fake.receiveOnlySizeMutex.RLock() + defer fake.receiveOnlySizeMutex.RUnlock() + return len(fake.receiveOnlySizeArgsForCall) +} + +func (fake *Model) ReceiveOnlySizeCalls(stub func(string) (db.Counts, error)) { + fake.receiveOnlySizeMutex.Lock() + defer fake.receiveOnlySizeMutex.Unlock() + fake.ReceiveOnlySizeStub = stub +} + +func (fake *Model) ReceiveOnlySizeArgsForCall(i int) string { + fake.receiveOnlySizeMutex.RLock() + defer fake.receiveOnlySizeMutex.RUnlock() + argsForCall := fake.receiveOnlySizeArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *Model) ReceiveOnlySizeReturns(result1 db.Counts, result2 error) { + fake.receiveOnlySizeMutex.Lock() + defer fake.receiveOnlySizeMutex.Unlock() + fake.ReceiveOnlySizeStub = nil + fake.receiveOnlySizeReturns = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + +func (fake *Model) ReceiveOnlySizeReturnsOnCall(i int, result1 db.Counts, result2 error) { + fake.receiveOnlySizeMutex.Lock() + defer fake.receiveOnlySizeMutex.Unlock() + fake.ReceiveOnlySizeStub = nil + if fake.receiveOnlySizeReturnsOnCall == nil { + fake.receiveOnlySizeReturnsOnCall = make(map[int]struct { + result1 db.Counts + result2 error + }) + } + fake.receiveOnlySizeReturnsOnCall[i] = struct { + result1 db.Counts + result2 error + }{result1, result2} +} + func (fake *Model) RemoteNeedFolderFiles(arg1 string, arg2 protocol.DeviceID, arg3 int, arg4 int) ([]protocol.FileInfo, error) { fake.remoteNeedFolderFilesMutex.Lock() ret, specificReturn := fake.remoteNeedFolderFilesReturnsOnCall[len(fake.remoteNeedFolderFilesArgsForCall)] @@ -2514,6 +2934,70 @@ func (fake *Model) RemoteNeedFolderFilesReturnsOnCall(i int, result1 []protocol. }{result1, result2} } +func (fake *Model) RemoteSequences(arg1 string) (map[protocol.DeviceID]int64, error) { + fake.remoteSequencesMutex.Lock() + ret, specificReturn := fake.remoteSequencesReturnsOnCall[len(fake.remoteSequencesArgsForCall)] + fake.remoteSequencesArgsForCall = append(fake.remoteSequencesArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.RemoteSequencesStub + fakeReturns := fake.remoteSequencesReturns + fake.recordInvocation("RemoteSequences", []interface{}{arg1}) + fake.remoteSequencesMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) RemoteSequencesCallCount() int { + fake.remoteSequencesMutex.RLock() + defer fake.remoteSequencesMutex.RUnlock() + return len(fake.remoteSequencesArgsForCall) +} + +func (fake *Model) RemoteSequencesCalls(stub func(string) (map[protocol.DeviceID]int64, error)) { + fake.remoteSequencesMutex.Lock() + defer fake.remoteSequencesMutex.Unlock() + fake.RemoteSequencesStub = stub +} + +func (fake *Model) RemoteSequencesArgsForCall(i int) string { + fake.remoteSequencesMutex.RLock() + defer fake.remoteSequencesMutex.RUnlock() + argsForCall := fake.remoteSequencesArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *Model) RemoteSequencesReturns(result1 map[protocol.DeviceID]int64, result2 error) { + fake.remoteSequencesMutex.Lock() + defer fake.remoteSequencesMutex.Unlock() + fake.RemoteSequencesStub = nil + fake.remoteSequencesReturns = struct { + result1 map[protocol.DeviceID]int64 + result2 error + }{result1, result2} +} + +func (fake *Model) RemoteSequencesReturnsOnCall(i int, result1 map[protocol.DeviceID]int64, result2 error) { + fake.remoteSequencesMutex.Lock() + defer fake.remoteSequencesMutex.Unlock() + fake.RemoteSequencesStub = nil + if fake.remoteSequencesReturnsOnCall == nil { + fake.remoteSequencesReturnsOnCall = make(map[int]struct { + result1 map[protocol.DeviceID]int64 + result2 error + }) + } + fake.remoteSequencesReturnsOnCall[i] = struct { + result1 map[protocol.DeviceID]int64 + result2 error + }{result1, result2} +} + func (fake *Model) Request(arg1 protocol.Connection, arg2 *protocol.Request) (protocol.RequestResponse, error) { fake.requestMutex.Lock() ret, specificReturn := fake.requestReturnsOnCall[len(fake.requestArgsForCall)] @@ -2995,6 +3479,71 @@ func (fake *Model) ScanFoldersReturnsOnCall(i int, result1 map[string]error) { }{result1} } +func (fake *Model) Sequence(arg1 string, arg2 protocol.DeviceID) (int64, error) { + fake.sequenceMutex.Lock() + ret, specificReturn := fake.sequenceReturnsOnCall[len(fake.sequenceArgsForCall)] + fake.sequenceArgsForCall = append(fake.sequenceArgsForCall, struct { + arg1 string + arg2 protocol.DeviceID + }{arg1, arg2}) + stub := fake.SequenceStub + fakeReturns := fake.sequenceReturns + fake.recordInvocation("Sequence", []interface{}{arg1, arg2}) + fake.sequenceMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *Model) SequenceCallCount() int { + fake.sequenceMutex.RLock() + defer fake.sequenceMutex.RUnlock() + return len(fake.sequenceArgsForCall) +} + +func (fake *Model) SequenceCalls(stub func(string, protocol.DeviceID) (int64, error)) { + fake.sequenceMutex.Lock() + defer fake.sequenceMutex.Unlock() + fake.SequenceStub = stub +} + +func (fake *Model) SequenceArgsForCall(i int) (string, protocol.DeviceID) { + fake.sequenceMutex.RLock() + defer fake.sequenceMutex.RUnlock() + argsForCall := fake.sequenceArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *Model) SequenceReturns(result1 int64, result2 error) { + fake.sequenceMutex.Lock() + defer fake.sequenceMutex.Unlock() + fake.SequenceStub = nil + fake.sequenceReturns = struct { + result1 int64 + result2 error + }{result1, result2} +} + +func (fake *Model) SequenceReturnsOnCall(i int, result1 int64, result2 error) { + fake.sequenceMutex.Lock() + defer fake.sequenceMutex.Unlock() + fake.SequenceStub = nil + if fake.sequenceReturnsOnCall == nil { + fake.sequenceReturnsOnCall = make(map[int]struct { + result1 int64 + result2 error + }) + } + fake.sequenceReturnsOnCall[i] = struct { + result1 int64 + result2 error + }{result1, result2} +} + func (fake *Model) Serve(arg1 context.Context) error { fake.serveMutex.Lock() ret, specificReturn := fake.serveReturnsOnCall[len(fake.serveArgsForCall)] @@ -3290,6 +3839,8 @@ func (fake *Model) Invocations() map[string][][]interface{} { defer fake.invocationsMutex.RUnlock() fake.addConnectionMutex.RLock() defer fake.addConnectionMutex.RUnlock() + fake.allGlobalFilesMutex.RLock() + defer fake.allGlobalFilesMutex.RUnlock() fake.availabilityMutex.RLock() defer fake.availabilityMutex.RUnlock() fake.bringToFrontMutex.RLock() @@ -3310,8 +3861,6 @@ func (fake *Model) Invocations() map[string][][]interface{} { defer fake.currentGlobalFileMutex.RUnlock() fake.currentIgnoresMutex.RLock() defer fake.currentIgnoresMutex.RUnlock() - fake.dBSnapshotMutex.RLock() - defer fake.dBSnapshotMutex.RUnlock() fake.delayScanMutex.RLock() defer fake.delayScanMutex.RUnlock() fake.deviceStatisticsMutex.RLock() @@ -3330,10 +3879,10 @@ func (fake *Model) Invocations() map[string][][]interface{} { defer fake.folderStatisticsMutex.RUnlock() fake.getFolderVersionsMutex.RLock() defer fake.getFolderVersionsMutex.RUnlock() - fake.getMtimeMappingMutex.RLock() - defer fake.getMtimeMappingMutex.RUnlock() fake.globalDirectoryTreeMutex.RLock() defer fake.globalDirectoryTreeMutex.RUnlock() + fake.globalSizeMutex.RLock() + defer fake.globalSizeMutex.RUnlock() fake.indexMutex.RLock() defer fake.indexMutex.RUnlock() fake.indexUpdateMutex.RLock() @@ -3342,8 +3891,16 @@ func (fake *Model) Invocations() map[string][][]interface{} { defer fake.loadIgnoresMutex.RUnlock() fake.localChangedFolderFilesMutex.RLock() defer fake.localChangedFolderFilesMutex.RUnlock() + fake.localFilesMutex.RLock() + defer fake.localFilesMutex.RUnlock() + fake.localFilesSequencedMutex.RLock() + defer fake.localFilesSequencedMutex.RUnlock() + fake.localSizeMutex.RLock() + defer fake.localSizeMutex.RUnlock() fake.needFolderFilesMutex.RLock() defer fake.needFolderFilesMutex.RUnlock() + fake.needSizeMutex.RLock() + defer fake.needSizeMutex.RUnlock() fake.onHelloMutex.RLock() defer fake.onHelloMutex.RUnlock() fake.overrideMutex.RLock() @@ -3352,8 +3909,12 @@ func (fake *Model) Invocations() map[string][][]interface{} { defer fake.pendingDevicesMutex.RUnlock() fake.pendingFoldersMutex.RLock() defer fake.pendingFoldersMutex.RUnlock() + fake.receiveOnlySizeMutex.RLock() + defer fake.receiveOnlySizeMutex.RUnlock() fake.remoteNeedFolderFilesMutex.RLock() defer fake.remoteNeedFolderFilesMutex.RUnlock() + fake.remoteSequencesMutex.RLock() + defer fake.remoteSequencesMutex.RUnlock() fake.requestMutex.RLock() defer fake.requestMutex.RUnlock() fake.requestGlobalMutex.RLock() @@ -3370,6 +3931,8 @@ func (fake *Model) Invocations() map[string][][]interface{} { defer fake.scanFolderSubdirsMutex.RUnlock() fake.scanFoldersMutex.RLock() defer fake.scanFoldersMutex.RUnlock() + fake.sequenceMutex.RLock() + defer fake.sequenceMutex.RUnlock() fake.serveMutex.RLock() defer fake.serveMutex.RUnlock() fake.setIgnoresMutex.RLock() diff --git a/lib/model/model.go b/lib/model/model.go index 63bdbea2f..9d46d925d 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -16,11 +16,13 @@ import ( "errors" "fmt" "io" + "iter" "net" "os" "path/filepath" "reflect" "runtime" + "slices" "strings" stdsync "sync" "sync/atomic" @@ -28,10 +30,11 @@ import ( "github.com/thejerf/suture/v4" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/connections" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/ignore" @@ -93,7 +96,16 @@ type Model interface { GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]error, error) - DBSnapshot(folder string) (*db.Snapshot, error) + LocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) + LocalFilesSequenced(folder string, device protocol.DeviceID, startSet int64) (iter.Seq[protocol.FileInfo], func() error) + LocalSize(folder string, device protocol.DeviceID) (db.Counts, error) + GlobalSize(folder string) (db.Counts, error) + NeedSize(folder string, device protocol.DeviceID) (db.Counts, error) + ReceiveOnlySize(folder string) (db.Counts, error) + Sequence(folder string, device protocol.DeviceID) (int64, error) + AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) + RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) + NeedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, []protocol.FileInfo, []protocol.FileInfo, error) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, page, perpage int) ([]protocol.FileInfo, error) LocalChangedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, error) @@ -101,7 +113,6 @@ type Model interface { CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool, error) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) - GetMtimeMapping(folder string, file string) (fs.MtimeMapping, error) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) ([]Availability, error) Completion(device protocol.DeviceID, folder string) (FolderCompletion, error) @@ -127,12 +138,11 @@ type model struct { // constructor parameters cfg config.Wrapper id protocol.DeviceID - db *db.Lowlevel + sdb db.DB protectedFiles []string evLogger events.Logger // constant or concurrency safe fields - finder *db.BlockFinder progressEmitter *ProgressEmitter shortID protocol.ShortID // globalRequestLimiter limits the amount of data in concurrent incoming @@ -145,11 +155,11 @@ type model struct { started chan struct{} keyGen *protocol.KeyGenerator promotionTimer *time.Timer + observed *db.ObservedDB // fields protected by mut mut sync.RWMutex folderCfgs map[string]config.FolderConfiguration // folder -> cfg - folderFiles map[string]*db.FileSet // folder -> files deviceStatRefs map[protocol.DeviceID]*stats.DeviceStatisticsReference // deviceID -> statsRef folderIgnores map[string]*ignore.Matcher // folder -> matcher object folderRunners *serviceMap[string, service] // folder -> puller or scanner @@ -173,7 +183,7 @@ type model struct { var _ config.Verifier = &model{} -type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *semaphore.Semaphore) service +type folderFactory func(*model, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *semaphore.Semaphore) service var folderFactories = make(map[config.FolderType]folderFactory) @@ -202,7 +212,7 @@ var ( // NewModel creates and starts a new model. The model starts in read-only mode, // where it sends index information to connected peers and responds to requests // for file data without altering the local folder in any way. -func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger, keyGen *protocol.KeyGenerator) Model { +func NewModel(cfg config.Wrapper, id protocol.DeviceID, sdb db.DB, protectedFiles []string, evLogger events.Logger, keyGen *protocol.KeyGenerator) Model { spec := svcutil.SpecWithDebugLogger(l) m := &model{ Supervisor: suture.New("model", spec), @@ -210,12 +220,11 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protec // constructor parameters cfg: cfg, id: id, - db: ldb, + sdb: sdb, protectedFiles: protectedFiles, evLogger: evLogger, // constant or concurrency safe fields - finder: db.NewBlockFinder(ldb), progressEmitter: NewProgressEmitter(cfg, evLogger), shortID: id.Short(), globalRequestLimiter: semaphore.New(1024 * cfg.Options().MaxConcurrentIncomingRequestKiB()), @@ -224,11 +233,11 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protec started: make(chan struct{}), keyGen: keyGen, promotionTimer: time.NewTimer(0), + observed: db.NewObservedDB(sdb), // fields protected by mut mut: sync.NewRWMutex(), folderCfgs: make(map[string]config.FolderConfiguration), - folderFiles: make(map[string]*db.FileSet), deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference), folderIgnores: make(map[string]*ignore.Matcher), folderRunners: newServiceMap[string, service](evLogger), @@ -246,7 +255,7 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protec indexHandlers: newServiceMap[protocol.DeviceID, *indexHandlerRegistry](evLogger), } for devID, cfg := range cfg.Devices() { - m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID) + m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(db.NewTyped(sdb, "devicestats/"+devID.String())) m.setConnRequestLimitersLocked(cfg) } m.Add(m.folderRunners) @@ -327,21 +336,20 @@ func (m *model) fatal(err error) { } // Need to hold lock on m.mut when calling this. -func (m *model) addAndStartFolderLocked(cfg config.FolderConfiguration, fset *db.FileSet, cacheIgnoredFiles bool) { - ignores := ignore.New(cfg.Filesystem(nil), ignore.WithCache(cacheIgnoredFiles)) +func (m *model) addAndStartFolderLocked(cfg config.FolderConfiguration, cacheIgnoredFiles bool) { + ignores := ignore.New(cfg.Filesystem(), ignore.WithCache(cacheIgnoredFiles)) if cfg.Type != config.FolderTypeReceiveEncrypted { if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) { l.Warnln("Loading ignores:", err) } } - m.addAndStartFolderLockedWithIgnores(cfg, fset, ignores) + m.addAndStartFolderLockedWithIgnores(cfg, ignores) } // Only needed for testing, use addAndStartFolderLocked instead. -func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguration, fset *db.FileSet, ignores *ignore.Matcher) { +func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguration, ignores *ignore.Matcher) { m.folderCfgs[cfg.ID] = cfg - m.folderFiles[cfg.ID] = fset m.folderIgnores[cfg.ID] = ignores _, ok := m.folderRunners.Get(cfg.ID) @@ -360,16 +368,19 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio // Find any devices for which we hold the index in the db, but the folder // is not shared, and drop it. expected := mapDevices(cfg.DeviceIDs()) - for _, available := range fset.ListDevices() { + devs, _ := m.sdb.ListDevicesForFolder(cfg.ID) + for _, available := range devs { if _, ok := expected[available]; !ok { l.Debugln("dropping", folder, "state for", available) - fset.Drop(available) + m.sdb.DropAllFiles(folder, available) } } - v, ok := fset.Sequence(protocol.LocalDeviceID), true - indexHasFiles := ok && v > 0 - if !indexHasFiles { + seq, err := m.sdb.GetDeviceSequence(folder, protocol.LocalDeviceID) + if err != nil { + panic(fmt.Errorf("error getting sequence number: %w", err)) + } + if seq == 0 { // It's a blank folder, so this may the first time we're looking at // it. Attempt to create and tag with our marker as appropriate. We // don't really do anything with errors at this point except warn - @@ -392,7 +403,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio } // These are our metadata files, and they should always be hidden. - ffs := cfg.Filesystem(nil) + ffs := cfg.Filesystem() _ = ffs.Hide(config.DefaultMarkerName) _ = ffs.Hide(versioner.DefaultPath) _ = ffs.Hide(".stignore") @@ -409,7 +420,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio m.warnAboutOverwritingProtectedFiles(cfg, ignores) - p := folderFactory(m, fset, ignores, cfg, ver, m.evLogger, m.folderIOLimiter) + p := folderFactory(m, ignores, cfg, ver, m.evLogger, m.folderIOLimiter) m.folderRunners.Add(folder, p) l.Infof("Ready to synchronize %s (%s)", cfg.Description(), cfg.Type) @@ -421,7 +432,7 @@ func (m *model) warnAboutOverwritingProtectedFiles(cfg config.FolderConfiguratio } // This is a bit of a hack. - ffs := cfg.Filesystem(nil) + ffs := cfg.Filesystem() if ffs.Type() != fs.FilesystemTypeBasic { return } @@ -468,7 +479,7 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) { // otherwise not removable) Syncthing-specific marker files. if err := cfg.RemoveMarker(); err != nil && !errors.Is(err, os.ErrNotExist) { moved := config.DefaultMarkerName + time.Now().Format(".removed-20060102-150405") - fs := cfg.Filesystem(nil) + fs := cfg.Filesystem() _ = fs.Rename(config.DefaultMarkerName, moved) } } @@ -482,7 +493,7 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) { m.mut.Unlock() // Remove it from the database - db.DropFolder(m.db, cfg.ID) + m.sdb.DropFolder(cfg.ID) } // Need to hold lock on m.mut when calling this. @@ -490,7 +501,6 @@ func (m *model) cleanupFolderLocked(cfg config.FolderConfiguration) { // clear up our config maps m.folderRunners.Remove(cfg.ID) delete(m.folderCfgs, cfg.ID) - delete(m.folderFiles, cfg.ID) delete(m.folderIgnores, cfg.ID) delete(m.folderVersioners, cfg.ID) delete(m.folderEncryptionPasswordTokens, cfg.ID) @@ -525,28 +535,14 @@ func (m *model) restartFolder(from, to config.FolderConfiguration, cacheIgnoredF m.mut.Lock() defer m.mut.Unlock() - // Cache the (maybe) existing fset before it's removed by cleanupFolderLocked - fset := m.folderFiles[folder] - fsetNil := fset == nil - m.cleanupFolderLocked(from) if !to.Paused { - if fsetNil { - // Create a new fset. Might take a while and we do it under - // locking, but it's unsafe to create fset:s concurrently so - // that's the price we pay. - var err error - fset, err = db.NewFileSet(folder, m.db) - if err != nil { - return fmt.Errorf("restarting %v: %w", to.Description(), err) - } - } - m.addAndStartFolderLocked(to, fset, cacheIgnoredFiles) + m.addAndStartFolderLocked(to, cacheIgnoredFiles) } runner, _ := m.folderRunners.Get(to.ID) m.indexHandlers.Each(func(_ protocol.DeviceID, r *indexHandlerRegistry) error { - r.RegisterFolderState(to, fset, runner) + r.RegisterFolderState(to, runner) return nil }) @@ -568,22 +564,14 @@ func (m *model) newFolder(cfg config.FolderConfiguration, cacheIgnoredFiles bool m.mut.Lock() defer m.mut.Unlock() - // Creating the fileset can take a long time (metadata calculation), but - // nevertheless should happen inside the lock (same as when restarting - // a folder). - fset, err := db.NewFileSet(cfg.ID, m.db) - if err != nil { - return fmt.Errorf("adding %v: %w", cfg.Description(), err) - } - - m.addAndStartFolderLocked(cfg, fset, cacheIgnoredFiles) + m.addAndStartFolderLocked(cfg, cacheIgnoredFiles) // Cluster configs might be received and processed before reaching this // point, i.e. before the folder is started. If that's the case, start // index senders here. m.indexHandlers.Each(func(_ protocol.DeviceID, r *indexHandlerRegistry) error { runner, _ := m.folderRunners.Get(cfg.ID) - r.RegisterFolderState(cfg, fset, runner) + r.RegisterFolderState(cfg, runner) return nil }) @@ -923,46 +911,78 @@ func (m *model) Completion(device protocol.DeviceID, folder string) (FolderCompl func (m *model) folderCompletion(device protocol.DeviceID, folder string) (FolderCompletion, error) { m.mut.RLock() err := m.checkFolderRunningRLocked(folder) - rf := m.folderFiles[folder] m.mut.RUnlock() if err != nil { return FolderCompletion{}, err } - snap, err := rf.Snapshot() - if err != nil { - return FolderCompletion{}, err - } - defer snap.Release() - m.mut.RLock() state := m.remoteFolderStates[device][folder] downloaded := m.deviceDownloads[device].BytesDownloaded(folder) m.mut.RUnlock() - need := snap.NeedSize(device) + need, err := m.sdb.CountNeed(folder, device) + if err != nil { + return FolderCompletion{}, err + } need.Bytes -= downloaded // This might be more than it really is, because some blocks can be of a smaller size. if need.Bytes < 0 { need.Bytes = 0 } - comp := newFolderCompletion(snap.GlobalSize(), need, snap.Sequence(device), state) + seq, err := m.sdb.GetDeviceSequence(folder, device) + if err != nil { + return FolderCompletion{}, err + } + glob, err := m.sdb.CountGlobal(folder) + if err != nil { + return FolderCompletion{}, err + } + comp := newFolderCompletion(glob, need, seq, state) l.Debugf("%v Completion(%s, %q): %v", m, device, folder, comp.Map()) return comp, nil } -// DBSnapshot returns a snapshot of the database content relevant to the given folder. -func (m *model) DBSnapshot(folder string) (*db.Snapshot, error) { - m.mut.RLock() - err := m.checkFolderRunningRLocked(folder) - rf := m.folderFiles[folder] - m.mut.RUnlock() - if err != nil { - return nil, err - } - return rf.Snapshot() +func (m *model) LocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { + return m.sdb.AllLocalFiles(folder, device) +} + +func (m *model) LocalFilesSequenced(folder string, device protocol.DeviceID, startSeq int64) (iter.Seq[protocol.FileInfo], func() error) { + return m.sdb.AllLocalFilesBySequence(folder, device, startSeq, 0) +} + +func (m *model) AllForBlocksHash(folder string, h []byte) (iter.Seq[db.FileMetadata], func() error) { + return m.sdb.AllLocalFilesWithBlocksHash(folder, h) +} + +func (m *model) LocalSize(folder string, device protocol.DeviceID) (db.Counts, error) { + return m.sdb.CountLocal(folder, device) +} + +func (m *model) GlobalSize(folder string) (db.Counts, error) { + return m.sdb.CountGlobal(folder) +} + +func (m *model) NeedSize(folder string, device protocol.DeviceID) (db.Counts, error) { + return m.sdb.CountNeed(folder, device) +} + +func (m *model) ReceiveOnlySize(folder string) (db.Counts, error) { + return m.sdb.CountReceiveOnlyChanged(folder) +} + +func (m *model) Sequence(folder string, device protocol.DeviceID) (int64, error) { + return m.sdb.GetDeviceSequence(folder, device) +} + +func (m *model) AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) { + return m.sdb.AllGlobalFiles(folder) +} + +func (m *model) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) { + return m.sdb.RemoteSequences(folder) } func (m *model) FolderProgressBytesCompleted(folder string) int64 { @@ -973,20 +993,14 @@ func (m *model) FolderProgressBytesCompleted(folder string) int64 { // progress, queued, and to be queued on next puller iteration. func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, []protocol.FileInfo, []protocol.FileInfo, error) { m.mut.RLock() - rf, rfOk := m.folderFiles[folder] runner, runnerOk := m.folderRunners.Get(folder) - cfg := m.folderCfgs[folder] + cfg, cfgOK := m.folderCfgs[folder] m.mut.RUnlock() - if !rfOk { + if !cfgOK { return nil, nil, nil, ErrFolderMissing } - snap, err := rf.Snapshot() - if err != nil { - return nil, nil, nil, err - } - defer snap.Release() var progress, queued, rest []protocol.FileInfo var seen map[string]struct{} @@ -1000,14 +1014,14 @@ func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.Fi seen = make(map[string]struct{}, len(progressNames)+len(queuedNames)) for i, name := range progressNames { - if f, ok := snap.GetGlobalTruncated(name); ok { + if f, ok, err := m.sdb.GetGlobalFile(folder, name); err == nil && ok { progress[i] = f seen[name] = struct{}{} } } for i, name := range queuedNames { - if f, ok := snap.GetGlobalTruncated(name); ok { + if f, ok, err := m.sdb.GetGlobalFile(folder, name); err == nil && ok { queued[i] = f seen[name] = struct{}{} } @@ -1020,21 +1034,29 @@ func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.Fi p.toSkip -= skipped } - rest = make([]protocol.FileInfo, 0, perpage) - snap.WithNeedTruncated(protocol.LocalDeviceID, func(f protocol.FileInfo) bool { - if cfg.IgnoreDelete && f.IsDeleted() { - return true - } + if p.get > 0 { + rest = make([]protocol.FileInfo, 0, p.get) + it, errFn := m.sdb.AllNeededGlobalFiles(folder, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0) + for f := range it { + if cfg.IgnoreDelete && f.IsDeleted() { + continue + } - if p.skip() { - return true + if p.skip() { + continue + } + if _, ok := seen[f.Name]; !ok { + rest = append(rest, f) + p.get-- + } + if p.get == 0 { + break + } } - if _, ok := seen[f.Name]; !ok { - rest = append(rest, f) - p.get-- + if err := errFn(); err != nil { + return nil, nil, nil, err } - return p.get > 0 - }) + } return progress, queued, rest, nil } @@ -1043,63 +1065,56 @@ func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]protocol.Fi // remote device to become synced with a folder. func (m *model) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, page, perpage int) ([]protocol.FileInfo, error) { m.mut.RLock() - rf, ok := m.folderFiles[folder] + _, ok := m.folderCfgs[folder] m.mut.RUnlock() if !ok { return nil, ErrFolderMissing } - snap, err := rf.Snapshot() - if err != nil { - return nil, err - } - defer snap.Release() - - files := make([]protocol.FileInfo, 0, perpage) - p := newPager(page, perpage) - snap.WithNeedTruncated(device, func(f protocol.FileInfo) bool { - if p.skip() { - return true - } - files = append(files, f) - return !p.done() - }) - return files, nil + it, errFn := m.sdb.AllNeededGlobalFiles(folder, device, config.PullOrderAlphabetic, perpage, (page-1)*perpage) + files := slices.Collect(it) + return files, errFn() } func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]protocol.FileInfo, error) { m.mut.RLock() - rf, ok := m.folderFiles[folder] + _, ok := m.folderCfgs[folder] m.mut.RUnlock() if !ok { return nil, ErrFolderMissing } - snap, err := rf.Snapshot() + ros, err := m.sdb.CountReceiveOnlyChanged(folder) if err != nil { return nil, err } - defer snap.Release() - - if snap.ReceiveOnlyChangedSize().TotalItems() == 0 { + if ros.TotalItems() == 0 { return nil, nil } p := newPager(page, perpage) files := make([]protocol.FileInfo, 0, perpage) - snap.WithHaveTruncated(protocol.LocalDeviceID, func(f protocol.FileInfo) bool { + // This could be made more efficient with a specifically targeted DB + // call + it, errFn := m.sdb.AllLocalFiles(folder, protocol.LocalDeviceID) + for f := range it { if !f.IsReceiveOnlyChanged() { - return true + continue } if p.skip() { - return true + continue } files = append(files, f) - return !p.done() - }) + if p.done() { + break + } + } + if err := errFn(); err != nil { + return nil, err + } return files, nil } @@ -1343,11 +1358,11 @@ func (m *model) ensureIndexHandler(conn protocol.Connection) *indexHandlerRegist } // Create a new index handler for this device. - indexHandlerRegistry = newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.evLogger) + indexHandlerRegistry = newIndexHandlerRegistry(conn, m.sdb, m.deviceDownloads[deviceID], m.evLogger) for id, fcfg := range m.folderCfgs { l.Debugln("Registering folder", id, "for", deviceID.Short()) runner, _ := m.folderRunners.Get(id) - indexHandlerRegistry.RegisterFolderState(fcfg, m.folderFiles[id], runner) + indexHandlerRegistry.RegisterFolderState(fcfg, runner) } m.indexHandlers.Add(deviceID, indexHandlerRegistry) @@ -1376,7 +1391,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi seenFolders := make(map[string]remoteFolderState, len(folders)) updatedPending := make([]updatedPendingFolder, 0, len(folders)) deviceID := deviceCfg.DeviceID - expiredPending, err := m.db.PendingFoldersForDevice(deviceID) + expiredPending, err := m.observed.PendingFoldersForDevice(deviceID) if err != nil { l.Infof("Could not get pending folders for cleanup: %v", err) } @@ -1398,7 +1413,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi of.Label = folder.Label of.ReceiveEncrypted = len(ccDeviceInfos[folder.ID].local.EncryptionPasswordToken) > 0 of.RemoteEncrypted = len(ccDeviceInfos[folder.ID].remote.EncryptionPasswordToken) > 0 - if err := m.db.AddOrUpdatePendingFolder(folder.ID, of, deviceID); err != nil { + if err := m.observed.AddOrUpdatePendingFolder(folder.ID, of, deviceID); err != nil { l.Warnf("Failed to persist pending folder entry to database: %v", err) } if !folder.Paused { @@ -1485,7 +1500,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi expiredPendingList := make([]map[string]string, 0, len(expiredPending)) for folder := range expiredPending { - if err = m.db.RemovePendingFolderForDevice(folder, deviceID); err != nil { + if err = m.observed.RemovePendingFolderForDevice(folder, deviceID); err != nil { msg := "Failed to remove pending folder-device entry" l.Warnf("%v (%v, %v): %v", msg, folder, deviceID, err) m.evLogger.Log(events.Failure, msg) @@ -2015,7 +2030,7 @@ func (m *model) Request(conn protocol.Connection, req *protocol.Request) (out pr // Grab the FS after limiting, as it causes I/O and we want to minimize // the race time between the symlink check and the read. - folderFs := folderCfg.Filesystem(nil) + folderFs := folderCfg.Filesystem() if err := osutil.TraversesSymlink(folderFs, filepath.Dir(req.Name)); err != nil { l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), req.Folder, req.Name, req.Offset, req.Size) @@ -2138,46 +2153,11 @@ func (m *model) recheckFile(deviceID protocol.DeviceID, folder, name string, off } func (m *model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool, error) { - m.mut.RLock() - fs, ok := m.folderFiles[folder] - m.mut.RUnlock() - if !ok { - return protocol.FileInfo{}, false, ErrFolderMissing - } - snap, err := fs.Snapshot() - if err != nil { - return protocol.FileInfo{}, false, err - } - f, ok := snap.Get(protocol.LocalDeviceID, file) - snap.Release() - return f, ok, nil + return m.sdb.GetDeviceFile(folder, protocol.LocalDeviceID, file) } func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) { - m.mut.RLock() - ffs, ok := m.folderFiles[folder] - m.mut.RUnlock() - if !ok { - return protocol.FileInfo{}, false, ErrFolderMissing - } - snap, err := ffs.Snapshot() - if err != nil { - return protocol.FileInfo{}, false, err - } - f, ok := snap.GetGlobal(file) - snap.Release() - return f, ok, nil -} - -func (m *model) GetMtimeMapping(folder string, file string) (fs.MtimeMapping, error) { - m.mut.RLock() - ffs, ok := m.folderFiles[folder] - fcfg := m.folderCfgs[folder] - m.mut.RUnlock() - if !ok { - return fs.MtimeMapping{}, ErrFolderMissing - } - return fs.GetMtimeMapping(fcfg.Filesystem(ffs), file) + return m.sdb.GetGlobalFile(folder, file) } // Connection returns if we are connected to the given device. @@ -2208,7 +2188,7 @@ func (m *model) LoadIgnores(folder string) ([]string, []string, error) { } if !ignoresOk { - ignores = ignore.New(cfg.Filesystem(nil)) + ignores = ignore.New(cfg.Filesystem()) } err := ignores.Load(".stignore") @@ -2263,7 +2243,7 @@ func (m *model) setIgnores(cfg config.FolderConfiguration, content []string) err return err } - if err := ignore.WriteIgnores(cfg.Filesystem(nil), ".stignore", content); err != nil { + if err := ignore.WriteIgnores(cfg.Filesystem(), ".stignore", content); err != nil { l.Warnln("Saving .stignore:", err) return err } @@ -2282,7 +2262,7 @@ func (m *model) setIgnores(cfg config.FolderConfiguration, content []string) err // and add it to a list of known devices ahead of any checks. func (m *model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.Hello) error { if _, ok := m.cfg.Device(remoteID); !ok { - if err := m.db.AddOrUpdatePendingDevice(remoteID, hello.DeviceName, addr.String()); err != nil { + if err := m.observed.AddOrUpdatePendingDevice(remoteID, hello.DeviceName, addr.String()); err != nil { l.Warnf("Failed to persist pending device entry to database: %v", err) } m.evLogger.Log(events.PendingDevicesChanged, map[string][]interface{}{ @@ -2611,13 +2591,11 @@ func (m *model) generateClusterConfigRLocked(device protocol.DeviceID) (*protoco DisableTempIndexes: folderCfg.DisableTempIndexes, } - fs := m.folderFiles[folderCfg.ID] - // Even if we aren't paused, if we haven't started the folder yet // pretend we are. Otherwise the remote might get confused about // the missing index info (and drop all the info). We will send // another cluster config once the folder is started. - protocolFolder.Paused = folderCfg.Paused || fs == nil + protocolFolder.Paused = folderCfg.Paused for _, folderDevice := range folderCfg.Devices { deviceCfg, _ := m.cfg.Device(folderDevice.DeviceID) @@ -2640,14 +2618,12 @@ func (m *model) generateClusterConfigRLocked(device protocol.DeviceID) (*protoco } } - if fs != nil { - if deviceCfg.DeviceID == m.id { - protocolDevice.IndexID = fs.IndexID(protocol.LocalDeviceID) - protocolDevice.MaxSequence = fs.Sequence(protocol.LocalDeviceID) - } else { - protocolDevice.IndexID = fs.IndexID(deviceCfg.DeviceID) - protocolDevice.MaxSequence = fs.Sequence(deviceCfg.DeviceID) - } + if deviceCfg.DeviceID == m.id { + protocolDevice.IndexID, _ = m.sdb.GetIndexID(folderCfg.ID, protocol.LocalDeviceID) + protocolDevice.MaxSequence, _ = m.sdb.GetDeviceSequence(folderCfg.ID, protocol.LocalDeviceID) + } else { + protocolDevice.IndexID, _ = m.sdb.GetIndexID(folderCfg.ID, deviceCfg.DeviceID) + protocolDevice.MaxSequence, _ = m.sdb.GetDeviceSequence(folderCfg.ID, deviceCfg.DeviceID) } protocolFolder.Devices = append(protocolFolder.Devices, protocolDevice) @@ -2744,7 +2720,7 @@ func findByName(slice []*TreeEntry, name string) *TreeEntry { func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly bool) ([]*TreeEntry, error) { m.mut.RLock() - files, ok := m.folderFiles[folder] + _, ok := m.folderCfgs[folder] m.mut.RUnlock() if !ok { return nil, ErrFolderMissing @@ -2760,15 +2736,14 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly prefix = prefix + sep } - snap, err := files.Snapshot() - if err != nil { - return nil, err - } - defer snap.Release() - snap.WithPrefixedGlobalTruncated(prefix, func(f protocol.FileInfo) bool { + for f, err := range itererr.Zip(m.sdb.AllGlobalFilesPrefix(folder, prefix)) { + if err != nil { + return nil, err + } + // Don't include the prefix itself. - if f.IsInvalid() || f.IsDeleted() || strings.HasPrefix(prefix, f.Name) { - return true + if f.Invalid || f.Deleted || strings.HasPrefix(prefix, f.Name) { + continue } f.Name = strings.Replace(f.Name, prefix, "", 1) @@ -2777,7 +2752,7 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly base := filepath.Base(f.Name) if levels > -1 && strings.Count(f.Name, sep) > levels { - return true + continue } parent := root @@ -2785,28 +2760,22 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly for _, path := range strings.Split(dir, sep) { child := findByName(parent.Children, path) if child == nil { - err = fmt.Errorf("could not find child '%s' for path '%s' in parent '%s'", path, f.Name, parent.Name) - return false + return nil, fmt.Errorf("could not find child '%s' for path '%s' in parent '%s'", path, f.Name, parent.Name) } parent = child } } if dirsOnly && !f.IsDirectory() { - return true + continue } parent.Children = append(parent.Children, &TreeEntry{ Name: base, Type: f.Type.String(), ModTime: f.ModTime(), - Size: f.FileSize(), + Size: f.Size, }) - - return true - }) - if err != nil { - return nil, err } return root.Children, nil @@ -2860,46 +2829,42 @@ func (m *model) Availability(folder string, file protocol.FileInfo, block protoc m.mut.RLock() defer m.mut.RUnlock() - fs, ok := m.folderFiles[folder] - cfg := m.folderCfgs[folder] - + cfg, ok := m.folderCfgs[folder] if !ok { return nil, ErrFolderMissing } - snap, err := fs.Snapshot() - if err != nil { - return nil, err - } - defer snap.Release() - - return m.blockAvailabilityRLocked(cfg, snap, file, block), nil + return m.blockAvailabilityRLocked(cfg, file, block), nil } -func (m *model) blockAvailability(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo, block protocol.BlockInfo) []Availability { +func (m *model) blockAvailability(cfg config.FolderConfiguration, file protocol.FileInfo, block protocol.BlockInfo) []Availability { m.mut.RLock() defer m.mut.RUnlock() - return m.blockAvailabilityRLocked(cfg, snap, file, block) + return m.blockAvailabilityRLocked(cfg, file, block) } -func (m *model) blockAvailabilityRLocked(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo, block protocol.BlockInfo) []Availability { +func (m *model) blockAvailabilityRLocked(cfg config.FolderConfiguration, file protocol.FileInfo, block protocol.BlockInfo) []Availability { var candidates []Availability - candidates = append(candidates, m.fileAvailabilityRLocked(cfg, snap, file)...) + candidates = append(candidates, m.fileAvailabilityRLocked(cfg, file)...) candidates = append(candidates, m.blockAvailabilityFromTemporaryRLocked(cfg, file, block)...) return candidates } -func (m *model) fileAvailability(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo) []Availability { +func (m *model) fileAvailability(cfg config.FolderConfiguration, file protocol.FileInfo) []Availability { m.mut.RLock() defer m.mut.RUnlock() - return m.fileAvailabilityRLocked(cfg, snap, file) + return m.fileAvailabilityRLocked(cfg, file) } -func (m *model) fileAvailabilityRLocked(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo) []Availability { +func (m *model) fileAvailabilityRLocked(cfg config.FolderConfiguration, file protocol.FileInfo) []Availability { var availabilities []Availability - for _, device := range snap.Availability(file.Name) { + devs, err := m.sdb.GetGlobalAvailability(cfg.ID, file.Name) + if err != nil { + return nil + } + for _, device := range devs { if _, ok := m.remoteFolderStates[device]; !ok { continue } @@ -2936,15 +2901,14 @@ func (m *model) BringToFront(folder, file string) { } func (m *model) ResetFolder(folder string) error { - m.mut.RLock() - defer m.mut.RUnlock() + m.mut.Lock() + defer m.mut.Unlock() _, ok := m.folderRunners.Get(folder) if ok { return errors.New("folder must be paused when resetting") } l.Infof("Cleaning metadata for reset folder %q", folder) - db.DropFolder(m.db, folder) - return nil + return m.sdb.DropFolder(folder) } func (m *model) String() string { @@ -3058,7 +3022,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool { for deviceID, toCfg := range toDevices { fromCfg, ok := fromDevices[deviceID] if !ok { - sr := stats.NewDeviceStatisticsReference(m.db, deviceID) + sr := stats.NewDeviceStatisticsReference(db.NewTyped(m.sdb, "devicestats/"+deviceID.String())) m.mut.Lock() m.deviceStatRefs[deviceID] = sr m.mut.Unlock() @@ -3151,7 +3115,7 @@ func (m *model) setConnRequestLimitersLocked(cfg config.DeviceConfiguration) { func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.DeviceConfiguration, existingFolders map[string]config.FolderConfiguration, ignoredDevices deviceIDSet, removedFolders map[string]struct{}) { var removedPendingFolders []map[string]string - pendingFolders, err := m.db.PendingFolders() + pendingFolders, err := m.observed.PendingFolders() if err != nil { msg := "Could not iterate through pending folder entries for cleanup" l.Warnf("%v: %v", msg, err) @@ -3164,7 +3128,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device // folders as well, assuming the folder is no longer of interest // at all (but might become pending again). l.Debugf("Discarding pending removed folder %v from all devices", folderID) - if err := m.db.RemovePendingFolder(folderID); err != nil { + if err := m.observed.RemovePendingFolder(folderID); err != nil { msg := "Failed to remove pending folder entry" l.Warnf("%v (%v): %v", msg, folderID, err) m.evLogger.Log(events.Failure, msg) @@ -3191,7 +3155,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device } continue removeFolderForDevice: - if err := m.db.RemovePendingFolderForDevice(folderID, deviceID); err != nil { + if err := m.observed.RemovePendingFolderForDevice(folderID, deviceID); err != nil { msg := "Failed to remove pending folder-device entry" l.Warnf("%v (%v, %v): %v", msg, folderID, deviceID, err) m.evLogger.Log(events.Failure, msg) @@ -3210,7 +3174,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device } var removedPendingDevices []map[string]string - pendingDevices, err := m.db.PendingDevices() + pendingDevices, err := m.observed.PendingDevices() if err != nil { msg := "Could not iterate through pending device entries for cleanup" l.Warnf("%v: %v", msg, err) @@ -3228,7 +3192,7 @@ func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.Device } continue removeDevice: - if err := m.db.RemovePendingDevice(deviceID); err != nil { + if err := m.observed.RemovePendingDevice(deviceID); err != nil { msg := "Failed to remove pending device entry" l.Warnf("%v: %v", msg, err) m.evLogger.Log(events.Failure, msg) @@ -3265,20 +3229,20 @@ func (m *model) checkFolderRunningRLocked(folder string) error { // PendingDevices lists unknown devices that tried to connect. func (m *model) PendingDevices() (map[protocol.DeviceID]db.ObservedDevice, error) { - return m.db.PendingDevices() + return m.observed.PendingDevices() } // PendingFolders lists folders that we don't yet share with the offering devices. It // returns the entries grouped by folder and filters for a given device unless the // argument is specified as EmptyDeviceID. func (m *model) PendingFolders(device protocol.DeviceID) (map[string]db.PendingFolder, error) { - return m.db.PendingFoldersForDevice(device) + return m.observed.PendingFoldersForDevice(device) } // DismissPendingDevices removes the record of a specific pending device. func (m *model) DismissPendingDevice(device protocol.DeviceID) error { l.Debugf("Discarding pending device %v", device) - err := m.db.RemovePendingDevice(device) + err := m.observed.RemovePendingDevice(device) if err != nil { return err } @@ -3298,7 +3262,7 @@ func (m *model) DismissPendingFolder(device protocol.DeviceID, folder string) er var removedPendingFolders []map[string]string if device == protocol.EmptyDeviceID { l.Debugf("Discarding pending removed folder %s from all devices", folder) - err := m.db.RemovePendingFolder(folder) + err := m.observed.RemovePendingFolder(folder) if err != nil { return err } @@ -3307,7 +3271,7 @@ func (m *model) DismissPendingFolder(device protocol.DeviceID, folder string) er } } else { l.Debugf("Discarding pending folder %s from device %v", folder, device) - err := m.db.RemovePendingFolderForDevice(folder, device) + err := m.observed.RemovePendingFolderForDevice(folder, device) if err != nil { return err } @@ -3436,7 +3400,7 @@ type storedEncryptionToken struct { } func readEncryptionToken(cfg config.FolderConfiguration) ([]byte, error) { - fd, err := cfg.Filesystem(nil).Open(encryptionTokenPath(cfg)) + fd, err := cfg.Filesystem().Open(encryptionTokenPath(cfg)) if err != nil { return nil, err } @@ -3450,7 +3414,7 @@ func readEncryptionToken(cfg config.FolderConfiguration) ([]byte, error) { func writeEncryptionToken(token []byte, cfg config.FolderConfiguration) error { tokenName := encryptionTokenPath(cfg) - fd, err := cfg.Filesystem(nil).OpenFile(tokenName, fs.OptReadWrite|fs.OptCreate, 0o666) + fd, err := cfg.Filesystem().OpenFile(tokenName, fs.OptReadWrite|fs.OptCreate, 0o666) if err != nil { return err } diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 0c6274248..a1cd98f15 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -13,6 +13,7 @@ import ( "errors" "fmt" "io" + "iter" mrand "math/rand" "os" "path/filepath" @@ -24,10 +25,10 @@ import ( "testing" "time" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/db/backend" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/ignore" @@ -77,7 +78,7 @@ func addFolderDevicesToClusterConfig(cc *protocol.ClusterConfig, remote protocol func TestRequest(t *testing.T) { wrapper, fcfg, cancel := newDefaultCfgWrapper() - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() defer cancel() m := setupModel(t, wrapper) defer cleanupModel(m) @@ -165,7 +166,7 @@ func BenchmarkIndex_100(b *testing.B) { func benchmarkIndex(b *testing.B, nfiles int) { m, _, fcfg, wcfgCancel := setupModelWithConnection(b) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) files := genFiles(nfiles) must(b, m.Index(device1Conn, &protocol.Index{Folder: fcfg.ID, Files: files})) @@ -192,7 +193,7 @@ func BenchmarkIndexUpdate_10000_1(b *testing.B) { func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) { m, _, fcfg, wcfgCancel := setupModelWithConnection(b) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) files := genFiles(nfiles) ufiles := genFiles(nufiles) @@ -235,7 +236,7 @@ func BenchmarkRequestOut(b *testing.B) { func BenchmarkRequestInSingleFile(b *testing.B) { w, cancel := newConfigWrapper(defaultCfg) defer cancel() - ffs := w.FolderList()[0].Filesystem(nil) + ffs := w.FolderList()[0].Filesystem() m := setupModel(b, w) defer cleanupModel(m) @@ -1195,7 +1196,7 @@ func TestAutoAcceptPrefersLabel(t *testing.T) { func TestAutoAcceptFallsBackToID(t *testing.T) { // Prefers label, falls back to ID. m, cancel := newState(t, defaultAutoAcceptCfg) - ffs := defaultFolderConfig.Filesystem(nil) + ffs := defaultFolderConfig.Filesystem() id := srand.String(8) label := srand.String(8) if err := ffs.MkdirAll(label, 0o777); err != nil { @@ -1488,7 +1489,7 @@ func changeIgnores(t *testing.T, m *testModel, expected []string) { func TestIgnores(t *testing.T) { w, cancel := newConfigWrapper(defaultCfg) defer cancel() - ffs := w.FolderList()[0].Filesystem(nil) + ffs := w.FolderList()[0].Filesystem() m := setupModel(t, w) defer cleanupModel(m) @@ -1523,7 +1524,7 @@ func TestIgnores(t *testing.T) { ID: "fresh", Path: "XXX", FilesystemType: config.FilesystemTypeFake, } - ignores := ignore.New(fcfg.Filesystem(nil), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles)) + ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles)) m.mut.Lock() m.folderCfgs[fcfg.ID] = fcfg m.folderIgnores[fcfg.ID] = ignores @@ -1555,7 +1556,7 @@ func TestIgnores(t *testing.T) { func TestEmptyIgnores(t *testing.T) { w, cancel := newConfigWrapper(defaultCfg) defer cancel() - ffs := w.FolderList()[0].Filesystem(nil) + ffs := w.FolderList()[0].Filesystem() m := setupModel(t, w) defer cleanupModel(m) @@ -1628,12 +1629,11 @@ func TestROScanRecovery(t *testing.T) { defer cancel() m := newModel(t, cfg, myID, nil) - set := newFileSet(t, "default", m.db) - set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ + m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{ {Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}}, }) - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() // Remove marker to generate an error ffs.Remove(fcfg.MarkerName) @@ -1675,12 +1675,11 @@ func TestRWScanRecovery(t *testing.T) { defer cancel() m := newModel(t, cfg, myID, nil) - set := newFileSet(t, "default", m.db) - set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ + m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{ {Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}}, }) - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() // Generate error if err := ffs.Remove(config.DefaultMarkerName); err != nil { @@ -1706,8 +1705,9 @@ func TestRWScanRecovery(t *testing.T) { func TestGlobalDirectoryTree(t *testing.T) { m, conn, fcfg, wCancel := setupModelWithConnection(t) defer wCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) + var seq int64 b := func(isfile bool, path ...string) protocol.FileInfo { typ := protocol.FileInfoTypeDirectory var blocks []protocol.BlockInfo @@ -1716,12 +1716,14 @@ func TestGlobalDirectoryTree(t *testing.T) { typ = protocol.FileInfoTypeFile blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}} } + seq++ return protocol.FileInfo{ Name: filepath.Join(path...), Type: typ, ModifiedS: 0x666, Blocks: blocks, Size: 0xa, + Sequence: seq, } } f := func(name string) *TreeEntry { @@ -1813,13 +1815,13 @@ func TestGlobalDirectoryTree(t *testing.T) { result, _ := m.GlobalDirectoryTree("default", "", -1, false) if mm(result) != mm(expectedResult) { - t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(expectedResult)) + t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(expectedResult)) } result, _ = m.GlobalDirectoryTree("default", "another", -1, false) if mm(result) != mm(findByName(expectedResult, "another").Children) { - t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(findByName(expectedResult, "another").Children)) + t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(findByName(expectedResult, "another").Children)) } result, _ = m.GlobalDirectoryTree("default", "", 0, false) @@ -1831,7 +1833,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n============\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "", 1, false) @@ -1852,7 +1854,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "", -1, true) @@ -1882,7 +1884,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "", 1, true) @@ -1901,7 +1903,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "another", 0, false) @@ -1911,7 +1913,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "some/directory", 0, false) @@ -1920,7 +1922,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "some/directory", 1, false) @@ -1931,7 +1933,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "some/directory", 2, false) @@ -1944,7 +1946,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } result, _ = m.GlobalDirectoryTree("default", "another", -1, true) @@ -1957,7 +1959,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } // No prefix matching! @@ -1965,7 +1967,7 @@ func TestGlobalDirectoryTree(t *testing.T) { currentResult = []*TreeEntry{} if mm(result) != mm(currentResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) + t.Fatalf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } } @@ -2010,7 +2012,7 @@ func BenchmarkTree_100_10(b *testing.B) { func benchmarkTree(b *testing.B, n1, n2 int) { m, _, fcfg, wcfgCancel := setupModelWithConnection(b) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) m.ScanFolder(fcfg.ID) files := genDeepFiles(n1, n2) @@ -2027,7 +2029,7 @@ func benchmarkTree(b *testing.B, n1, n2 int) { func TestIssue3028(t *testing.T) { w, cancel := newConfigWrapper(defaultCfg) defer cancel() - ffs := w.FolderList()[0].Filesystem(nil) + ffs := w.FolderList()[0].Filesystem() m := setupModel(t, w) defer cleanupModel(m) @@ -2039,8 +2041,8 @@ func TestIssue3028(t *testing.T) { // Scan, and get a count of how many files are there now m.ScanFolderSubdirs("default", []string{"testrm", "testrm2"}) - locorigfiles := localSize(t, m, "default").Files - globorigfiles := globalSize(t, m, "default").Files + locorigfiles := mustV(m.LocalSize("default", protocol.LocalDeviceID)).Files + globorigfiles := mustV(m.GlobalSize("default")).Files // Delete @@ -2051,8 +2053,8 @@ func TestIssue3028(t *testing.T) { // deleted files increases by two m.ScanFolderSubdirs("default", []string{"testrm", "testrm2"}) - loc := localSize(t, m, "default") - glob := globalSize(t, m, "default") + loc := mustV(m.LocalSize("default", protocol.LocalDeviceID)) + glob := mustV(m.GlobalSize("default")) if loc.Files != locorigfiles-2 { t.Errorf("Incorrect local accounting; got %d current files, expected %d", loc.Files, locorigfiles-2) @@ -2127,24 +2129,22 @@ func TestIssue4357(t *testing.T) { func TestIndexesForUnknownDevicesDropped(t *testing.T) { m := newModel(t, defaultCfgWrapper, myID, nil) - files := newFileSet(t, "default", m.db) - files.Drop(device1) - files.Update(device1, genFiles(1)) - files.Drop(device2) - files.Update(device2, genFiles(1)) + m.sdb.DropAllFiles("default", device1) + m.sdb.Update("default", device1, genFiles(1)) + m.sdb.DropAllFiles("default", device2) + m.sdb.Update("default", device2, genFiles(1)) - if len(files.ListDevices()) != 2 { + if devs, err := m.sdb.ListDevicesForFolder("default"); err != nil || len(devs) != 2 { + t.Log(devs, err) t.Error("expected two devices") } m.newFolder(defaultFolderConfig, false) defer cleanupModel(m) - // Remote sequence is cached, hence need to recreated. - files = newFileSet(t, "default", m.db) - - if l := len(files.ListDevices()); l != 1 { - t.Errorf("Expected one device got %v", l) + if devs, err := m.sdb.ListDevicesForFolder("default"); err != nil || len(devs) != 1 { + t.Log(devs, err) + t.Error("expected one device") } } @@ -2270,7 +2270,7 @@ func TestIssue3829(t *testing.T) { func TestIssue4573(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() - testFs := fcfg.Filesystem(nil) + testFs := fcfg.Filesystem() defer os.RemoveAll(testFs.URI()) must(t, testFs.MkdirAll("inaccessible", 0o755)) @@ -2300,7 +2300,7 @@ func TestIssue4573(t *testing.T) { func TestInternalScan(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() - testFs := fcfg.Filesystem(nil) + testFs := fcfg.Filesystem() defer os.RemoveAll(testFs.URI()) testCases := map[string]func(protocol.FileInfo) bool{ @@ -2372,12 +2372,11 @@ func TestCustomMarkerName(t *testing.T) { }) defer cancel() - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() m := newModel(t, cfg, myID, nil) - set := newFileSet(t, "default", m.db) - set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ + m.sdb.Update("default", protocol.LocalDeviceID, []protocol.FileInfo{ {Name: "dummyfile"}, }) @@ -2401,7 +2400,7 @@ func TestCustomMarkerName(t *testing.T) { func TestRemoveDirWithContent(t *testing.T) { m, conn, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, tfs.URI()) tfs.MkdirAll("dirwith", 0o755) @@ -2463,7 +2462,7 @@ func TestIssue4475(t *testing.T) { m, conn, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() defer cleanupModel(m) - testFs := fcfg.Filesystem(nil) + testFs := fcfg.Filesystem() // Scenario: Dir is deleted locally and before syncing/index exchange // happens, a file is create in that dir on the remote. @@ -2525,7 +2524,7 @@ func TestVersionRestore(t *testing.T) { fcfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", config.FilesystemTypeFake, srand.String(32)) fcfg.Versioning.Type = "simple" fcfg.FSWatcherEnabled = false - filesystem := fcfg.Filesystem(nil) + filesystem := fcfg.Filesystem() rawConfig := config.Configuration{ Version: config.CurrentVersion, @@ -2759,7 +2758,7 @@ func TestIssue4094(t *testing.T) { t.Fatalf("failed setting ignores: %v", err) } - if _, err := fcfg.Filesystem(nil).Lstat(".stignore"); err != nil { + if _, err := fcfg.Filesystem().Lstat(".stignore"); err != nil { t.Fatalf("failed stating .stignore: %v", err) } } @@ -2788,7 +2787,7 @@ func TestIssue4903(t *testing.T) { t.Fatalf("expected path missing error, got: %v, debug: %s", err, fcfg.CheckPath()) } - if _, err := fcfg.Filesystem(nil).Lstat("."); !fs.IsNotExist(err) { + if _, err := fcfg.Filesystem().Lstat("."); !fs.IsNotExist(err) { t.Fatalf("Expected missing path error, got: %v", err) } } @@ -2798,7 +2797,7 @@ func TestIssue5002(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() fd, err := ffs.Create("foo") must(t, err) @@ -2827,7 +2826,7 @@ func TestIssue5002(t *testing.T) { func TestParentOfUnignored(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() must(t, ffs.Mkdir("bar", 0o755)) must(t, ffs.Mkdir("baz", 0o755)) @@ -2906,7 +2905,7 @@ func TestFolderRestartZombies(t *testing.T) { func TestRequestLimit(t *testing.T) { wrapper, fcfg, cancel := newDefaultCfgWrapper() - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() file := "tmpfile" fd, err := ffs.Create(file) @@ -2966,7 +2965,7 @@ func TestConnCloseOnRestart(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() m := setupModel(t, w) - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) br := &testutil.BlockingRW{} nw := &testutil.NoopRW{} @@ -3009,7 +3008,7 @@ func TestModTimeWindow(t *testing.T) { defer wCancel() tfs := modtimeTruncatingFS{ trunc: 0, - Filesystem: fcfg.Filesystem(nil), + Filesystem: fcfg.Filesystem(), } // fcfg.RawModTimeWindowS = 2 setFolder(t, w, fcfg) @@ -3069,7 +3068,7 @@ func TestModTimeWindow(t *testing.T) { func TestDevicePause(t *testing.T) { m, _, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) sub := m.evLogger.Subscribe(events.DevicePaused) defer sub.Unsubscribe() @@ -3099,7 +3098,7 @@ func TestDevicePause(t *testing.T) { func TestDeviceWasSeen(t *testing.T) { m, _, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) m.deviceWasSeen(device1) @@ -3194,7 +3193,7 @@ func TestRenameSequenceOrder(t *testing.T) { numFiles := 20 - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() for i := 0; i < numFiles; i++ { v := fmt.Sprintf("%d", i) writeFile(t, ffs, v, []byte(v)) @@ -3202,14 +3201,7 @@ func TestRenameSequenceOrder(t *testing.T) { m.ScanFolders() - count := 0 - snap := dbSnapshot(t, m, "default") - snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - + count := countIterator[protocol.FileInfo](t)(m.LocalFiles("default", protocol.LocalDeviceID)) if count != numFiles { t.Errorf("Unexpected count: %d != %d", count, numFiles) } @@ -3229,14 +3221,11 @@ func TestRenameSequenceOrder(t *testing.T) { // Scan m.ScanFolders() - // Verify sequence of a appearing is followed by c disappearing. - snap = dbSnapshot(t, m, "default") - defer snap.Release() - var firstExpectedSequence int64 var secondExpectedSequence int64 failed := false - snap.WithHaveSequence(0, func(i protocol.FileInfo) bool { + it, errFn := m.LocalFilesSequenced("default", protocol.LocalDeviceID, 0) + for i := range it { t.Log(i) if i.FileName() == "17" { firstExpectedSequence = i.SequenceNo() + 1 @@ -3250,8 +3239,10 @@ func TestRenameSequenceOrder(t *testing.T) { if i.FileName() == "16" { failed = i.SequenceNo() != secondExpectedSequence || failed } - return true - }) + } + if err := errFn(); err != nil { + t.Fatal(err) + } if failed { t.Fail() } @@ -3263,19 +3254,12 @@ func TestRenameSameFile(t *testing.T) { m := setupModel(t, wcfg) defer cleanupModel(m) - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() writeFile(t, ffs, "file", []byte("file")) m.ScanFolders() - count := 0 - snap := dbSnapshot(t, m, "default") - snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool { - count++ - return true - }) - snap.Release() - + count := countIterator[protocol.FileInfo](t)(m.LocalFiles("default", protocol.LocalDeviceID)) if count != 1 { t.Errorf("Unexpected count: %d != %d", count, 1) } @@ -3288,12 +3272,10 @@ func TestRenameSameFile(t *testing.T) { m.ScanFolders() - snap = dbSnapshot(t, m, "default") - defer snap.Release() - prevSeq := int64(0) seen := false - snap.WithHaveSequence(0, func(i protocol.FileInfo) bool { + it, errFn := m.LocalFilesSequenced("default", protocol.LocalDeviceID, 0) + for i := range it { if i.SequenceNo() <= prevSeq { t.Fatalf("non-increasing sequences: %d <= %d", i.SequenceNo(), prevSeq) } @@ -3304,84 +3286,9 @@ func TestRenameSameFile(t *testing.T) { seen = true } prevSeq = i.SequenceNo() - return true - }) -} - -func TestRenameEmptyFile(t *testing.T) { - wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper() - defer wcfgCancel() - m := setupModel(t, wcfg) - defer cleanupModel(m) - - ffs := fcfg.Filesystem(nil) - - writeFile(t, ffs, "file", []byte("data")) - writeFile(t, ffs, "empty", nil) - - m.ScanFolders() - - snap := dbSnapshot(t, m, "default") - defer snap.Release() - empty, eok := snap.Get(protocol.LocalDeviceID, "empty") - if !eok { - t.Fatal("failed to find empty file") } - file, fok := snap.Get(protocol.LocalDeviceID, "file") - if !fok { - t.Fatal("failed to find non-empty file") - } - - count := 0 - snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileInfo) bool { - count++ - return true - }) - - if count != 0 { - t.Fatalf("Found %d entries for empty file, expected 0", count) - } - - count = 0 - snap.WithBlocksHash(file.BlocksHash, func(_ protocol.FileInfo) bool { - count++ - return true - }) - - if count != 1 { - t.Fatalf("Found %d entries for non-empty file, expected 1", count) - } - - must(t, ffs.Rename("file", "new-file")) - must(t, ffs.Rename("empty", "new-empty")) - - // Scan - m.ScanFolders() - - snap = dbSnapshot(t, m, "default") - defer snap.Release() - - count = 0 - snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileInfo) bool { - count++ - return true - }) - - if count != 0 { - t.Fatalf("Found %d entries for empty file, expected 0", count) - } - - count = 0 - snap.WithBlocksHash(file.BlocksHash, func(i protocol.FileInfo) bool { - count++ - if i.FileName() != "new-file" { - t.Fatalf("unexpected file name %s, expected new-file", i.FileName()) - } - return true - }) - - if count != 1 { - t.Fatalf("Found %d entries for non-empty file, expected 1", count) + if err := errFn(); err != nil { + t.Fatal(err) } } @@ -3391,7 +3298,7 @@ func TestBlockListMap(t *testing.T) { m := setupModel(t, wcfg) defer cleanupModel(m) - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() writeFile(t, ffs, "one", []byte("content")) writeFile(t, ffs, "two", []byte("content")) writeFile(t, ffs, "three", []byte("content")) @@ -3400,23 +3307,25 @@ func TestBlockListMap(t *testing.T) { m.ScanFolders() - snap := dbSnapshot(t, m, "default") - defer snap.Release() - fi, ok := snap.Get(protocol.LocalDeviceID, "one") + fi, ok, err := m.model.CurrentFolderFile("default", "one") + if err != nil { + t.Fatal(err) + } if !ok { t.Error("failed to find existing file") } var paths []string - snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileInfo) bool { - paths = append(paths, fi.FileName()) - return true - }) - snap.Release() + for fi, err := range itererr.Zip(m.model.AllForBlocksHash(fcfg.ID, fi.BlocksHash)) { + if err != nil { + t.Fatal(err) + } + paths = append(paths, fi.Name) + } expected := []string{"one", "two", "three", "four", "five"} if !equalStringsInAnyOrder(paths, expected) { - t.Errorf("expected %q got %q", expected, paths) + t.Fatalf("expected %q got %q", expected, paths) } // Fudge the files around @@ -3437,19 +3346,18 @@ func TestBlockListMap(t *testing.T) { m.ScanFolders() // Check we're left with 2 of the 5 - snap = dbSnapshot(t, m, "default") - defer snap.Release() paths = paths[:0] - snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileInfo) bool { - paths = append(paths, fi.FileName()) - return true - }) - snap.Release() + for fi, err := range itererr.Zip(m.model.AllForBlocksHash(fcfg.ID, fi.BlocksHash)) { + if err != nil { + t.Fatal(err) + } + paths = append(paths, fi.Name) + } expected = []string{"new-three", "five"} if !equalStringsInAnyOrder(paths, expected) { - t.Errorf("expected %q got %q", expected, paths) + t.Fatalf("expected %q got %q", expected, paths) } } @@ -3459,16 +3367,17 @@ func TestScanRenameCaseOnly(t *testing.T) { m := setupModel(t, wcfg) defer cleanupModel(m) - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() name := "foo" writeFile(t, ffs, name, []byte("contents")) m.ScanFolders() - snap := dbSnapshot(t, m, fcfg.ID) - defer snap.Release() found := false - snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool { + for i, err := range itererr.Zip(m.LocalFiles(fcfg.ID, protocol.LocalDeviceID)) { + if err != nil { + t.Fatal(err) + } if found { t.Fatal("got more than one file") } @@ -3476,21 +3385,20 @@ func TestScanRenameCaseOnly(t *testing.T) { t.Fatalf("got file %v, expected %v", i.FileName(), name) } found = true - return true - }) - snap.Release() + } upper := strings.ToUpper(name) must(t, ffs.Rename(name, upper)) m.ScanFolders() - snap = dbSnapshot(t, m, fcfg.ID) - defer snap.Release() found = false - snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileInfo) bool { + for i, err := range itererr.Zip(m.LocalFiles(fcfg.ID, protocol.LocalDeviceID)) { + if err != nil { + t.Fatal(err) + } if i.FileName() == name { if i.IsDeleted() { - return true + continue } t.Fatal("renamed file not deleted") } @@ -3501,8 +3409,7 @@ func TestScanRenameCaseOnly(t *testing.T) { t.Fatal("got more than the expected files") } found = true - return true - }) + } } func TestClusterConfigOnFolderAdd(t *testing.T) { @@ -3577,7 +3484,7 @@ func TestAddFolderCompletion(t *testing.T) { func TestScanDeletedROChangedOnSR(t *testing.T) { m, conn, fcfg, wCancel := setupModelWithConnection(t) - ffs := fcfg.Filesystem(nil) + ffs := fcfg.Filesystem() defer wCancel() defer cleanupModelAndRemoveDir(m, ffs.URI()) fcfg.Type = config.FolderTypeReceiveOnly @@ -3599,7 +3506,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) { must(t, ffs.Remove(name)) m.ScanFolders() - if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 1 { + if mustV(m.ReceiveOnlySize(fcfg.ID)).Deleted != 1 { t.Fatal("expected one receive only changed deleted item") } @@ -3607,10 +3514,10 @@ func TestScanDeletedROChangedOnSR(t *testing.T) { setFolder(t, m.cfg, fcfg) m.ScanFolders() - if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 0 { + if mustV(m.ReceiveOnlySize(fcfg.ID)).Deleted != 0 { t.Fatal("expected no receive only changed deleted item") } - if localSize(t, m, fcfg.ID).Deleted != 1 { + if mustV(m.LocalSize(fcfg.ID, protocol.LocalDeviceID)).Deleted != 1 { t.Fatal("expected one local deleted item") } } @@ -3682,7 +3589,7 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec func TestIssue6961(t *testing.T) { wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper() defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() waiter, err := wcfg.Modify(func(cfg *config.Configuration) { cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2")) fcfg.Type = config.FolderTypeReceiveOnly @@ -3693,11 +3600,6 @@ func TestIssue6961(t *testing.T) { waiter.Wait() // Always recalc/repair when opening a fileset. m := newModel(t, wcfg, myID, nil) - m.db.Close() - m.db, err = db.NewLowlevel(backend.OpenMemory(), m.evLogger, db.WithRecheckInterval(time.Millisecond)) - if err != nil { - t.Fatal(err) - } m.ServeBackground() defer cleanupModelAndRemoveDir(m, tfs.URI()) conn1 := addFakeConn(m, device1, fcfg.ID) @@ -3752,11 +3654,9 @@ func TestIssue6961(t *testing.T) { func TestCompletionEmptyGlobal(t *testing.T) { m, conn, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID.Short()), Sequence: 1}} - m.mut.Lock() - m.folderFiles[fcfg.ID].Update(protocol.LocalDeviceID, files) - m.mut.Unlock() + m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files) files[0].Deleted = true files[0].Version = files[0].Version.Update(device1.Short()) must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: fcfg.ID, Files: files})) @@ -3953,7 +3853,7 @@ func TestCCFolderNotRunning(t *testing.T) { // Create the folder, but don't start it. w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() m := newModel(t, w, myID, nil) defer cleanupModelAndRemoveDir(m, tfs.URI()) @@ -3990,7 +3890,7 @@ func TestPendingFolder(t *testing.T) { Time: time.Now().Truncate(time.Second), Label: pfolder, } - if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device2); err != nil { + if err := m.observed.AddOrUpdatePendingFolder(pfolder, of, device2); err != nil { t.Fatal(err) } deviceFolders, err := m.PendingFolders(protocol.EmptyDeviceID) @@ -4009,7 +3909,7 @@ func TestPendingFolder(t *testing.T) { t.Fatal(err) } setDevice(t, w, config.DeviceConfiguration{DeviceID: device3}) - if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device3); err != nil { + if err := m.observed.AddOrUpdatePendingFolder(pfolder, of, device3); err != nil { t.Fatal(err) } deviceFolders, err = m.PendingFolders(device2) @@ -4060,7 +3960,7 @@ func TestDeletedNotLocallyChangedReceiveEncrypted(t *testing.T) { func deletedNotLocallyChanged(t *testing.T, ft config.FolderType) { w, fcfg, wCancel := newDefaultCfgWrapper() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() fcfg.Type = ft setFolder(t, w, fcfg) defer wCancel() @@ -4141,3 +4041,17 @@ type modtimeTruncatingFileInfo struct { func (fi modtimeTruncatingFileInfo) ModTime() time.Time { return fi.FileInfo.ModTime().Truncate(fi.trunc) } + +func countIterator[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) int { + return func(it iter.Seq[T], errFn func() error) int { + t.Helper() + count := 0 + for range it { + count++ + } + if err := errFn(); err != nil { + t.Fatal(err) + } + return count + } +} diff --git a/lib/model/progressemitter.go b/lib/model/progressemitter.go index 4a291a519..6242c33b8 100644 --- a/lib/model/progressemitter.go +++ b/lib/model/progressemitter.go @@ -76,7 +76,6 @@ func (t *ProgressEmitter) Serve(ctx context.Context) error { return nil case <-t.timer.C: t.mut.Lock() - l.Debugln("progress emitter: timer - looking after", len(t.registry)) newLastUpdated := lastUpdate newCount = t.lenRegistryLocked() @@ -94,8 +93,6 @@ func (t *ProgressEmitter) Serve(ctx context.Context) error { lastCount = newCount t.sendDownloadProgressEventLocked() progressUpdates = t.computeProgressUpdates() - } else { - l.Debugln("progress emitter: nothing new") } if newCount != 0 { @@ -247,7 +244,6 @@ func (t *ProgressEmitter) Register(s *sharedPullerState) { t.mut.Lock() defer t.mut.Unlock() if t.disabled { - l.Debugln("progress emitter: disabled, skip registering") return } l.Debugln("progress emitter: registering", s.folder, s.file.Name) @@ -266,7 +262,6 @@ func (t *ProgressEmitter) Deregister(s *sharedPullerState) { defer t.mut.Unlock() if t.disabled { - l.Debugln("progress emitter: disabled, skip deregistering") return } diff --git a/lib/model/queue.go b/lib/model/queue.go index 16ac83c94..7ca129c26 100644 --- a/lib/model/queue.go +++ b/lib/model/queue.go @@ -7,10 +7,8 @@ package model import ( - "sort" "time" - "github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/sync" ) @@ -127,13 +125,6 @@ func (q *jobQueue) Jobs(page, perpage int) ([]string, []string, int) { return progress, queued, (page - 1) * perpage } -func (q *jobQueue) Shuffle() { - q.mut.Lock() - defer q.mut.Unlock() - - rand.Shuffle(q.queued) -} - func (q *jobQueue) Reset() { q.mut.Lock() defer q.mut.Unlock() @@ -152,45 +143,3 @@ func (q *jobQueue) lenProgress() int { defer q.mut.Unlock() return len(q.progress) } - -func (q *jobQueue) SortSmallestFirst() { - q.mut.Lock() - defer q.mut.Unlock() - - sort.Sort(smallestFirst(q.queued)) -} - -func (q *jobQueue) SortLargestFirst() { - q.mut.Lock() - defer q.mut.Unlock() - - sort.Sort(sort.Reverse(smallestFirst(q.queued))) -} - -func (q *jobQueue) SortOldestFirst() { - q.mut.Lock() - defer q.mut.Unlock() - - sort.Sort(oldestFirst(q.queued)) -} - -func (q *jobQueue) SortNewestFirst() { - q.mut.Lock() - defer q.mut.Unlock() - - sort.Sort(sort.Reverse(oldestFirst(q.queued))) -} - -// The usual sort.Interface boilerplate - -type smallestFirst []jobQueueEntry - -func (q smallestFirst) Len() int { return len(q) } -func (q smallestFirst) Less(a, b int) bool { return q[a].size < q[b].size } -func (q smallestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] } - -type oldestFirst []jobQueueEntry - -func (q oldestFirst) Len() int { return len(q) } -func (q oldestFirst) Less(a, b int) bool { return q[a].modified < q[b].modified } -func (q oldestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] } diff --git a/lib/model/queue_test.go b/lib/model/queue_test.go index a49f2c70f..d8467ff4d 100644 --- a/lib/model/queue_test.go +++ b/lib/model/queue_test.go @@ -163,95 +163,6 @@ func TestBringToFront(t *testing.T) { } } -func TestShuffle(t *testing.T) { - q := newJobQueue() - q.Push("f1", 0, time.Time{}) - q.Push("f2", 0, time.Time{}) - q.Push("f3", 0, time.Time{}) - q.Push("f4", 0, time.Time{}) - - // This test will fail once in eight million times (1 / (4!)^5) :) - for i := 0; i < 5; i++ { - q.Shuffle() - _, queued, _ := q.Jobs(1, 100) - if l := len(queued); l != 4 { - t.Fatalf("Weird length %d returned from jobs(1, 100)", l) - } - - t.Logf("%v", queued) - if _, equal := messagediff.PrettyDiff([]string{"f1", "f2", "f3", "f4"}, queued); !equal { - // The queue was shuffled - return - } - } - - t.Error("Queue was not shuffled after five attempts.") -} - -func TestSortBySize(t *testing.T) { - q := newJobQueue() - q.Push("f1", 20, time.Time{}) - q.Push("f2", 40, time.Time{}) - q.Push("f3", 30, time.Time{}) - q.Push("f4", 10, time.Time{}) - - q.SortSmallestFirst() - - _, actual, _ := q.Jobs(1, 100) - if l := len(actual); l != 4 { - t.Fatalf("Weird length %d returned from jobs(1, 100)", l) - } - expected := []string{"f4", "f1", "f3", "f2"} - - if diff, equal := messagediff.PrettyDiff(expected, actual); !equal { - t.Errorf("SortSmallestFirst() diff:\n%s", diff) - } - - q.SortLargestFirst() - - _, actual, _ = q.Jobs(1, 100) - if l := len(actual); l != 4 { - t.Fatalf("Weird length %d returned from jobs(1, 100)", l) - } - expected = []string{"f2", "f3", "f1", "f4"} - - if diff, equal := messagediff.PrettyDiff(expected, actual); !equal { - t.Errorf("SortLargestFirst() diff:\n%s", diff) - } -} - -func TestSortByAge(t *testing.T) { - q := newJobQueue() - q.Push("f1", 0, time.Unix(20, 0)) - q.Push("f2", 0, time.Unix(40, 0)) - q.Push("f3", 0, time.Unix(30, 0)) - q.Push("f4", 0, time.Unix(10, 0)) - - q.SortOldestFirst() - - _, actual, _ := q.Jobs(1, 100) - if l := len(actual); l != 4 { - t.Fatalf("Weird length %d returned from jobs(1, 100)", l) - } - expected := []string{"f4", "f1", "f3", "f2"} - - if diff, equal := messagediff.PrettyDiff(expected, actual); !equal { - t.Errorf("SortOldestFirst() diff:\n%s", diff) - } - - q.SortNewestFirst() - - _, actual, _ = q.Jobs(1, 100) - if l := len(actual); l != 4 { - t.Fatalf("Weird length %d returned from jobs(1, 100)", l) - } - expected = []string{"f2", "f3", "f1", "f4"} - - if diff, equal := messagediff.PrettyDiff(expected, actual); !equal { - t.Errorf("SortNewestFirst() diff:\n%s", diff) - } -} - func BenchmarkJobQueueBump(b *testing.B) { files := genFiles(10000) diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go index 991656ed3..3e437ebfb 100644 --- a/lib/model/requests_test.go +++ b/lib/model/requests_test.go @@ -32,7 +32,7 @@ func TestRequestSimple(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, tfs.URI()) // We listen for incoming index updates and trigger when we see one for @@ -80,7 +80,7 @@ func TestSymlinkTraversalRead(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) // We listen for incoming index updates and trigger when we see one for // the expected test file. @@ -123,7 +123,7 @@ func TestSymlinkTraversalWrite(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) // We listen for incoming index updates and trigger when we see one for // the expected names. @@ -182,7 +182,7 @@ func TestRequestCreateTmpSymlink(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) // We listen for incoming index updates and trigger when we see one for // the expected test file. @@ -229,7 +229,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) { w, wCancel := newConfigWrapper(defaultCfgWrapper.RawCopy()) defer wCancel() fcfg := w.FolderList()[0] - fss := fcfg.Filesystem(nil) + fss := fcfg.Filesystem() fcfg.Type = ft setFolder(t, w, fcfg) m := setupModel(t, w) @@ -358,7 +358,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) { func TestIssue4841(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) received := make(chan []protocol.FileInfo) fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error { @@ -407,7 +407,7 @@ func TestIssue4841(t *testing.T) { func TestRescanIfHaveInvalidContent(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, tfs.URI()) payload := []byte("hello") @@ -465,9 +465,11 @@ func TestRescanIfHaveInvalidContent(t *testing.T) { } func TestParentDeletion(t *testing.T) { + t.Skip("flaky") + m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - testFs := fcfg.Filesystem(nil) + testFs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, testFs.URI()) parent := "foo" @@ -546,7 +548,7 @@ func TestRequestSymlinkWindows(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) + defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) received := make(chan []protocol.FileInfo) fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error { @@ -623,7 +625,7 @@ func TestRequestRemoteRenameChanged(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModel(m) received := make(chan []protocol.FileInfo) @@ -756,7 +758,7 @@ func TestRequestRemoteRenameChanged(t *testing.T) { func TestRequestRemoteRenameConflict(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModel(m) recv := make(chan int) @@ -846,7 +848,7 @@ func TestRequestRemoteRenameConflict(t *testing.T) { func TestRequestDeleteChanged(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, tfs.URI()) done := make(chan struct{}) @@ -960,7 +962,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() m := setupModel(t, w) - fss := fcfg.Filesystem(nil) + fss := fcfg.Filesystem() defer cleanupModel(m) folderIgnoresAlwaysReload(t, m, fcfg) @@ -1054,7 +1056,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) { func TestRequestLastFileProgress(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, tfs.URI()) done := make(chan struct{}) @@ -1089,7 +1091,7 @@ func TestRequestIndexSenderPause(t *testing.T) { m, fc, fcfg, wcfgCancel := setupModelWithConnection(t) defer wcfgCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, tfs.URI()) indexChan := make(chan []protocol.FileInfo) @@ -1202,7 +1204,7 @@ func TestRequestIndexSenderPause(t *testing.T) { func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() dir1 := "foo" dir2 := "bar" @@ -1217,7 +1219,7 @@ func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) { // Add connection (sends incoming cluster config) before starting the new model m = &testModel{ - model: NewModel(m.cfg, m.id, m.db, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model), + model: NewModel(m.cfg, m.id, m.sdb, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model), evCancel: m.evCancel, stopped: make(chan struct{}), } @@ -1269,7 +1271,7 @@ func TestRequestReceiveEncrypted(t *testing.T) { w, fcfg, wCancel := newDefaultCfgWrapper() defer wCancel() - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() fcfg.Type = config.FolderTypeReceiveEncrypted setFolder(t, w, fcfg) @@ -1281,10 +1283,7 @@ func TestRequestReceiveEncrypted(t *testing.T) { files := genFiles(2) files[1].LocalFlags = protocol.FlagLocalReceiveOnly - m.mut.RLock() - fset := m.folderFiles[fcfg.ID] - m.mut.RUnlock() - fset.Update(protocol.LocalDeviceID, files) + m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files) indexChan := make(chan []protocol.FileInfo, 10) done := make(chan struct{}) @@ -1376,7 +1375,7 @@ func TestRequestGlobalInvalidToValid(t *testing.T) { must(t, err) waiter.Wait() conn := addFakeConn(m, device2, fcfg.ID) - tfs := fcfg.Filesystem(nil) + tfs := fcfg.Filesystem() defer cleanupModelAndRemoveDir(m, tfs.URI()) indexChan := make(chan []protocol.FileInfo, 1) @@ -1402,7 +1401,7 @@ func TestRequestGlobalInvalidToValid(t *testing.T) { file.SetIgnored() m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: fcfg.ID, Files: []protocol.FileInfo{prepareFileInfoForIndex(file)}}) - // Wait for the ignored file to be received and possible pulled + // Wait for the ignored file to be received and possibly pulled timeout := time.After(10 * time.Second) globalUpdated := false for { @@ -1422,13 +1421,9 @@ func TestRequestGlobalInvalidToValid(t *testing.T) { } globalUpdated = true } - snap, err := m.DBSnapshot(fcfg.ID) - if err != nil { + if s, err := m.NeedSize(fcfg.ID, protocol.LocalDeviceID); err != nil { t.Fatal(err) - } - need := snap.NeedSize(protocol.LocalDeviceID) - snap.Release() - if need.Files == 0 { + } else if s.Files == 0 { break } } diff --git a/lib/model/testos_test.go b/lib/model/testos_test.go index 2c56265b6..b7110ef75 100644 --- a/lib/model/testos_test.go +++ b/lib/model/testos_test.go @@ -6,10 +6,6 @@ package model -import ( - "github.com/syncthing/syncthing/lib/fs" -) - // fatal is the required common interface between *testing.B and *testing.T type fatal interface { Fatal(...interface{}) @@ -23,9 +19,9 @@ func must(f fatal, err error) { } } -func mustRemove(f fatal, err error) { - f.Helper() - if err != nil && !fs.IsNotExist(err) { - f.Fatal(err) +func mustV[T any](v T, err error) T { + if err != nil { + panic(err) } + return v } diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go index 1e203f9fd..7949bc617 100644 --- a/lib/model/testutils_test.go +++ b/lib/model/testutils_test.go @@ -12,9 +12,8 @@ import ( "testing" "time" + "github.com/syncthing/syncthing/internal/db/sqlite" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/db/backend" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/ignore" @@ -149,11 +148,14 @@ type testModel struct { func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, protectedFiles []string) *testModel { t.Helper() evLogger := events.NewLogger() - ldb, err := db.NewLowlevel(backend.OpenMemory(), evLogger) + mdb, err := sqlite.OpenTemp() if err != nil { t.Fatal(err) } - m := NewModel(cfg, id, ldb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model) + t.Cleanup(func() { + mdb.Close() + }) + m := NewModel(cfg, id, mdb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model) ctx, cancel := context.WithCancel(context.Background()) go evLogger.Serve(ctx) return &testModel{ @@ -174,12 +176,6 @@ func (m *testModel) ServeBackground() { <-m.started } -func (m *testModel) testAvailability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability { - av, err := m.model.Availability(folder, file, block) - must(m.t, err) - return av -} - func (m *testModel) testCurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) { f, ok, err := m.model.CurrentFolderFile(folder, file) must(m.t, err) @@ -198,7 +194,7 @@ func cleanupModel(m *testModel) { <-m.stopped } m.evCancel() - m.db.Close() + m.sdb.Close() os.Remove(m.cfg.ConfigPath()) } @@ -240,52 +236,6 @@ func (*alwaysChanged) Changed() bool { return true } -func localSize(t *testing.T, m Model, folder string) db.Counts { - t.Helper() - snap := dbSnapshot(t, m, folder) - defer snap.Release() - return snap.LocalSize() -} - -func globalSize(t *testing.T, m Model, folder string) db.Counts { - t.Helper() - snap := dbSnapshot(t, m, folder) - defer snap.Release() - return snap.GlobalSize() -} - -func receiveOnlyChangedSize(t *testing.T, m Model, folder string) db.Counts { - t.Helper() - snap := dbSnapshot(t, m, folder) - defer snap.Release() - return snap.ReceiveOnlyChangedSize() -} - -func needSizeLocal(t *testing.T, m Model, folder string) db.Counts { - t.Helper() - snap := dbSnapshot(t, m, folder) - defer snap.Release() - return snap.NeedSize(protocol.LocalDeviceID) -} - -func dbSnapshot(t *testing.T, m Model, folder string) *db.Snapshot { - t.Helper() - snap, err := m.DBSnapshot(folder) - if err != nil { - t.Fatal(err) - } - return snap -} - -func fsetSnapshot(t *testing.T, fset *db.FileSet) *db.Snapshot { - t.Helper() - snap, err := fset.Snapshot() - if err != nil { - t.Fatal(err) - } - return snap -} - // Reach in and update the ignore matcher to one that always does // reloads when asked to, instead of checking file mtimes. This is // because we will be changing the files on disk often enough that the @@ -293,10 +243,9 @@ func fsetSnapshot(t *testing.T, fset *db.FileSet) *db.Snapshot { func folderIgnoresAlwaysReload(t testing.TB, m *testModel, fcfg config.FolderConfiguration) { t.Helper() m.removeFolder(fcfg) - fset := newFileSet(t, fcfg.ID, m.db) - ignores := ignore.New(fcfg.Filesystem(nil), ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged())) + ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged())) m.mut.Lock() - m.addAndStartFolderLockedWithIgnores(fcfg, fset, ignores) + m.addAndStartFolderLockedWithIgnores(fcfg, ignores) m.mut.Unlock() } @@ -319,12 +268,11 @@ func basicClusterConfig(local, remote protocol.DeviceID, folders ...string) *pro } func localIndexUpdate(m *testModel, folder string, fs []protocol.FileInfo) { - m.mut.RLock() - fset := m.folderFiles[folder] - m.mut.RUnlock() - - fset.Update(protocol.LocalDeviceID, fs) - seq := fset.Sequence(protocol.LocalDeviceID) + m.sdb.Update(folder, protocol.LocalDeviceID, fs) + seq, err := m.sdb.GetDeviceSequence(folder, protocol.LocalDeviceID) + if err != nil { + panic(err) + } filenames := make([]string, len(fs)) for i, file := range fs { filenames[i] = file.Name @@ -345,15 +293,6 @@ func newDeviceConfiguration(defaultCfg config.DeviceConfiguration, id protocol.D return cfg } -func newFileSet(t testing.TB, folder string, ldb *db.Lowlevel) *db.FileSet { - t.Helper() - fset, err := db.NewFileSet(folder, ldb) - if err != nil { - t.Fatal(err) - } - return fset -} - func replace(t testing.TB, w config.Wrapper, to config.Configuration) { t.Helper() waiter, err := w.Modify(func(cfg *config.Configuration) { diff --git a/lib/protocol/bep_fileinfo.go b/lib/protocol/bep_fileinfo.go index f29fd1ae7..7925d14d6 100644 --- a/lib/protocol/bep_fileinfo.go +++ b/lib/protocol/bep_fileinfo.go @@ -19,10 +19,12 @@ import ( // FileInfo.LocalFlags flags const ( - FlagLocalUnsupported = 1 << 0 // The kind is unsupported, e.g. symlinks on Windows - FlagLocalIgnored = 1 << 1 // Matches local ignore patterns - FlagLocalMustRescan = 1 << 2 // Doesn't match content on disk, must be rechecked fully - FlagLocalReceiveOnly = 1 << 3 // Change detected on receive only folder + FlagLocalUnsupported = 1 << 0 // 1: The kind is unsupported, e.g. symlinks on Windows + FlagLocalIgnored = 1 << 1 // 2: Matches local ignore patterns + FlagLocalMustRescan = 1 << 2 // 4: Doesn't match content on disk, must be rechecked fully + FlagLocalReceiveOnly = 1 << 3 // 8: Change detected on receive only folder + FlagLocalGlobal = 1 << 4 // 16: This is the global file version + FlagLocalNeeded = 1 << 5 // 32: We need this file // Flags that should result in the Invalid bit on outgoing updates LocalInvalidFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly @@ -32,7 +34,7 @@ const ( // disk. LocalConflictFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalReceiveOnly - LocalAllFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly + LocalAllFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly | FlagLocalGlobal | FlagLocalNeeded ) // BlockSizes is the list of valid block sizes, from min to max diff --git a/lib/protocol/protocol_test.go b/lib/protocol/protocol_test.go index c4f304126..8a0c1fa1f 100644 --- a/lib/protocol/protocol_test.go +++ b/lib/protocol/protocol_test.go @@ -10,10 +10,8 @@ import ( "bytes" "context" "encoding/hex" - "encoding/json" "errors" "io" - "os" "sync" "testing" "time" @@ -280,28 +278,6 @@ func TestUnmarshalFDPUv16v17(t *testing.T) { } } -func testMarshal(t *testing.T, prefix string, m1, m2 proto.Message) bool { - buf, err := proto.Marshal(m1) - if err != nil { - t.Fatal(err) - } - - err = proto.Unmarshal(buf, m2) - if err != nil { - t.Fatal(err) - } - - bs1, _ := json.MarshalIndent(m1, "", " ") - bs2, _ := json.MarshalIndent(m2, "", " ") - if !bytes.Equal(bs1, bs2) { - os.WriteFile(prefix+"-1.txt", bs1, 0o644) - os.WriteFile(prefix+"-2.txt", bs2, 0o644) - return false - } - - return true -} - func TestWriteCompressed(t *testing.T) { for _, random := range []bool{false, true} { buf := new(bytes.Buffer) diff --git a/lib/protocol/vector.go b/lib/protocol/vector.go index 30cce4231..91ee274f7 100644 --- a/lib/protocol/vector.go +++ b/lib/protocol/vector.go @@ -7,6 +7,11 @@ package protocol import ( + "encoding/binary" + "encoding/hex" + "fmt" + "strconv" + "strings" "time" "github.com/syncthing/syncthing/internal/gen/bep" @@ -20,6 +25,17 @@ type Vector struct { Counters []Counter } +func (v *Vector) String() string { + var buf strings.Builder + for i, c := range v.Counters { + if i > 0 { + buf.WriteRune(',') + } + fmt.Fprintf(&buf, "%x:%d", c.ID, c.Value) + } + return buf.String() +} + func (v *Vector) ToWire() *bep.Vector { counters := make([]*bep.Counter, len(v.Counters)) for i, c := range v.Counters { @@ -42,6 +58,31 @@ func VectorFromWire(w *bep.Vector) Vector { return v } +func VectorFromString(s string) (Vector, error) { + pairs := strings.Split(s, ",") + var v Vector + v.Counters = make([]Counter, len(pairs)) + for i, pair := range pairs { + idStr, valStr, ok := strings.Cut(pair, ":") + if !ok { + return Vector{}, fmt.Errorf("bad pair %q", pair) + } + idslice, err := hex.DecodeString(idStr) + if err != nil { + return Vector{}, fmt.Errorf("bad id in pair %q", pair) + } + var idbs [8]byte + copy(idbs[8-len(idslice):], idslice) + id := binary.BigEndian.Uint64(idbs[:]) + val, err := strconv.ParseUint(valStr, 10, 64) + if err != nil { + return Vector{}, fmt.Errorf("bad val in pair %q", pair) + } + v.Counters[i] = Counter{ID: ShortID(id), Value: val} + } + return v, nil +} + // Counter represents a single counter in the version vector. type Counter struct { ID ShortID diff --git a/lib/relay/protocol/packets_xdr.go b/lib/relay/protocol/packets_xdr.go index fb0903b62..0477c066b 100644 --- a/lib/relay/protocol/packets_xdr.go +++ b/lib/relay/protocol/packets_xdr.go @@ -31,7 +31,7 @@ struct header { */ -func (header) XDRSize() int { +func (o header) XDRSize() int { return 4 + 4 + 4 } @@ -60,7 +60,6 @@ func (o *header) UnmarshalXDR(bs []byte) error { u := &xdr.Unmarshaller{Data: bs} return o.UnmarshalXDRFrom(u) } - func (o *header) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { o.magic = u.UnmarshalUint32() o.messageType = int32(u.UnmarshalUint32()) @@ -79,27 +78,26 @@ struct Ping { */ -func (Ping) XDRSize() int { +func (o Ping) XDRSize() int { return 0 } - -func (Ping) MarshalXDR() ([]byte, error) { +func (o Ping) MarshalXDR() ([]byte, error) { return nil, nil } -func (Ping) MustMarshalXDR() []byte { +func (o Ping) MustMarshalXDR() []byte { return nil } -func (Ping) MarshalXDRInto(_ *xdr.Marshaller) error { +func (o Ping) MarshalXDRInto(m *xdr.Marshaller) error { return nil } -func (*Ping) UnmarshalXDR(_ []byte) error { +func (o *Ping) UnmarshalXDR(bs []byte) error { return nil } -func (*Ping) UnmarshalXDRFrom(_ *xdr.Unmarshaller) error { +func (o *Ping) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { return nil } @@ -114,27 +112,26 @@ struct Pong { */ -func (Pong) XDRSize() int { +func (o Pong) XDRSize() int { return 0 } - -func (Pong) MarshalXDR() ([]byte, error) { +func (o Pong) MarshalXDR() ([]byte, error) { return nil, nil } -func (Pong) MustMarshalXDR() []byte { +func (o Pong) MustMarshalXDR() []byte { return nil } -func (Pong) MarshalXDRInto(_ *xdr.Marshaller) error { +func (o Pong) MarshalXDRInto(m *xdr.Marshaller) error { return nil } -func (*Pong) UnmarshalXDR(_ []byte) error { +func (o *Pong) UnmarshalXDR(bs []byte) error { return nil } -func (*Pong) UnmarshalXDRFrom(_ *xdr.Unmarshaller) error { +func (o *Pong) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { return nil } @@ -149,27 +146,26 @@ struct RelayFull { */ -func (RelayFull) XDRSize() int { +func (o RelayFull) XDRSize() int { return 0 } - -func (RelayFull) MarshalXDR() ([]byte, error) { +func (o RelayFull) MarshalXDR() ([]byte, error) { return nil, nil } -func (RelayFull) MustMarshalXDR() []byte { +func (o RelayFull) MustMarshalXDR() []byte { return nil } -func (RelayFull) MarshalXDRInto(_ *xdr.Marshaller) error { +func (o RelayFull) MarshalXDRInto(m *xdr.Marshaller) error { return nil } -func (*RelayFull) UnmarshalXDR(_ []byte) error { +func (o *RelayFull) UnmarshalXDR(bs []byte) error { return nil } -func (*RelayFull) UnmarshalXDRFrom(_ *xdr.Unmarshaller) error { +func (o *RelayFull) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { return nil } @@ -219,7 +215,6 @@ func (o *JoinRelayRequest) UnmarshalXDR(bs []byte) error { u := &xdr.Unmarshaller{Data: bs} return o.UnmarshalXDRFrom(u) } - func (o *JoinRelayRequest) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { o.Token = u.UnmarshalString() return u.Error @@ -274,7 +269,6 @@ func (o *JoinSessionRequest) UnmarshalXDR(bs []byte) error { u := &xdr.Unmarshaller{Data: bs} return o.UnmarshalXDRFrom(u) } - func (o *JoinSessionRequest) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { o.Key = u.UnmarshalBytesMax(32) return u.Error @@ -331,7 +325,6 @@ func (o *Response) UnmarshalXDR(bs []byte) error { u := &xdr.Unmarshaller{Data: bs} return o.UnmarshalXDRFrom(u) } - func (o *Response) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { o.Code = int32(u.UnmarshalUint32()) o.Message = u.UnmarshalString() @@ -387,7 +380,6 @@ func (o *ConnectRequest) UnmarshalXDR(bs []byte) error { u := &xdr.Unmarshaller{Data: bs} return o.UnmarshalXDRFrom(u) } - func (o *ConnectRequest) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { o.ID = u.UnmarshalBytesMax(32) return u.Error @@ -470,7 +462,6 @@ func (o *SessionInvitation) UnmarshalXDR(bs []byte) error { u := &xdr.Unmarshaller{Data: bs} return o.UnmarshalXDRFrom(u) } - func (o *SessionInvitation) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { o.From = u.UnmarshalBytesMax(32) o.Key = u.UnmarshalBytesMax(32) diff --git a/lib/stats/debug.go b/lib/stats/debug.go deleted file mode 100644 index 0681277df..000000000 --- a/lib/stats/debug.go +++ /dev/null @@ -1,13 +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 https://mozilla.org/MPL/2.0/. - -package stats - -import ( - "github.com/syncthing/syncthing/lib/logger" -) - -var l = logger.DefaultLogger.NewFacility("stats", "Persistent device and folder statistics") diff --git a/lib/stats/device.go b/lib/stats/device.go index e6d97f683..d8d13a3da 100644 --- a/lib/stats/device.go +++ b/lib/stats/device.go @@ -9,9 +9,7 @@ package stats import ( "time" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/internal/db" ) const ( @@ -25,19 +23,17 @@ type DeviceStatistics struct { } type DeviceStatisticsReference struct { - ns *db.NamespacedKV - device protocol.DeviceID + kv *db.Typed } -func NewDeviceStatisticsReference(dba backend.Backend, device protocol.DeviceID) *DeviceStatisticsReference { +func NewDeviceStatisticsReference(kv *db.Typed) *DeviceStatisticsReference { return &DeviceStatisticsReference{ - ns: db.NewDeviceStatisticsNamespace(dba, device.String()), - device: device, + kv: kv, } } func (s *DeviceStatisticsReference) GetLastSeen() (time.Time, error) { - t, ok, err := s.ns.Time(lastSeenKey) + t, ok, err := s.kv.Time(lastSeenKey) if err != nil { return time.Time{}, err } else if !ok { @@ -45,29 +41,25 @@ func (s *DeviceStatisticsReference) GetLastSeen() (time.Time, error) { // time.Time{} from s.ns return time.Unix(0, 0), nil } - l.Debugln("stats.DeviceStatisticsReference.GetLastSeen:", s.device, t) return t, nil } func (s *DeviceStatisticsReference) GetLastConnectionDuration() (time.Duration, error) { - d, ok, err := s.ns.Int64(connDurationKey) + d, ok, err := s.kv.Int64(connDurationKey) if err != nil { return 0, err } else if !ok { return 0, nil } - l.Debugln("stats.DeviceStatisticsReference.GetLastConnectionDuration:", s.device, d) return time.Duration(d), nil } func (s *DeviceStatisticsReference) WasSeen() error { - l.Debugln("stats.DeviceStatisticsReference.WasSeen:", s.device) - return s.ns.PutTime(lastSeenKey, time.Now().Truncate(time.Second)) + return s.kv.PutTime(lastSeenKey, time.Now().Truncate(time.Second)) } func (s *DeviceStatisticsReference) LastConnectionDuration(d time.Duration) error { - l.Debugln("stats.DeviceStatisticsReference.LastConnectionDuration:", s.device, d) - return s.ns.PutInt64(connDurationKey, d.Nanoseconds()) + return s.kv.PutInt64(connDurationKey, d.Nanoseconds()) } func (s *DeviceStatisticsReference) GetStatistics() (DeviceStatistics, error) { diff --git a/lib/stats/folder.go b/lib/stats/folder.go index 8e67c6e84..a8d587359 100644 --- a/lib/stats/folder.go +++ b/lib/stats/folder.go @@ -9,7 +9,7 @@ package stats import ( "time" - "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/internal/db" ) type FolderStatistics struct { @@ -18,8 +18,7 @@ type FolderStatistics struct { } type FolderStatisticsReference struct { - ns *db.NamespacedKV - folder string + kv *db.Typed } type LastFile struct { @@ -28,27 +27,26 @@ type LastFile struct { Deleted bool `json:"deleted"` } -func NewFolderStatisticsReference(ldb *db.Lowlevel, folder string) *FolderStatisticsReference { +func NewFolderStatisticsReference(kv *db.Typed) *FolderStatisticsReference { return &FolderStatisticsReference{ - ns: db.NewFolderStatisticsNamespace(ldb, folder), - folder: folder, + kv: kv, } } func (s *FolderStatisticsReference) GetLastFile() (LastFile, error) { - at, ok, err := s.ns.Time("lastFileAt") + at, ok, err := s.kv.Time("lastFileAt") if err != nil { return LastFile{}, err } else if !ok { return LastFile{}, nil } - file, ok, err := s.ns.String("lastFileName") + file, ok, err := s.kv.String("lastFileName") if err != nil { return LastFile{}, err } else if !ok { return LastFile{}, nil } - deleted, _, err := s.ns.Bool("lastFileDeleted") + deleted, _, err := s.kv.Bool("lastFileDeleted") if err != nil { return LastFile{}, err } @@ -60,25 +58,24 @@ func (s *FolderStatisticsReference) GetLastFile() (LastFile, error) { } func (s *FolderStatisticsReference) ReceivedFile(file string, deleted bool) error { - l.Debugln("stats.FolderStatisticsReference.ReceivedFile:", s.folder, file) - if err := s.ns.PutTime("lastFileAt", time.Now().Truncate(time.Second)); err != nil { + if err := s.kv.PutTime("lastFileAt", time.Now().Truncate(time.Second)); err != nil { return err } - if err := s.ns.PutString("lastFileName", file); err != nil { + if err := s.kv.PutString("lastFileName", file); err != nil { return err } - if err := s.ns.PutBool("lastFileDeleted", deleted); err != nil { + if err := s.kv.PutBool("lastFileDeleted", deleted); err != nil { return err } return nil } func (s *FolderStatisticsReference) ScanCompleted() error { - return s.ns.PutTime("lastScan", time.Now().Truncate(time.Second)) + return s.kv.PutTime("lastScan", time.Now().Truncate(time.Second)) } func (s *FolderStatisticsReference) GetLastScanTime() (time.Time, error) { - lastScan, ok, err := s.ns.Time("lastScan") + lastScan, ok, err := s.kv.Time("lastScan") if err != nil { return time.Time{}, err } else if !ok { diff --git a/lib/stats/stats_test.go b/lib/stats/stats_test.go index 0ebb42342..7f850ae36 100644 --- a/lib/stats/stats_test.go +++ b/lib/stats/stats_test.go @@ -13,15 +13,20 @@ import ( "testing" "time" - "github.com/syncthing/syncthing/lib/db/backend" - "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/db/sqlite" ) func TestDeviceStat(t *testing.T) { - db := backend.OpenLevelDBMemory() - defer db.Close() + sdb, err := sqlite.OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + sdb.Close() + }) - sr := NewDeviceStatisticsReference(db, protocol.LocalDeviceID) + sr := NewDeviceStatisticsReference(db.NewTyped(sdb, "devstatref")) if err := sr.WasSeen(); err != nil { t.Fatal(err) } diff --git a/lib/syncthing/globalmigrations.go b/lib/syncthing/globalmigrations.go index cc4987497..2589f1882 100644 --- a/lib/syncthing/globalmigrations.go +++ b/lib/syncthing/globalmigrations.go @@ -7,13 +7,8 @@ package syncthing import ( - "encoding/binary" - "io" - + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/fs" - "github.com/syncthing/syncthing/lib/protocol" ) const ( @@ -21,8 +16,8 @@ const ( globalMigrationDBKey = "globalMigrationVersion" ) -func globalMigration(ll *db.Lowlevel, cfg config.Wrapper) error { - miscDB := db.NewMiscDataNamespace(ll) +func globalMigration(kv db.KV, cfg config.Wrapper) error { + miscDB := db.NewMiscDB(kv) prevVersion, _, err := miscDB.Int64(globalMigrationDBKey) if err != nil { return err @@ -32,101 +27,7 @@ func globalMigration(ll *db.Lowlevel, cfg config.Wrapper) error { return nil } - if prevVersion < 1 { - if err := encryptionTrailerSizeMigration(ll, cfg); err != nil { - return err - } - } + // currently no migrations return miscDB.PutInt64(globalMigrationDBKey, globalMigrationVersion) } - -func encryptionTrailerSizeMigration(ll *db.Lowlevel, cfg config.Wrapper) error { - encFolders := cfg.Folders() - for folderID, folderCfg := range cfg.Folders() { - if folderCfg.Type != config.FolderTypeReceiveEncrypted { - delete(encFolders, folderID) - } - } - if len(encFolders) == 0 { - return nil - } - - l.Infoln("Running global migration to fix encryption file sizes") - - // Trigger index re-transfer with fixed up sizes - db.DropDeltaIndexIDs(ll) - - for folderID, folderCfg := range encFolders { - fset, err := db.NewFileSet(folderID, ll) - if err != nil { - return err - } - snap, err := fset.Snapshot() - if err != nil { - return err - } - batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error { - // As we can't touch the version, we need to first invalidate the - // files, and then re-add the modified valid files - invalidFiles := make([]protocol.FileInfo, len(files)) - for i, f := range files { - f.SetUnsupported() - invalidFiles[i] = f - } - fset.Update(protocol.LocalDeviceID, invalidFiles) - fset.Update(protocol.LocalDeviceID, files) - return nil - }) - filesystem := folderCfg.Filesystem(fset) - var innerErr error - snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileInfo) bool { - size, err := sizeOfEncryptedTrailer(filesystem, fi.Name) - if err != nil { - // Best effort: If we fail to read a file, it will show as - // locally changed on next scan. - return true - } - fi.EncryptionTrailerSize = size - batch.Append(fi) - err = batch.FlushIfFull() - if err != nil { - innerErr = err - return false - } - return true - }) - snap.Release() - if innerErr != nil { - return innerErr - } - err = batch.Flush() - if err != nil { - return err - } - } - - return nil -} - -// sizeOfEncryptedTrailer returns the size of the encrypted trailer on disk. -// This amount of bytes should be subtracted from the file size to get the -// original file size. -func sizeOfEncryptedTrailer(fs fs.Filesystem, name string) (int, error) { - f, err := fs.Open(name) - if err != nil { - return 0, err - } - defer f.Close() - if _, err := f.Seek(-4, io.SeekEnd); err != nil { - return 0, err - } - var buf [4]byte - if _, err := io.ReadFull(f, buf[:]); err != nil { - return 0, err - } - // The stored size is the size of the encrypted data. - size := int(binary.BigEndian.Uint32(buf[:])) - // We add the size of the length word itself as well. - return size + 4, nil -} diff --git a/lib/syncthing/internals.go b/lib/syncthing/internals.go index 135b29263..af82b9ac4 100644 --- a/lib/syncthing/internals.go +++ b/lib/syncthing/internals.go @@ -8,9 +8,10 @@ package syncthing import ( "context" + "iter" "time" - "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/stats" @@ -23,6 +24,8 @@ type Internals struct { model model.Model } +type Counts = db.Counts + func newInternals(model model.Model) *Internals { return &Internals{ model: model, @@ -77,14 +80,38 @@ func (m *Internals) PendingFolders(deviceID protocol.DeviceID) (map[string]db.Pe return m.model.PendingFolders(deviceID) } -func (m *Internals) DBSnapshot(folderID string) (*db.Snapshot, error) { - return m.model.DBSnapshot(folderID) -} - func (m *Internals) ScanFolderSubdirs(folderID string, paths []string) error { return m.model.ScanFolderSubdirs(folderID, paths) } +func (m *Internals) GlobalSize(folder string) (Counts, error) { + counts, err := m.model.GlobalSize(folder) + if err != nil { + return Counts{}, err + } + return counts, nil +} + +func (m *Internals) LocalSize(folder string) (Counts, error) { + counts, err := m.model.LocalSize(folder, protocol.LocalDeviceID) + if err != nil { + return Counts{}, err + } + return counts, nil +} + +func (m *Internals) NeedSize(folder string, device protocol.DeviceID) (Counts, error) { + counts, err := m.model.NeedSize(folder, device) + if err != nil { + return Counts{}, err + } + return counts, nil +} + +func (m *Internals) AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) { + return m.model.AllGlobalFiles(folder) +} + func (m *Internals) FolderProgressBytesCompleted(folder string) int64 { return m.model.FolderProgressBytesCompleted(folder) } diff --git a/lib/syncthing/syncthing.go b/lib/syncthing/syncthing.go index 60ae452c9..866d3a5dd 100644 --- a/lib/syncthing/syncthing.go +++ b/lib/syncthing/syncthing.go @@ -22,13 +22,12 @@ import ( "github.com/thejerf/suture/v4" + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/lib/api" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/connections" "github.com/syncthing/syncthing/lib/connections/registry" - "github.com/syncthing/syncthing/lib/db" - "github.com/syncthing/syncthing/lib/db/backend" "github.com/syncthing/syncthing/lib/discover" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/locations" @@ -52,21 +51,19 @@ const ( ) type Options struct { - AuditWriter io.Writer - NoUpgrade bool - ProfilerAddr string - ResetDeltaIdxs bool - Verbose bool - // null duration means use default value - DBRecheckInterval time.Duration - DBIndirectGCInterval time.Duration + AuditWriter io.Writer + NoUpgrade bool + ProfilerAddr string + ResetDeltaIdxs bool + Verbose bool + DBMaintenanceInterval time.Duration } type App struct { myID protocol.DeviceID mainService *suture.Supervisor cfg config.Wrapper - ll *db.Lowlevel + sdb db.DB evLogger events.Logger cert tls.Certificate opts Options @@ -80,14 +77,10 @@ type App struct { Internals *Internals } -func New(cfg config.Wrapper, dbBackend backend.Backend, evLogger events.Logger, cert tls.Certificate, opts Options) (*App, error) { - ll, err := db.NewLowlevel(dbBackend, evLogger, db.WithRecheckInterval(opts.DBRecheckInterval), db.WithIndirectGCInterval(opts.DBIndirectGCInterval)) - if err != nil { - return nil, err - } +func New(cfg config.Wrapper, sdb db.DB, evLogger events.Logger, cert tls.Certificate, opts Options) (*App, error) { a := &App{ cfg: cfg, - ll: ll, + sdb: sdb, evLogger: evLogger, opts: opts, cert: cert, @@ -124,7 +117,7 @@ func (a *App) Start() error { func (a *App) startup() error { a.mainService.Add(ur.NewFailureHandler(a.cfg, a.evLogger)) - a.mainService.Add(a.ll) + a.mainService.Add(a.sdb.Service(a.opts.DBMaintenanceInterval)) if a.opts.AuditWriter != nil { a.mainService.Add(newAuditService(a.opts.AuditWriter, a.evLogger)) @@ -180,14 +173,12 @@ func (a *App) startup() error { perf := ur.CpuBench(context.Background(), 3, 150*time.Millisecond) l.Infof("Hashing performance is %.02f MB/s", perf) - if err := db.UpdateSchema(a.ll); err != nil { - l.Warnln("Database schema:", err) - return err - } - if a.opts.ResetDeltaIdxs { l.Infoln("Reinitializing delta index IDs") - db.DropDeltaIndexIDs(a.ll) + if err := a.sdb.DropAllIndexIDs(); err != nil { + l.Warnln("Drop index IDs:", err) + return err + } } protectedFiles := []string{ @@ -198,17 +189,22 @@ func (a *App) startup() error { } // Remove database entries for folders that no longer exist in the config - folders := a.cfg.Folders() - for _, folder := range a.ll.ListFolders() { - if _, ok := folders[folder]; !ok { + cfgFolders := a.cfg.Folders() + dbFolders, err := a.sdb.ListFolders() + if err != nil { + l.Warnln("Listing folders:", err) + return err + } + for _, folder := range dbFolders { + if _, ok := cfgFolders[folder]; !ok { l.Infof("Cleaning metadata for dropped folder %q", folder) - db.DropFolder(a.ll, folder) + a.sdb.DropFolder(folder) } } // Grab the previously running version string from the database. - miscDB := db.NewMiscDataNamespace(a.ll) + miscDB := db.NewMiscDB(a.sdb) prevVersion, _, err := miscDB.String("prevVersion") if err != nil { l.Warnln("Database:", err) @@ -229,7 +225,10 @@ func (a *App) startup() error { if a.cfg.Options().SendFullIndexOnUpgrade { // Drop delta indexes in case we've changed random stuff we // shouldn't have. We will resend our index on next connect. - db.DropDeltaIndexIDs(a.ll) + if err := a.sdb.DropAllIndexIDs(); err != nil { + l.Warnln("Drop index IDs:", err) + return err + } } } @@ -238,13 +237,13 @@ func (a *App) startup() error { miscDB.PutString("prevVersion", build.Version) } - if err := globalMigration(a.ll, a.cfg); err != nil { + if err := globalMigration(a.sdb, a.cfg); err != nil { l.Warnln("Global migration:", err) return err } keyGen := protocol.NewKeyGenerator() - m := model.NewModel(a.cfg, a.myID, a.ll, protectedFiles, a.evLogger, keyGen) + m := model.NewModel(a.cfg, a.myID, a.sdb, protectedFiles, a.evLogger, keyGen) a.Internals = newInternals(m) a.mainService.Add(m) @@ -333,7 +332,7 @@ func (a *App) wait(errChan <-chan error) { done := make(chan struct{}) go func() { - a.ll.Close() + a.sdb.Close() close(done) }() select { @@ -399,7 +398,7 @@ func (a *App) stopWithErr(stopReason svcutil.ExitStatus, err error) svcutil.Exit return a.exitStatus } -func (a *App) setupGUI(m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.Manager, connectionsService connections.Service, urService *ur.Service, errors, systemLog logger.Recorder, miscDB *db.NamespacedKV) error { +func (a *App) setupGUI(m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.Manager, connectionsService connections.Service, urService *ur.Service, errors, systemLog logger.Recorder, miscDB *db.Typed) error { guiCfg := a.cfg.GUI() if !guiCfg.Enabled { diff --git a/lib/syncthing/syncthing_test.go b/lib/syncthing/syncthing_test.go index e9feedf44..89dcabc1c 100644 --- a/lib/syncthing/syncthing_test.go +++ b/lib/syncthing/syncthing_test.go @@ -8,11 +8,12 @@ package syncthing import ( "os" + "strings" "testing" "time" + "github.com/syncthing/syncthing/internal/db/sqlite" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db/backend" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/svcutil" @@ -71,8 +72,14 @@ func TestStartupFail(t *testing.T) { }, protocol.LocalDeviceID, events.NoopLogger) defer os.Remove(cfg.ConfigPath()) - db := backend.OpenMemory() - app, err := New(cfg, db, events.NoopLogger, cert, Options{}) + sdb, err := sqlite.OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + sdb.Close() + }) + app, err := New(cfg, sdb, events.NoopLogger, cert, Options{}) if err != nil { t.Fatal(err) } @@ -102,10 +109,9 @@ func TestStartupFail(t *testing.T) { t.Errorf(`Got different errors "%v" from Start and "%v" from Error`, startErr, err) } - if trans, err := db.NewReadTransaction(); err == nil { + if _, err := sdb.ListFolders(); err == nil { t.Error("Expected error due to db being closed, got nil") - trans.Release() - } else if !backend.IsClosed(err) { + } else if !strings.Contains(err.Error(), "closed") { t.Error("Expected error due to db being closed, got", err) } } diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index 023bbd8f1..41fc77545 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -12,9 +12,16 @@ import ( "fmt" "io" "os" + "sync" + "time" + "github.com/syncthing/syncthing/internal/db" + newdb "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/db/olddb" + "github.com/syncthing/syncthing/internal/db/olddb/backend" + "github.com/syncthing/syncthing/internal/db/sqlite" + "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" - "github.com/syncthing/syncthing/lib/db/backend" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/locations" @@ -150,6 +157,125 @@ func copyFile(src, dst string) error { return nil } -func OpenDBBackend(path string, tuning config.Tuning) (backend.Backend, error) { - return backend.Open(path, backend.Tuning(tuning)) +// Opens a database +func OpenDatabase(path string) (newdb.DB, error) { + sql, err := sqlite.Open(path) + if err != nil { + return nil, err + } + + sdb := newdb.MetricsWrap(sql) + + return sdb, nil +} + +// Attempts migration of the old (LevelDB-based) database type to the new (SQLite-based) type +func TryMigrateDatabase() error { + oldDBDir := locations.Get(locations.LegacyDatabase) + if _, err := os.Lstat(oldDBDir); err != nil { + // No old database + return nil + } + + be, err := backend.OpenLevelDBRO(oldDBDir) + if err != nil { + // Apparently, not a valid old database + return nil + } + + sdb, err := sqlite.OpenForMigration(locations.Get(locations.Database)) + if err != nil { + return err + } + + miscDB := db.NewMiscDB(sdb) + if when, ok, err := miscDB.Time("migrated-from-leveldb-at"); err == nil && ok { + l.Warnf("Old-style database present but already migrated at %v; please manually move or remove %s.", when, oldDBDir) + return nil + } + + l.Infoln("Migrating old-style database to SQLite; this may take a while...") + t0 := time.Now() + + ll, err := olddb.NewLowlevel(be) + if err != nil { + return err + } + + totFiles, totBlocks := 0, 0 + for _, folder := range ll.ListFolders() { + // Start a writer routine + fis := make(chan protocol.FileInfo, 50) + var writeErr error + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + var batch []protocol.FileInfo + files, blocks := 0, 0 + t0 := time.Now() + t1 := time.Now() + for fi := range fis { + batch = append(batch, fi) + files++ + blocks += len(fi.Blocks) + if len(batch) == 1000 { + writeErr = sdb.Update(folder, protocol.LocalDeviceID, batch) + if writeErr != nil { + return + } + batch = batch[:0] + if time.Since(t1) > 10*time.Second { + d := time.Since(t0) + 1 + t1 = time.Now() + l.Infof("Migrating folder %s... (%d files and %dk blocks in %v, %.01f files/s)", folder, files, blocks/1000, d.Truncate(time.Second), float64(files)/d.Seconds()) + } + } + } + if len(batch) > 0 { + writeErr = sdb.Update(folder, protocol.LocalDeviceID, batch) + } + d := time.Since(t0) + 1 + l.Infof("Migrated folder %s; %d files and %dk blocks in %v, %.01f files/s", folder, files, blocks/1000, d.Truncate(time.Second), float64(files)/d.Seconds()) + totFiles += files + totBlocks += blocks + }() + + // Iterate the existing files + fs, err := olddb.NewFileSet(folder, ll) + if err != nil { + return err + } + snap, err := fs.Snapshot() + if err != nil { + return err + } + _ = snap.WithHaveSequence(0, func(fi protocol.FileInfo) bool { + fis <- fi + return true + }) + close(fis) + snap.Release() + + // Wait for writes to complete + wg.Wait() + if writeErr != nil { + return writeErr + } + } + + l.Infoln("Migrating virtual mtimes...") + if err := ll.IterateMtimes(sdb.PutMtime); err != nil { + l.Warnln("Failed to migrate mtimes:", err) + } + + _ = miscDB.PutTime("migrated-from-leveldb-at", time.Now()) + _ = miscDB.PutString("migrated-from-leveldb-by", build.LongVersion) + + be.Close() + sdb.Close() + _ = os.Rename(oldDBDir, oldDBDir+"-migrated") + + l.Infof("Migration complete, %d files and %dk blocks in %s", totFiles, totBlocks/1000, time.Since(t0).Truncate(time.Second)) + return nil } diff --git a/lib/ur/usage_report.go b/lib/ur/usage_report.go index a76e1512e..e69536995 100644 --- a/lib/ur/usage_report.go +++ b/lib/ur/usage_report.go @@ -22,10 +22,10 @@ import ( "time" "github.com/shirou/gopsutil/v4/process" + "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/connections" - "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/scanner" @@ -41,7 +41,7 @@ const Version = 3 var StartTime = time.Now().Truncate(time.Second) type Model interface { - DBSnapshot(folder string) (*db.Snapshot, error) + GlobalSize(folder string) (db.Counts, error) UsageReportingStats(report *contract.Report, version int, preview bool) } @@ -83,12 +83,10 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) ( var totFiles, maxFiles int var totBytes, maxBytes int64 for folderID := range s.cfg.Folders() { - snap, err := s.model.DBSnapshot(folderID) + global, err := s.model.GlobalSize(folderID) if err != nil { - continue + return nil, err } - global := snap.GlobalSize() - snap.Release() totFiles += int(global.Files) totBytes += global.Bytes if int(global.Files) > maxFiles { diff --git a/lib/versioner/external.go b/lib/versioner/external.go index 2a5559408..07467cb90 100644 --- a/lib/versioner/external.go +++ b/lib/versioner/external.go @@ -41,7 +41,7 @@ func newExternal(cfg config.FolderConfiguration) Versioner { s := external{ command: command, - filesystem: cfg.Filesystem(nil), + filesystem: cfg.Filesystem(), } l.Debugf("instantiated %#v", s) diff --git a/lib/versioner/simple.go b/lib/versioner/simple.go index f56a8279b..6a9f4d514 100644 --- a/lib/versioner/simple.go +++ b/lib/versioner/simple.go @@ -41,7 +41,7 @@ func newSimple(cfg config.FolderConfiguration) Versioner { s := simple{ keep: keep, cleanoutDays: cleanoutDays, - folderFs: cfg.Filesystem(nil), + folderFs: cfg.Filesystem(), versionsFs: versionerFsFromFolderCfg(cfg), copyRangeMethod: cfg.CopyRangeMethod.ToFS(), } diff --git a/lib/versioner/simple_test.go b/lib/versioner/simple_test.go index 0087f826b..affcf3081 100644 --- a/lib/versioner/simple_test.go +++ b/lib/versioner/simple_test.go @@ -64,7 +64,7 @@ func TestSimpleVersioningVersionCount(t *testing.T) { }, }, } - fs := cfg.Filesystem(nil) + fs := cfg.Filesystem() v := newSimple(cfg) @@ -116,7 +116,7 @@ func TestPathTildes(t *testing.T) { }, }, } - fs := cfg.Filesystem(nil) + fs := cfg.Filesystem() v := newSimple(cfg) const testPath = "test" diff --git a/lib/versioner/staggered.go b/lib/versioner/staggered.go index e97c2e6d4..9b6ab5434 100644 --- a/lib/versioner/staggered.go +++ b/lib/versioner/staggered.go @@ -44,7 +44,7 @@ func newStaggered(cfg config.FolderConfiguration) Versioner { versionsFs := versionerFsFromFolderCfg(cfg) s := &staggered{ - folderFs: cfg.Filesystem(nil), + folderFs: cfg.Filesystem(), versionsFs: versionsFs, interval: [4]interval{ {30, 60 * 60}, // first hour -> 30 sec between versions diff --git a/lib/versioner/trashcan.go b/lib/versioner/trashcan.go index 7378c3a33..2e228900d 100644 --- a/lib/versioner/trashcan.go +++ b/lib/versioner/trashcan.go @@ -33,7 +33,7 @@ func newTrashcan(cfg config.FolderConfiguration) Versioner { // On error we default to 0, "do not clean out the trash can" s := &trashcan{ - folderFs: cfg.Filesystem(nil), + folderFs: cfg.Filesystem(), versionsFs: versionerFsFromFolderCfg(cfg), cleanoutDays: cleanoutDays, copyRangeMethod: cfg.CopyRangeMethod.ToFS(), diff --git a/lib/versioner/trashcan_test.go b/lib/versioner/trashcan_test.go index a12ee6076..d1abe0349 100644 --- a/lib/versioner/trashcan_test.go +++ b/lib/versioner/trashcan_test.go @@ -34,7 +34,7 @@ func TestTrashcanArchiveRestoreSwitcharoo(t *testing.T) { FSPath: tmpDir2, }, } - folderFs := cfg.Filesystem(nil) + folderFs := cfg.Filesystem() versionsFs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir2) @@ -113,7 +113,7 @@ func TestTrashcanRestoreDeletedFile(t *testing.T) { }, } - folderFs := cfg.Filesystem(nil) + folderFs := cfg.Filesystem() versionsFs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir2) @@ -209,7 +209,7 @@ func TestTrashcanCleanOut(t *testing.T) { }, } - fs := cfg.Filesystem(nil) + fs := cfg.Filesystem() v := newTrashcan(cfg) diff --git a/lib/versioner/util.go b/lib/versioner/util.go index 4693d1d9c..41b4aaf32 100644 --- a/lib/versioner/util.go +++ b/lib/versioner/util.go @@ -259,7 +259,7 @@ func restoreFile(method fs.CopyRangeMethod, src, dst fs.Filesystem, filePath str } func versionerFsFromFolderCfg(cfg config.FolderConfiguration) (versionsFs fs.Filesystem) { - folderFs := cfg.Filesystem(nil) + folderFs := cfg.Filesystem() if cfg.Versioning.FSPath == "" { versionsFs = fs.NewFilesystem(folderFs.Type(), filepath.Join(folderFs.URI(), DefaultPath)) } else if cfg.Versioning.FSType == config.FilesystemTypeBasic { diff --git a/meta/copyright_test.go b/meta/copyright_test.go index 8de266210..31078562a 100644 --- a/meta/copyright_test.go +++ b/meta/copyright_test.go @@ -19,11 +19,12 @@ import ( // File extensions to check var copyrightCheckExts = map[string]bool{ - ".go": true, + ".go": true, + ".sql": true, } // Directories to search -var copyrightCheckDirs = []string{".", "../cmd", "../lib", "../test", "../script"} +var copyrightCheckDirs = []string{".", "../cmd", "../internal", "../lib", "../test", "../script"} // Valid copyright headers, searched for in the top five lines in each file. var copyrightRegexps = []string{ From f1e136a17bed30e124e29740d1554cc107a1186a Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 29 Mar 2025 15:29:01 +0100 Subject: [PATCH 04/62] build: also run APT publish for v2 tags --- .github/workflows/build-syncthing.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index a574c7e9e..9b1f0f121 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -817,7 +817,7 @@ jobs: publish-apt: name: Publish APT - if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v1')) + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')) environment: release needs: - package-debian From a99e670ebbee386f8fc386452ebe689548d7c6c8 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 1 Apr 2025 04:42:16 -0700 Subject: [PATCH 05/62] chore: harmonise command line flags (#10007) (v2 change) This cleans up the command line parsing a little: - Remove the hack for supporting legacy single-dash long options (e.g. `-home`), thus enabling actual short options - Move legacy imperative flags from under the serve command into separate commands, e.g. `syncthing serve --paths` to see the paths list is now `syncthing paths`, `syncthing --upgrade-check` is now `syncthing upgrade --check` - Add environment variable support for all remaining flags for the `serve` command (with one exception, left for the reader to discover), as these are now all modifiers and not imperative ``` % syncthing --help Usage: syncthing Flags: -h, --help Show context-sensitive help. Commands: serve Run Syncthing (default) cli Command line interface for Syncthing browser Open GUI in browser, then exit decrypt Decrypt or verify an encrypted folder device-id Show device ID, then exit generate Generate key and config, then exit paths Show configuration paths, then exit upgrade Perform or check for upgrade, then exit version Show current version, then exit debug Various debugging commands install-completions Print commands to install shell completions Run "syncthing --help" for more information on a command. ``` ``` % syncthing serve --help Usage: syncthing serve [flags] Run Syncthing (default) Flags: -h, --help Show context-sensitive help. -C, --config=PATH Set configuration directory (config and keys) ($STCONFDIR) -D, --data=PATH Set data directory (database and logs) ($STDATADIR) -H, --home=PATH Set configuration and data directory ($STHOMEDIR) --allow-newer-config Allow loading newer than current config version ($STALLOWNEWERCONFIG) --audit Write events to audit file ($STAUDIT) --auditfile=PATH Specify audit file (use "-" for stdout, "--" for stderr) ($STAUDITFILE) --db-maintenance-interval=8h Database maintenance interval ($STDBMAINTINTERVAL) --gui-address=URL Override GUI address (e.g. "http://192.0.2.42:8443") ($STGUIADDRESS) --gui-apikey=API-KEY Override GUI API key ($STGUIAPIKEY) --no-console Hide console window ($STHIDECONSOLE) --logfile=PATH Log file name (see below) ($STLOGFILE) --logflags=BITS Select information in log line prefix (see below) ($STLOGFLAGS) --log-max-old-files=N Number of old files to keep (zero to keep only current) ($STNUMLOGFILES) --log-max-size=BYTES Maximum size of any file (zero to disable log rotation) ($STLOGMAXSIZE) --no-browser Do not start browser ($STNOBROWSER) --no-default-folder Don't create the "default" folder on first startup ($STNODEFAULTFOLDER) --no-port-probing Don't try to find free ports for GUI and listen addresses on first startup ($STNOPORTPROBING) --no-restart Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash ($STNORESTART) --no-upgrade Disable automatic upgrades ($STNOUPGRADE) --paused Start with all devices and folders paused ($STPAUSED) --unpaused Start with all devices and folders unpaused ($STUNPAUSED) --verbose Print verbose log output ($STVERBOSE) --debug-gui-assets-dir=PATH Directory to load GUI assets from ($STGUIASSETS) --debug-perf-stats Write running performance statistics to perf-$pid.csv (Unix only) ($STPERFSTATS) --debug-profile-block Write block profiles to block-$pid-$timestamp.pprof every 20 seconds ($STBLOCKPROFILE) --debug-profile-cpu Write a CPU profile to cpu-$pid.pprof on exit ($STCPUPROFILE) --debug-profile-heap Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases ($STHEAPPROFILE) --debug-profiler-listen=ADDR Network profiler listen address ($STPROFILER) --debug-reset-delta-idxs Reset delta index IDs, forcing a full index exchange ... ``` --- cmd/syncthing/cli/main.go | 8 +- cmd/syncthing/cmdutil/diroptions.go | 14 + cmd/syncthing/cmdutil/options_common.go | 16 - cmd/syncthing/generate/generate.go | 15 +- ...ptions_others.go => hideconsole_others.go} | 4 +- ...ions_windows.go => hideconsole_windows.go} | 6 +- cmd/syncthing/main.go | 295 +++++++++--------- 7 files changed, 172 insertions(+), 186 deletions(-) create mode 100644 cmd/syncthing/cmdutil/diroptions.go delete mode 100644 cmd/syncthing/cmdutil/options_common.go rename cmd/syncthing/{cmdutil/options_others.go => hideconsole_others.go} (86%) rename cmd/syncthing/{cmdutil/options_windows.go => hideconsole_windows.go} (65%) diff --git a/cmd/syncthing/cli/main.go b/cmd/syncthing/cli/main.go index 82367cc67..b0dceff57 100644 --- a/cmd/syncthing/cli/main.go +++ b/cmd/syncthing/cli/main.go @@ -19,10 +19,10 @@ import ( ) type CLI struct { - cmdutil.CommonOptions - DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"` - GUIAddress string `name:"gui-address"` - GUIAPIKey string `name:"gui-apikey"` + cmdutil.DirOptions + + GUIAddress string `name:"gui-address" env:"STGUIADDRESS"` + GUIAPIKey string `name:"gui-apikey" env:"STGUIAPIKEY"` Show showCommand `cmd:"" help:"Show command group"` Debug debugCommand `cmd:"" help:"Debug command group"` diff --git a/cmd/syncthing/cmdutil/diroptions.go b/cmd/syncthing/cmdutil/diroptions.go new file mode 100644 index 000000000..b51126f74 --- /dev/null +++ b/cmd/syncthing/cmdutil/diroptions.go @@ -0,0 +1,14 @@ +// Copyright (C) 2021 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 https://mozilla.org/MPL/2.0/. + +package cmdutil + +// DirOptions are reused among several subcommands +type DirOptions struct { + ConfDir string `name:"config" short:"C" placeholder:"PATH" env:"STCONFDIR" help:"Set configuration directory (config and keys)"` + DataDir string `name:"data" short:"D" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"` + HomeDir string `name:"home" short:"H" placeholder:"PATH" env:"STHOMEDIR" help:"Set configuration and data directory"` +} diff --git a/cmd/syncthing/cmdutil/options_common.go b/cmd/syncthing/cmdutil/options_common.go deleted file mode 100644 index 1a7a30999..000000000 --- a/cmd/syncthing/cmdutil/options_common.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2021 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 https://mozilla.org/MPL/2.0/. - -package cmdutil - -// CommonOptions are reused among several subcommands -type CommonOptions struct { - buildCommonOptions - ConfDir string `name:"config" placeholder:"PATH" env:"STCONFDIR" help:"Set configuration directory (config and keys)"` - HomeDir string `name:"home" placeholder:"PATH" env:"STHOMEDIR" help:"Set configuration and data directory"` - NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"` - SkipPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup"` -} diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go index 5446cb3ba..237aad2f3 100644 --- a/cmd/syncthing/generate/generate.go +++ b/cmd/syncthing/generate/generate.go @@ -21,22 +21,19 @@ import ( "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/locations" "github.com/syncthing/syncthing/lib/logger" - "github.com/syncthing/syncthing/lib/osutil" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/syncthing" ) type CLI struct { - cmdutil.CommonOptions - GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"` - GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"` + cmdutil.DirOptions + GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"` + GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"` + NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"` + NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"` } func (c *CLI) Run(l logger.Logger) error { - if c.HideConsole { - osutil.HideConsole() - } - if c.HomeDir != "" { if c.ConfDir != "" { return errors.New("--home must not be used together with --config") @@ -57,7 +54,7 @@ func (c *CLI) Run(l logger.Logger) error { c.GUIPassword = string(password) } - if err := Generate(l, c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.SkipPortProbing); err != nil { + if err := Generate(l, c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.NoPortProbing); err != nil { return fmt.Errorf("failed to generate config and keys: %w", err) } return nil diff --git a/cmd/syncthing/cmdutil/options_others.go b/cmd/syncthing/hideconsole_others.go similarity index 86% rename from cmd/syncthing/cmdutil/options_others.go rename to cmd/syncthing/hideconsole_others.go index e299ba48c..0688dac1c 100644 --- a/cmd/syncthing/cmdutil/options_others.go +++ b/cmd/syncthing/hideconsole_others.go @@ -7,8 +7,8 @@ //go:build !windows // +build !windows -package cmdutil +package main -type buildCommonOptions struct { +type buildSpecificOptions struct { HideConsole bool `hidden:""` } diff --git a/cmd/syncthing/cmdutil/options_windows.go b/cmd/syncthing/hideconsole_windows.go similarity index 65% rename from cmd/syncthing/cmdutil/options_windows.go rename to cmd/syncthing/hideconsole_windows.go index 99fb50d5b..f51e8acc3 100644 --- a/cmd/syncthing/cmdutil/options_windows.go +++ b/cmd/syncthing/hideconsole_windows.go @@ -4,8 +4,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package cmdutil +package main -type buildCommonOptions struct { - HideConsole bool `name:"no-console" help:"Hide console window"` +type buildSpecificOptions struct { + HideConsole bool `name:"no-console" help:"Hide console window" env:"STHIDECONSOLE"` } diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 740ac7c5b..7df390c71 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -26,7 +26,6 @@ import ( "runtime/pprof" "sort" "strconv" - "strings" "syscall" "time" @@ -130,51 +129,54 @@ var ( // commands and options here are top level commands to syncthing. // Cli is just a placeholder for the help text (see main). var entrypoint struct { - Serve serveOptions `cmd:"" help:"Run Syncthing"` - Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"` - Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"` - Cli cli.CLI `cmd:"" help:"Command line interface for Syncthing"` + Serve serveOptions `cmd:"" help:"Run Syncthing (default)" default:"withargs"` + CLI cli.CLI `cmd:"" help:"Command line interface for Syncthing"` + + Browser browserCmd `cmd:"" help:"Open GUI in browser, then exit"` + Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"` + DeviceID deviceIDCmd `cmd:"" help:"Show device ID, then exit"` + Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"` + Paths pathsCmd `cmd:"" help:"Show configuration paths, then exit"` + Upgrade upgradeCmd `cmd:"" help:"Perform or check for upgrade, then exit"` + Version versionCmd `cmd:"" help:"Show current version, then exit"` + Debug debugCmd `cmd:"" help:"Various debugging commands"` + InstallCompletions kongplete.InstallCompletions `cmd:"" help:"Print commands to install shell completions"` } // serveOptions are the options for the `syncthing serve` command. type serveOptions struct { - cmdutil.CommonOptions - AllowNewerConfig bool `help:"Allow loading newer than current config version"` - Audit bool `help:"Write events to audit file"` - AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"` - BrowserOnly bool `help:"Open GUI in browser"` - DataDir string `name:"data" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"` - DeviceID bool `help:"Show the device ID"` - GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` // DEPRECATED: replaced by subcommand! - GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"` - GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"` - LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"` - LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"` - LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"` - LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"` - NoBrowser bool `help:"Do not start browser"` - NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"` - NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"` - Paths bool `help:"Show configuration paths"` - Paused bool `help:"Start with all devices and folders paused"` - Unpaused bool `help:"Start with all devices and folders unpaused"` - Upgrade bool `help:"Perform upgrade"` - UpgradeCheck bool `help:"Check for available upgrade"` - UpgradeTo string `placeholder:"URL" help:"Force upgrade directly from specified URL"` - Verbose bool `help:"Print verbose log output"` - Version bool `help:"Show version"` - DBMaintenanceInterval time.Duration `env:"STDBMAINTINTERVAL" help:"Database maintenance interval" default:"8h"` + cmdutil.DirOptions + buildSpecificOptions + + AllowNewerConfig bool `help:"Allow loading newer than current config version" env:"STALLOWNEWERCONFIG"` + Audit bool `help:"Write events to audit file" env:"STAUDIT"` + AuditFile string `name:"auditfile" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)" placeholder:"PATH" env:"STAUDITFILE"` + DBMaintenanceInterval time.Duration `help:"Database maintenance interval" default:"8h" env:"STDBMAINTINTERVAL"` + GUIAddress string `name:"gui-address" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")" placeholder:"URL" env:"STGUIADDRESS"` + GUIAPIKey string `name:"gui-apikey" help:"Override GUI API key" placeholder:"API-KEY" env:"STGUIAPIKEY"` + HideConsole bool `name:"no-console" help:"Hide console window" env:"STHIDECONSOLE"` + LogFile string `name:"logfile" help:"Log file name (see below)" default:"${logFile}" placeholder:"PATH" env:"STLOGFILE"` + LogFlags int `name:"logflags" help:"Select information in log line prefix (see below)" default:"${logFlags}" placeholder:"BITS" env:"STLOGFLAGS"` + LogMaxFiles int `name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)" default:"${logMaxFiles}" placeholder:"N" env:"STNUMLOGFILES"` + LogMaxSize int `help:"Maximum size of any file (zero to disable log rotation)" default:"${logMaxSize}" placeholder:"BYTES" env:"STLOGMAXSIZE"` + NoBrowser bool `help:"Do not start browser" env:"STNOBROWSER"` + NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"` + NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"` + NoRestart bool `help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash" env:"STNORESTART"` + NoUpgrade bool `help:"Disable automatic upgrades" env:"STNOUPGRADE"` + Paused bool `help:"Start with all devices and folders paused" env:"STPAUSED"` + Unpaused bool `help:"Start with all devices and folders unpaused" env:"STUNPAUSED"` + Verbose bool `help:"Print verbose log output" env:"STVERBOSE"` // Debug options below - DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"` - DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"` - DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"` + DebugGUIAssetsDir string `help:"Directory to load GUI assets from" placeholder:"PATH" env:"STGUIASSETS"` + DebugPerfStats bool `help:"Write running performance statistics to perf-$pid.csv (Unix only)" env:"STPERFSTATS"` + DebugProfileBlock bool `help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds" env:"STBLOCKPROFILE"` DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"STCPUPROFILE"` - DebugProfileHeap bool `env:"STHEAPPROFILE" help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases"` - DebugProfilerListen string `placeholder:"ADDR" env:"STPROFILER" help:"Network profiler listen address"` - DebugResetDatabase bool `name:"reset-database" help:"Reset the database, forcing a full rescan and resync"` - DebugResetDeltaIdxs bool `name:"reset-deltas" help:"Reset delta index IDs, forcing a full index exchange"` + DebugProfileHeap bool `help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases" env:"STHEAPPROFILE"` + DebugProfilerListen string `help:"Network profiler listen address" placeholder:"ADDR" env:"STPROFILER" ` + DebugResetDeltaIdxs bool `help:"Reset delta index IDs, forcing a full index exchange"` // Internal options, not shown to users InternalRestarting bool `env:"STRESTART" hidden:"1"` @@ -206,29 +208,6 @@ func defaultVars() kong.Vars { } func main() { - // First some massaging of the raw command line to fit the new model. - // Basically this means adding the default command at the front, and - // converting -options to --options. - - args := os.Args[1:] - switch { - case len(args) == 0: - // Empty command line is equivalent to just calling serve - args = []string{"serve"} - case args[0] == "-help": - // For consistency, we consider this equivalent with --help even - // though kong would otherwise consider it a bad flag. - args[0] = "--help" - case args[0] == "-h", args[0] == "--help": - // Top level request for help, let it pass as-is to be handled by - // kong to list commands. - case strings.HasPrefix(args[0], "-"): - // There are flags not preceded by a command, so we tack on the - // "serve" command and convert the old style arguments (single dash) - // to new style (double dash). - args = append([]string{"serve"}, convertLegacyArgs(args)...) - } - // Create a parser with an overridden help function to print our extra // help info. parser, err := kong.New( @@ -245,7 +224,7 @@ func main() { } kongplete.Complete(parser) - ctx, err := parser.Parse(args) + ctx, err := parser.Parse(os.Args[1:]) parser.FatalIfErrorf(err) ctx.BindTo(l, (*logger.Logger)(nil)) // main logger available to subcommands err = ctx.Run() @@ -309,101 +288,12 @@ func (options serveOptions) Run() error { } } - if options.Version { - fmt.Println(build.LongVersion) - return nil - } - - if options.Paths { - fmt.Print(locations.PrettyPaths()) - return nil - } - - if options.DeviceID { - cert, err := tls.LoadX509KeyPair( - locations.Get(locations.CertFile), - locations.Get(locations.KeyFile), - ) - if err != nil { - l.Warnln("Error reading device ID:", err) - os.Exit(svcutil.ExitError.AsInt()) - } - - fmt.Println(protocol.NewDeviceID(cert.Certificate[0])) - return nil - } - - if options.BrowserOnly { - if err := openGUI(); err != nil { - l.Warnln("Failed to open web UI:", err) - os.Exit(svcutil.ExitError.AsInt()) - } - return nil - } - - if options.GenerateDir != "" { - if err := generate.Generate(l, options.GenerateDir, "", "", options.NoDefaultFolder, options.SkipPortProbing); err != nil { - l.Warnln("Failed to generate config and keys:", err) - os.Exit(svcutil.ExitError.AsInt()) - } - return nil - } - // Ensure that our home directory exists. if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0o700); err != nil { l.Warnln("Failure on home directory:", err) os.Exit(svcutil.ExitError.AsInt()) } - if options.UpgradeTo != "" { - err := upgrade.ToURL(options.UpgradeTo) - if err != nil { - l.Warnln("Error while Upgrading:", err) - os.Exit(svcutil.ExitError.AsInt()) - } - l.Infoln("Upgraded from", options.UpgradeTo) - return nil - } - - if options.UpgradeCheck { - if _, err := checkUpgrade(); err != nil { - l.Warnln("Checking for upgrade:", err) - os.Exit(exitCodeForUpgrade(err)) - } - return nil - } - - if options.Upgrade { - release, err := checkUpgrade() - if err == nil { - lf := flock.New(locations.Get(locations.CertFile)) - locked, err := lf.TryLock() - if err != nil { - l.Warnln("Upgrade:", err) - os.Exit(1) - } else if locked { - err = upgradeViaRest() - } else { - err = upgrade.To(release) - } - } - if err != nil { - l.Warnln("Upgrade:", err) - os.Exit(exitCodeForUpgrade(err)) - } - l.Infof("Upgraded to %q", release.Tag) - os.Exit(svcutil.ExitUpgrade.AsInt()) - } - - if options.DebugResetDatabase { - if err := resetDB(); err != nil { - l.Warnln("Resetting database:", err) - os.Exit(svcutil.ExitError.AsInt()) - } - l.Infoln("Successfully reset database - it will be rebuilt after next start.") - return nil - } - if options.InternalInnerProcess { syncthingMain(options) } else { @@ -568,7 +458,7 @@ func syncthingMain(options serveOptions) { evLogger := events.NewLogger() earlyService.Add(evLogger) - cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder, options.SkipPortProbing) + cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder, options.NoPortProbing) if err != nil { l.Warnln("Failed to initialize config:", err) os.Exit(svcutil.ExitError.AsInt()) @@ -938,3 +828,104 @@ func convertLegacyArgs(args []string) []string { return res } + +type versionCmd struct{} + +func (versionCmd) Run() error { + fmt.Println(build.LongVersion) + return nil +} + +type deviceIDCmd struct{} + +func (deviceIDCmd) Run() error { + cert, err := tls.LoadX509KeyPair( + locations.Get(locations.CertFile), + locations.Get(locations.KeyFile), + ) + if err != nil { + l.Warnln("Error reading device ID:", err) + os.Exit(svcutil.ExitError.AsInt()) + } + + fmt.Println(protocol.NewDeviceID(cert.Certificate[0])) + return nil +} + +type pathsCmd struct{} + +func (pathsCmd) Run() error { + fmt.Print(locations.PrettyPaths()) + return nil +} + +type upgradeCmd struct { + CheckOnly bool `short:"c" help:"Check for available upgrade, then exit"` + From string `short:"u" placeholder:"URL" help:"Force upgrade directly from specified URL"` +} + +func (u upgradeCmd) Run() error { + if u.CheckOnly { + if _, err := checkUpgrade(); err != nil { + l.Warnln("Checking for upgrade:", err) + os.Exit(exitCodeForUpgrade(err)) + } + return nil + } + + if u.From != "" { + err := upgrade.ToURL(u.From) + if err != nil { + l.Warnln("Error while Upgrading:", err) + os.Exit(svcutil.ExitError.AsInt()) + } + l.Infoln("Upgraded from", u.From) + return nil + } + + release, err := checkUpgrade() + if err == nil { + lf := flock.New(locations.Get(locations.CertFile)) + locked, err := lf.TryLock() + if err != nil { + l.Warnln("Upgrade:", err) + os.Exit(1) + } else if locked { + err = upgradeViaRest() + } else { + err = upgrade.To(release) + } + } + if err != nil { + l.Warnln("Upgrade:", err) + os.Exit(exitCodeForUpgrade(err)) + } + l.Infof("Upgraded to %q", release.Tag) + os.Exit(svcutil.ExitUpgrade.AsInt()) + return nil +} + +type browserCmd struct{} + +func (browserCmd) Run() error { + if err := openGUI(); err != nil { + l.Warnln("Failed to open web UI:", err) + os.Exit(svcutil.ExitError.AsInt()) + } + return nil +} + +type debugCmd struct { + ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"` +} + +type resetDatabaseCmd struct{} + +func (resetDatabaseCmd) Run() error { + if err := resetDB(); err != nil { + l.Warnln("Resetting database:", err) + os.Exit(svcutil.ExitError.AsInt()) + } + l.Infoln("Successfully reset database - it will be rebuilt after next start.") + return nil +} From 2c3a890d2f2ca67a58f0d3917e4141d8dfb5192b Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 2 Apr 2025 12:21:40 +0200 Subject: [PATCH 06/62] fix(syncthing): remove duplicate --no-console flag --- cmd/syncthing/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 7df390c71..53137467e 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -155,7 +155,6 @@ type serveOptions struct { DBMaintenanceInterval time.Duration `help:"Database maintenance interval" default:"8h" env:"STDBMAINTINTERVAL"` GUIAddress string `name:"gui-address" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")" placeholder:"URL" env:"STGUIADDRESS"` GUIAPIKey string `name:"gui-apikey" help:"Override GUI API key" placeholder:"API-KEY" env:"STGUIAPIKEY"` - HideConsole bool `name:"no-console" help:"Hide console window" env:"STHIDECONSOLE"` LogFile string `name:"logfile" help:"Log file name (see below)" default:"${logFile}" placeholder:"PATH" env:"STLOGFILE"` LogFlags int `name:"logflags" help:"Select information in log line prefix (see below)" default:"${logFlags}" placeholder:"BITS" env:"STLOGFLAGS"` LogMaxFiles int `name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)" default:"${logMaxFiles}" placeholder:"N" env:"STNUMLOGFILES"` From 0bcc31d058184ff4e1447cbed15301ab7dfaf45e Mon Sep 17 00:00:00 2001 From: bt90 Date: Wed, 2 Apr 2025 14:52:44 +0200 Subject: [PATCH 07/62] chore(db): increase journal limit to 64MiB (#10022) The current limit is far too low for our workloads. Perhaps we should aim even higher than the 64MiB this patch proposes? Citing the sqlite docs: > [...] after committing a transaction the rollback journal file may remain in the file-system. **This increases performance for subsequent transactions since overwriting an existing file is faster than append to a file**, but it also consumes file-system space. tl;dr: if the limit is too low, we're shooting ourselves in the foot in terms of performance. --- internal/db/sqlite/db_open.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/db/sqlite/db_open.go b/internal/db/sqlite/db_open.go index 9de8e4d1b..2f959be15 100644 --- a/internal/db/sqlite/db_open.go +++ b/internal/db/sqlite/db_open.go @@ -36,7 +36,7 @@ func Open(path string) (*DB, error) { // https://www.sqlite.org/pragma.html#pragma_optimize return nil, wrap(err, "PRAGMA optimize") } - if _, err := sqlDB.Exec(`PRAGMA journal_size_limit = 6144000`); err != nil { + if _, err := sqlDB.Exec(`PRAGMA journal_size_limit = 67108864`); err != nil { // https://www.powersync.com/blog/sqlite-optimizations-for-ultra-high-performance return nil, wrap(err, "PRAGMA journal_size_limit") } From 86cbc2486ffb5ef047ccccc5c244ea448df81de3 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 2 Apr 2025 05:58:59 -0700 Subject: [PATCH 08/62] chore: forget deleted files older than six months (fixes #6284) (#10023) This reduces the number of file entries we carry in the database, sometimes significantly. The downside is that if a file is deleted while a device is offline, and that device comes back more than the cutoff interval (six months) later, those files will get resurrected at some point. --- internal/db/sqlite/db_service.go | 25 +++++++++++++++++++++++++ lib/syncthing/utils.go | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/internal/db/sqlite/db_service.go b/internal/db/sqlite/db_service.go index 3786fbc10..647c1fdc1 100644 --- a/internal/db/sqlite/db_service.go +++ b/internal/db/sqlite/db_service.go @@ -8,6 +8,7 @@ package sqlite import ( "context" + "fmt" "time" "github.com/syncthing/syncthing/internal/db" @@ -16,6 +17,7 @@ import ( const ( internalMetaPrefix = "dbsvc" lastMaintKey = "lastMaint" + MaxDeletedFileAge = 180 * 24 * time.Hour ) type Service struct { @@ -24,6 +26,10 @@ type Service struct { internalMeta *db.Typed } +func (s *Service) String() string { + return fmt.Sprintf("sqlite.service@%p", s) +} + func newService(sdb *DB, maintenanceInterval time.Duration) *Service { return &Service{ sdb: sdb, @@ -73,6 +79,9 @@ func (s *Service) periodic(ctx context.Context) error { t1 := time.Now() defer func() { l.Debugln("Periodic done in", time.Since(t1), "+", t1.Sub(t0)) }() + if err := s.garbageCollectOldDeletedLocked(); err != nil { + return wrap(err) + } if err := s.garbageCollectBlocklistsAndBlocksLocked(ctx); err != nil { return wrap(err) } @@ -85,6 +94,22 @@ func (s *Service) periodic(ctx context.Context) error { return nil } +func (s *Service) garbageCollectOldDeletedLocked() error { + // Remove deleted files that are marked as not needed (we have processed + // them) and they were deleted more than MaxDeletedFileAge ago. + res, err := s.sdb.stmt(` + DELETE FROM files + WHERE deleted AND modified < ? AND local_flags & {{.FlagLocalNeeded}} == 0 + `).Exec(time.Now().Add(-MaxDeletedFileAge).UnixNano()) + if err != nil { + return wrap(err) + } + if aff, err := res.RowsAffected(); err == nil { + l.Debugln("Removed old deleted file records:", aff) + } + return nil +} + func (s *Service) garbageCollectBlocklistsAndBlocksLocked(ctx context.Context) error { // Remove all blocklists not referred to by any files and, by extension, // any blocks not referred to by a blocklist. This is an expensive diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index 41fc77545..019b59eaa 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -251,6 +251,11 @@ func TryMigrateDatabase() error { return err } _ = snap.WithHaveSequence(0, func(fi protocol.FileInfo) bool { + if fi.Deleted && time.Since(fi.ModTime()) > sqlite.MaxDeletedFileAge { + // Skip deleted files that match the garbage collection + // criteria in the database + return true + } fis <- fi return true }) From 4096a35b86822e9cd09969404fd0bd453ba9fb75 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 2 Apr 2025 10:35:37 -0700 Subject: [PATCH 09/62] fix(db): handle large numbers of blocks in update (#10025) Avoid failure when inserting file with very large block list --- internal/db/sqlite/db_test.go | 33 +++++++++++++++++++++++++++++++++ internal/db/sqlite/db_update.go | 17 ++++++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/internal/db/sqlite/db_test.go b/internal/db/sqlite/db_test.go index 82749d4a9..2fb7a6721 100644 --- a/internal/db/sqlite/db_test.go +++ b/internal/db/sqlite/db_test.go @@ -1055,6 +1055,39 @@ func TestBlocklistGarbageCollection(t *testing.T) { } } +func TestInsertLargeFile(t *testing.T) { + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // Add a large file (many blocks) + + files := []protocol.FileInfo{genFile("test1", 16000, 1)} + if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil { + t.Fatal(err) + } + + // Verify all the blocks are here + + for i, block := range files[0].Blocks { + bs, err := itererr.Collect(sdb.AllLocalBlocksWithHash(block.Hash)) + if err != nil { + t.Fatal(err) + } + if len(bs) == 0 { + t.Error("missing blocks for", i) + } + } +} + func TestErrorWrap(t *testing.T) { if wrap(nil, "foo") != nil { t.Fatal("nil should wrap to nil") diff --git a/internal/db/sqlite/db_update.go b/internal/db/sqlite/db_update.go index 1cfa652c5..d6db8b30a 100644 --- a/internal/db/sqlite/db_update.go +++ b/internal/db/sqlite/db_update.go @@ -310,11 +310,18 @@ func (*DB) insertBlocksLocked(tx *txPreparedStmts, blocklistHash []byte, blocks "size": b.Size, } } - _, err := tx.NamedExec(` - INSERT OR IGNORE INTO blocks (hash, blocklist_hash, idx, offset, size) - VALUES (:hash, :blocklist_hash, :idx, :offset, :size) - `, bs) - return wrap(err) + + // Very large block lists (>8000 blocks) result in "too many variables" + // error. Chunk it to a reasonable size. + for chunk := range slices.Chunk(bs, 1000) { + if _, err := tx.NamedExec(` + INSERT OR IGNORE INTO blocks (hash, blocklist_hash, idx, offset, size) + VALUES (:hash, :blocklist_hash, :idx, :offset, :size) + `, chunk); err != nil { + return wrap(err) + } + } + return nil } func (s *DB) recalcGlobalForFolder(txp *txPreparedStmts, folderIdx int64) error { From 82a0dd8eaab9f172c09f6beb36f85fc677fd39bd Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 2 Apr 2025 13:19:34 -0700 Subject: [PATCH 10/62] chore(db): use shorter read transactions and periodic checkpoint for smaller WAL (#10027) Also make sure our journal size limit is in effect when checkpointing. --- internal/db/sqlite/db.go | 1 + internal/db/sqlite/db_open.go | 4 ---- internal/db/sqlite/db_service.go | 14 +++++++---- internal/db/sqlite/db_update.go | 41 +++++++++++++++++++++++++++++++- lib/model/indexhandler.go | 17 ++----------- 5 files changed, 53 insertions(+), 24 deletions(-) diff --git a/internal/db/sqlite/db.go b/internal/db/sqlite/db.go index cb2da41d8..960218e1b 100644 --- a/internal/db/sqlite/db.go +++ b/internal/db/sqlite/db.go @@ -20,6 +20,7 @@ type DB struct { sql *sqlx.DB localDeviceIdx int64 updateLock sync.Mutex + updatePoints int statementsMut sync.RWMutex statements map[string]*sqlx.Stmt diff --git a/internal/db/sqlite/db_open.go b/internal/db/sqlite/db_open.go index 2f959be15..0a482f17c 100644 --- a/internal/db/sqlite/db_open.go +++ b/internal/db/sqlite/db_open.go @@ -36,10 +36,6 @@ func Open(path string) (*DB, error) { // https://www.sqlite.org/pragma.html#pragma_optimize return nil, wrap(err, "PRAGMA optimize") } - if _, err := sqlDB.Exec(`PRAGMA journal_size_limit = 67108864`); err != nil { - // https://www.powersync.com/blog/sqlite-optimizations-for-ultra-high-performance - return nil, wrap(err, "PRAGMA journal_size_limit") - } return openCommon(sqlDB) } diff --git a/internal/db/sqlite/db_service.go b/internal/db/sqlite/db_service.go index 647c1fdc1..b4bd71a1b 100644 --- a/internal/db/sqlite/db_service.go +++ b/internal/db/sqlite/db_service.go @@ -86,10 +86,16 @@ func (s *Service) periodic(ctx context.Context) error { return wrap(err) } - _, _ = s.sdb.sql.ExecContext(ctx, `ANALYZE`) - _, _ = s.sdb.sql.ExecContext(ctx, `PRAGMA optimize`) - _, _ = s.sdb.sql.ExecContext(ctx, `PRAGMA incremental_vacuum`) - _, _ = s.sdb.sql.ExecContext(ctx, `PRAGMA wal_checkpoint(TRUNCATE)`) + conn, err := s.sdb.sql.Conn(ctx) + if err != nil { + return wrap(err) + } + defer conn.Close() + _, _ = conn.ExecContext(ctx, `ANALYZE`) + _, _ = conn.ExecContext(ctx, `PRAGMA optimize`) + _, _ = conn.ExecContext(ctx, `PRAGMA incremental_vacuum`) + _, _ = conn.ExecContext(ctx, `PRAGMA journal_size_limit = 67108864`) + _, _ = conn.ExecContext(ctx, `PRAGMA wal_checkpoint(TRUNCATE)`) return nil } diff --git a/internal/db/sqlite/db_update.go b/internal/db/sqlite/db_update.go index d6db8b30a..6bd34e6a4 100644 --- a/internal/db/sqlite/db_update.go +++ b/internal/db/sqlite/db_update.go @@ -23,6 +23,13 @@ import ( "google.golang.org/protobuf/proto" ) +const ( + // Arbitrarily chosen values for checkpoint frequency.... + updatePointsPerFile = 100 + updatePointsPerBlock = 1 + updatePointsThreshold = 250_000 +) + func (s *DB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error { s.updateLock.Lock() defer s.updateLock.Unlock() @@ -143,7 +150,12 @@ func (s *DB) Update(folder string, device protocol.DeviceID, fs []protocol.FileI } } - return wrap(tx.Commit()) + if err := tx.Commit(); err != nil { + return wrap(err) + } + + s.periodicCheckpointLocked(fs) + return nil } func (s *DB) DropFolder(folder string) error { @@ -554,3 +566,30 @@ func (e fileRow) Compare(other fileRow) int { return 0 } } + +func (s *DB) periodicCheckpointLocked(fs []protocol.FileInfo) { + // Induce periodic checkpoints. We add points for each file and block, + // and checkpoint when we've written more than a threshold of points. + // This ensures we do not go too long without a checkpoint, while also + // not doing it incessantly for every update. + s.updatePoints += updatePointsPerFile * len(fs) + for _, f := range fs { + s.updatePoints += len(f.Blocks) * updatePointsPerBlock + } + if s.updatePoints > updatePointsThreshold { + l.Debugln("checkpoint at", s.updatePoints) + conn, err := s.sql.Conn(context.Background()) + if err != nil { + l.Debugln("conn:", err) + return + } + defer conn.Close() + if _, err := conn.ExecContext(context.Background(), `PRAGMA journal_size_limit = 67108864`); err != nil { + l.Debugln("PRAGMA journal_size_limit(RESTART):", err) + } + if _, err := conn.ExecContext(context.Background(), `PRAGMA wal_checkpoint(RESTART)`); err != nil { + l.Debugln("PRAGMA wal_checkpoint(RESTART):", err) + } + s.updatePoints = 0 + } +} diff --git a/lib/model/indexhandler.go b/lib/model/indexhandler.go index 49c33666e..2f4d2d9c7 100644 --- a/lib/model/indexhandler.go +++ b/lib/model/indexhandler.go @@ -295,7 +295,6 @@ func (s *indexHandler) sendIndexTo(ctx context.Context) error { var f protocol.FileInfo previousWasDelete := false - t0 := time.Now() for fi, err := range itererr.Zip(s.sdb.AllLocalFilesBySequence(s.folder, protocol.LocalDeviceID, s.localPrevSequence+1, 5000)) { if err != nil { return err @@ -304,15 +303,7 @@ func (s *indexHandler) sendIndexTo(ctx context.Context) error { // Even if the batch is full, we allow a last delete to slip in, we do this by making sure that // the batch ends with a non-delete, or that the last item in the batch is already a delete if batch.Full() && (!fi.IsDeleted() || previousWasDelete) { - if err := batch.Flush(); err != nil { - return err - } - if time.Since(t0) > 5*time.Second { - // minor hack -- avoid very long running read transactions - // during index transmission, to help prevent excessive - // growth of database WAL file - break - } + break } if fi.SequenceNo() < s.localPrevSequence+1 { @@ -348,11 +339,7 @@ func (s *indexHandler) sendIndexTo(ctx context.Context) error { batch.Append(f) } - if err := batch.Flush(); err != nil { - return err - } - - return nil + return batch.Flush() } func (s *indexHandler) receive(fs []protocol.FileInfo, update bool, op string, prevSequence, lastSequence int64) error { From b88aea34b6cd4c7b0ef24e8eea9e20e3c4dd4524 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 2 Apr 2025 23:58:46 -0700 Subject: [PATCH 11/62] fix(syncthing): make directory flags global for all commands (#10028) The home/config/data flags and end vars apply equally to all subcommands --- cmd/syncthing/cli/main.go | 8 -- cmd/syncthing/cmdutil/diroptions.go | 14 --- cmd/syncthing/cmdutil/util.go | 35 ------- cmd/syncthing/generate/generate.go | 15 +-- cmd/syncthing/main.go | 139 ++++++++++++++++------------ cmd/syncthing/monitor.go | 10 +- 6 files changed, 88 insertions(+), 133 deletions(-) delete mode 100644 cmd/syncthing/cmdutil/diroptions.go delete mode 100644 cmd/syncthing/cmdutil/util.go diff --git a/cmd/syncthing/cli/main.go b/cmd/syncthing/cli/main.go index b0dceff57..7422b6eb2 100644 --- a/cmd/syncthing/cli/main.go +++ b/cmd/syncthing/cli/main.go @@ -14,13 +14,10 @@ import ( "github.com/alecthomas/kong" "github.com/kballard/go-shellquote" - "github.com/syncthing/syncthing/cmd/syncthing/cmdutil" "github.com/syncthing/syncthing/lib/config" ) type CLI struct { - cmdutil.DirOptions - GUIAddress string `name:"gui-address" env:"STGUIADDRESS"` GUIAPIKey string `name:"gui-apikey" env:"STGUIAPIKEY"` @@ -37,11 +34,6 @@ type Context struct { } func (cli CLI) AfterApply(kongCtx *kong.Context) error { - err := cmdutil.SetConfigDataLocationsFromFlags(cli.HomeDir, cli.ConfDir, cli.DataDir) - if err != nil { - return fmt.Errorf("command line options: %w", err) - } - clientFactory := &apiClientFactory{ cfg: config.GUIConfiguration{ RawAddress: cli.GUIAddress, diff --git a/cmd/syncthing/cmdutil/diroptions.go b/cmd/syncthing/cmdutil/diroptions.go deleted file mode 100644 index b51126f74..000000000 --- a/cmd/syncthing/cmdutil/diroptions.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2021 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 https://mozilla.org/MPL/2.0/. - -package cmdutil - -// DirOptions are reused among several subcommands -type DirOptions struct { - ConfDir string `name:"config" short:"C" placeholder:"PATH" env:"STCONFDIR" help:"Set configuration directory (config and keys)"` - DataDir string `name:"data" short:"D" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"` - HomeDir string `name:"home" short:"H" placeholder:"PATH" env:"STHOMEDIR" help:"Set configuration and data directory"` -} diff --git a/cmd/syncthing/cmdutil/util.go b/cmd/syncthing/cmdutil/util.go deleted file mode 100644 index 6a44456b0..000000000 --- a/cmd/syncthing/cmdutil/util.go +++ /dev/null @@ -1,35 +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 https://mozilla.org/MPL/2.0/. - -package cmdutil - -import ( - "errors" - - "github.com/syncthing/syncthing/lib/locations" -) - -func SetConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error { - homeSet := homeDir != "" - confSet := confDir != "" - dataSet := dataDir != "" - switch { - case dataSet != confSet: - return errors.New("either both or none of --config and --data must be given, use --home to set both at once") - case homeSet && dataSet: - return errors.New("--home must not be used together with --config and --data") - case homeSet: - confDir = homeDir - dataDir = homeDir - fallthrough - case dataSet: - if err := locations.SetBaseDir(locations.ConfigBaseDir, confDir); err != nil { - return err - } - return locations.SetBaseDir(locations.DataBaseDir, dataDir) - } - return nil -} diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go index 237aad2f3..0d3479c22 100644 --- a/cmd/syncthing/generate/generate.go +++ b/cmd/syncthing/generate/generate.go @@ -11,11 +11,9 @@ import ( "bufio" "context" "crypto/tls" - "errors" "fmt" "os" - "github.com/syncthing/syncthing/cmd/syncthing/cmdutil" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/fs" @@ -26,7 +24,6 @@ import ( ) type CLI struct { - cmdutil.DirOptions GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"` GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"` NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"` @@ -34,16 +31,6 @@ type CLI struct { } func (c *CLI) Run(l logger.Logger) error { - if c.HomeDir != "" { - if c.ConfDir != "" { - return errors.New("--home must not be used together with --config") - } - c.ConfDir = c.HomeDir - } - if c.ConfDir == "" { - c.ConfDir = locations.GetBaseDir(locations.ConfigBaseDir) - } - // Support reading the password from a pipe or similar if c.GUIPassword == "-" { reader := bufio.NewReader(os.Stdin) @@ -54,7 +41,7 @@ func (c *CLI) Run(l logger.Logger) error { c.GUIPassword = string(password) } - if err := Generate(l, c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.NoPortProbing); err != nil { + if err := Generate(l, locations.GetBaseDir(locations.ConfigBaseDir), c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.NoPortProbing); err != nil { return fmt.Errorf("failed to generate config and keys: %w", err) } return nil diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 53137467e..1c2633a0c 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -35,7 +35,6 @@ import ( "github.com/willabides/kongplete" "github.com/syncthing/syncthing/cmd/syncthing/cli" - "github.com/syncthing/syncthing/cmd/syncthing/cmdutil" "github.com/syncthing/syncthing/cmd/syncthing/decrypt" "github.com/syncthing/syncthing/cmd/syncthing/generate" "github.com/syncthing/syncthing/internal/db" @@ -128,9 +127,17 @@ var ( // The entrypoint struct is the main entry point for the command line parser. The // commands and options here are top level commands to syncthing. // Cli is just a placeholder for the help text (see main). -var entrypoint struct { - Serve serveOptions `cmd:"" help:"Run Syncthing (default)" default:"withargs"` - CLI cli.CLI `cmd:"" help:"Command line interface for Syncthing"` +type CLI struct { + // The directory options are defined at top level and available for all + // subcommands. Their settings take effect on the `locations` package by + // way of the command line parser, so anything using `locations.Get` etc + // will be doing the right thing. + ConfDir string `name:"config" short:"C" placeholder:"PATH" env:"STCONFDIR" help:"Set configuration directory (config and keys)"` + DataDir string `name:"data" short:"D" placeholder:"PATH" env:"STDATADIR" help:"Set data directory (database and logs)"` + HomeDir string `name:"home" short:"H" placeholder:"PATH" env:"STHOMEDIR" help:"Set configuration and data directory"` + + Serve serveCmd `cmd:"" help:"Run Syncthing (default)" default:"withargs"` + CLI cli.CLI `cmd:"" help:"Command line interface for Syncthing"` Browser browserCmd `cmd:"" help:"Open GUI in browser, then exit"` Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"` @@ -144,9 +151,14 @@ var entrypoint struct { InstallCompletions kongplete.InstallCompletions `cmd:"" help:"Print commands to install shell completions"` } -// serveOptions are the options for the `syncthing serve` command. -type serveOptions struct { - cmdutil.DirOptions +func (c *CLI) AfterApply() error { + // Executed after parsing command line options but before running actual + // subcommands + return setConfigDataLocationsFromFlags(c.HomeDir, c.ConfDir, c.DataDir) +} + +// serveCmd are the options for the `syncthing serve` command. +type serveCmd struct { buildSpecificOptions AllowNewerConfig bool `help:"Allow loading newer than current config version" env:"STALLOWNEWERCONFIG"` @@ -209,6 +221,7 @@ func defaultVars() kong.Vars { func main() { // Create a parser with an overridden help function to print our extra // help info. + var entrypoint CLI parser, err := kong.New( &entrypoint, kong.ConfigureHelp(kong.HelpOptions{ @@ -242,46 +255,39 @@ func helpHandler(options kong.HelpOptions, ctx *kong.Context) error { return nil } -// serveOptions.Run() is the entrypoint for `syncthing serve` -func (options serveOptions) Run() error { - l.SetFlags(options.LogFlags) +// serveCmd.Run() is the entrypoint for `syncthing serve` +func (c *serveCmd) Run() error { + l.SetFlags(c.LogFlags) - if options.GUIAddress != "" { + if c.GUIAddress != "" { // The config picks this up from the environment. - os.Setenv("STGUIADDRESS", options.GUIAddress) + os.Setenv("STGUIADDRESS", c.GUIAddress) } - if options.GUIAPIKey != "" { + if c.GUIAPIKey != "" { // The config picks this up from the environment. - os.Setenv("STGUIAPIKEY", options.GUIAPIKey) + os.Setenv("STGUIAPIKEY", c.GUIAPIKey) } - if options.HideConsole { + if c.HideConsole { osutil.HideConsole() } - // Not set as default above because the strings can be really long. - err := cmdutil.SetConfigDataLocationsFromFlags(options.HomeDir, options.ConfDir, options.DataDir) - if err != nil { - l.Warnln("Command line options:", err) - os.Exit(svcutil.ExitError.AsInt()) - } - // Treat an explicitly empty log file name as no log file - if options.LogFile == "" { - options.LogFile = "-" + if c.LogFile == "" { + c.LogFile = "-" } - if options.LogFile != "default" { + if c.LogFile != "default" { // We must set this *after* expandLocations above. - if err := locations.Set(locations.LogFile, options.LogFile); err != nil { + if err := locations.Set(locations.LogFile, c.LogFile); err != nil { l.Warnln("Setting log file path:", err) os.Exit(svcutil.ExitError.AsInt()) } } - if options.DebugGUIAssetsDir != "" { + if c.DebugGUIAssetsDir != "" { // The asset dir is blank if STGUIASSETS wasn't set, in which case we // should look for extra assets in the default place. - if err := locations.Set(locations.GUIAssets, options.DebugGUIAssetsDir); err != nil { + if err := locations.Set(locations.GUIAssets, c.DebugGUIAssetsDir); err != nil { l.Warnln("Setting GUI assets path:", err) os.Exit(svcutil.ExitError.AsInt()) } @@ -293,10 +299,10 @@ func (options serveOptions) Run() error { os.Exit(svcutil.ExitError.AsInt()) } - if options.InternalInnerProcess { - syncthingMain(options) + if c.InternalInnerProcess { + c.syncthingMain() } else { - monitorMain(options) + c.monitorMain() } return nil } @@ -405,14 +411,14 @@ func upgradeViaRest() error { return err } -func syncthingMain(options serveOptions) { - if options.DebugProfileBlock { +func (c *serveCmd) syncthingMain() { + if c.DebugProfileBlock { startBlockProfiler() } - if options.DebugProfileHeap { + if c.DebugProfileHeap { startHeapProfiler() } - if options.DebugPerfStats { + if c.DebugPerfStats { startPerfStats() } @@ -457,7 +463,7 @@ func syncthingMain(options serveOptions) { evLogger := events.NewLogger() earlyService.Add(evLogger) - cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder, options.NoPortProbing) + cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, c.AllowNewerConfig, c.NoDefaultFolder, c.NoPortProbing) if err != nil { l.Warnln("Failed to initialize config:", err) os.Exit(svcutil.ExitError.AsInt()) @@ -468,7 +474,7 @@ func syncthingMain(options serveOptions) { // unless we are in a build where it's disabled or the STNOUPGRADE // environment variable is set. - if build.IsCandidate && !upgrade.DisabledByCompilation && !options.NoUpgrade { + if build.IsCandidate && !upgrade.DisabledByCompilation && !c.NoUpgrade { cfgWrapper.Modify(func(cfg *config.Configuration) { l.Infoln("Automatic upgrade is always enabled for candidate releases.") if cfg.Options.AutoUpgradeIntervalH == 0 || cfg.Options.AutoUpgradeIntervalH > 24 { @@ -495,7 +501,7 @@ func syncthingMain(options serveOptions) { // Check if auto-upgrades is possible, and if yes, and it's enabled do an initial // upgrade immediately. The auto-upgrade routine can only be started // later after App is initialised. - autoUpgradePossible := autoUpgradePossible(options) + autoUpgradePossible := c.autoUpgradePossible() if autoUpgradePossible && cfgWrapper.Options().AutoUpgradeEnabled() { // try to do upgrade directly and log the error if relevant. miscDB := db.NewMiscDB(sdb) @@ -515,21 +521,21 @@ func syncthingMain(options serveOptions) { } } - if options.Unpaused { + if c.Unpaused { setPauseState(cfgWrapper, false) - } else if options.Paused { + } else if c.Paused { setPauseState(cfgWrapper, true) } appOpts := syncthing.Options{ - NoUpgrade: options.NoUpgrade, - ProfilerAddr: options.DebugProfilerListen, - ResetDeltaIdxs: options.DebugResetDeltaIdxs, - Verbose: options.Verbose, - DBMaintenanceInterval: options.DBMaintenanceInterval, + NoUpgrade: c.NoUpgrade, + ProfilerAddr: c.DebugProfilerListen, + ResetDeltaIdxs: c.DebugResetDeltaIdxs, + Verbose: c.Verbose, + DBMaintenanceInterval: c.DBMaintenanceInterval, } - if options.Audit { - appOpts.AuditWriter = auditWriter(options.AuditFile) + if c.Audit { + appOpts.AuditWriter = auditWriter(c.AuditFile) } app, err := syncthing.New(cfgWrapper, sdb, evLogger, cert, appOpts) @@ -544,7 +550,7 @@ func syncthingMain(options serveOptions) { setupSignalHandling(app) - if options.DebugProfileCPU { + if c.DebugProfileCPU { f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid())) if err != nil { l.Warnln("Creating profile:", err) @@ -562,7 +568,7 @@ func syncthingMain(options serveOptions) { cleanConfigDirectory() - if cfgWrapper.Options().StartBrowser && !options.NoBrowser && !options.InternalRestarting { + if cfgWrapper.Options().StartBrowser && !c.NoBrowser && !c.InternalRestarting { // Can potentially block if the utility we are invoking doesn't // fork, and just execs, hence keep it in its own routine. go func() { _ = openURL(cfgWrapper.GUI().URL()) }() @@ -574,7 +580,7 @@ func syncthingMain(options serveOptions) { l.Warnln("Syncthing stopped with error:", app.Error()) } - if options.DebugProfileCPU { + if c.DebugProfileCPU { pprof.StopCPUProfile() } @@ -648,15 +654,11 @@ func auditWriter(auditFile string) io.Writer { return fd } -func resetDB() error { - return os.RemoveAll(locations.Get(locations.Database)) -} - -func autoUpgradePossible(options serveOptions) bool { +func (c *serveCmd) autoUpgradePossible() bool { if upgrade.DisabledByCompilation { return false } - if options.NoUpgrade { + if c.NoUpgrade { l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.") return false } @@ -921,10 +923,33 @@ type debugCmd struct { type resetDatabaseCmd struct{} func (resetDatabaseCmd) Run() error { - if err := resetDB(); err != nil { + l.Infoln("Removing database in", locations.Get(locations.Database)) + if err := os.RemoveAll(locations.Get(locations.Database)); err != nil { l.Warnln("Resetting database:", err) os.Exit(svcutil.ExitError.AsInt()) } l.Infoln("Successfully reset database - it will be rebuilt after next start.") return nil } + +func setConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error { + homeSet := homeDir != "" + confSet := confDir != "" + dataSet := dataDir != "" + switch { + case dataSet != confSet: + return errors.New("either both or none of --config and --data must be given, use --home to set both at once") + case homeSet && dataSet: + return errors.New("--home must not be used together with --config and --data") + case homeSet: + confDir = homeDir + dataDir = homeDir + fallthrough + case dataSet: + if err := locations.SetBaseDir(locations.ConfigBaseDir, confDir); err != nil { + return err + } + return locations.SetBaseDir(locations.DataBaseDir, dataDir) + } + return nil +} diff --git a/cmd/syncthing/monitor.go b/cmd/syncthing/monitor.go index a25f390fb..ba58f863d 100644 --- a/cmd/syncthing/monitor.go +++ b/cmd/syncthing/monitor.go @@ -43,7 +43,7 @@ const ( panicUploadNoticeWait = 10 * time.Second ) -func monitorMain(options serveOptions) { +func (c *serveCmd) monitorMain() { l.SetPrefix("[monitor] ") var dst io.Writer = os.Stdout @@ -58,8 +58,8 @@ func monitorMain(options serveOptions) { open := func(name string) (io.WriteCloser, error) { return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime) } - if options.LogMaxSize > 0 { - fileDst, err = newRotatedFile(logFile, open, int64(options.LogMaxSize), options.LogMaxFiles) + if c.LogMaxSize > 0 { + fileDst, err = newRotatedFile(logFile, open, int64(c.LogMaxSize), c.LogMaxFiles) } else { fileDst, err = open(logFile) } @@ -178,7 +178,7 @@ func monitorMain(options serveOptions) { if exiterr, ok := err.(*exec.ExitError); ok { exitCode := exiterr.ExitCode() - if stopped || options.NoRestart { + if stopped || c.NoRestart { os.Exit(exitCode) } if exitCode == svcutil.ExitUpgrade.AsInt() { @@ -192,7 +192,7 @@ func monitorMain(options serveOptions) { } } - if options.NoRestart { + if c.NoRestart { os.Exit(svcutil.ExitError.AsInt()) } From 8a2d8ebf813d16453fed0e01afe528da274fa752 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 3 Apr 2025 00:55:19 -0700 Subject: [PATCH 12/62] chore: configurable delete retention interval (#10030) Command line flag, as it also needs to be able to take effect during migration. --- cmd/syncthing/main.go | 41 ++++++++++++++++---------------- internal/db/sqlite/db.go | 18 ++++++++++---- internal/db/sqlite/db_open.go | 17 +++++++++---- internal/db/sqlite/db_service.go | 15 ++++++++---- lib/syncthing/utils.go | 8 +++---- 5 files changed, 62 insertions(+), 37 deletions(-) diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 1c2633a0c..172ca9cbe 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -161,24 +161,25 @@ func (c *CLI) AfterApply() error { type serveCmd struct { buildSpecificOptions - AllowNewerConfig bool `help:"Allow loading newer than current config version" env:"STALLOWNEWERCONFIG"` - Audit bool `help:"Write events to audit file" env:"STAUDIT"` - AuditFile string `name:"auditfile" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)" placeholder:"PATH" env:"STAUDITFILE"` - DBMaintenanceInterval time.Duration `help:"Database maintenance interval" default:"8h" env:"STDBMAINTINTERVAL"` - GUIAddress string `name:"gui-address" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")" placeholder:"URL" env:"STGUIADDRESS"` - GUIAPIKey string `name:"gui-apikey" help:"Override GUI API key" placeholder:"API-KEY" env:"STGUIAPIKEY"` - LogFile string `name:"logfile" help:"Log file name (see below)" default:"${logFile}" placeholder:"PATH" env:"STLOGFILE"` - LogFlags int `name:"logflags" help:"Select information in log line prefix (see below)" default:"${logFlags}" placeholder:"BITS" env:"STLOGFLAGS"` - LogMaxFiles int `name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)" default:"${logMaxFiles}" placeholder:"N" env:"STNUMLOGFILES"` - LogMaxSize int `help:"Maximum size of any file (zero to disable log rotation)" default:"${logMaxSize}" placeholder:"BYTES" env:"STLOGMAXSIZE"` - NoBrowser bool `help:"Do not start browser" env:"STNOBROWSER"` - NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"` - NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"` - NoRestart bool `help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash" env:"STNORESTART"` - NoUpgrade bool `help:"Disable automatic upgrades" env:"STNOUPGRADE"` - Paused bool `help:"Start with all devices and folders paused" env:"STPAUSED"` - Unpaused bool `help:"Start with all devices and folders unpaused" env:"STUNPAUSED"` - Verbose bool `help:"Print verbose log output" env:"STVERBOSE"` + AllowNewerConfig bool `help:"Allow loading newer than current config version" env:"STALLOWNEWERCONFIG"` + Audit bool `help:"Write events to audit file" env:"STAUDIT"` + AuditFile string `name:"auditfile" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)" placeholder:"PATH" env:"STAUDITFILE"` + DBMaintenanceInterval time.Duration `help:"Database maintenance interval" default:"8h" env:"STDBMAINTENANCEINTERVAL"` + DBDeleteRetentionInterval time.Duration `help:"Database deleted item retention interval" default:"4320h" env:"STDBDELETERETENTIONINTERVAL"` + GUIAddress string `name:"gui-address" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")" placeholder:"URL" env:"STGUIADDRESS"` + GUIAPIKey string `name:"gui-apikey" help:"Override GUI API key" placeholder:"API-KEY" env:"STGUIAPIKEY"` + LogFile string `name:"logfile" help:"Log file name (see below)" default:"${logFile}" placeholder:"PATH" env:"STLOGFILE"` + LogFlags int `name:"logflags" help:"Select information in log line prefix (see below)" default:"${logFlags}" placeholder:"BITS" env:"STLOGFLAGS"` + LogMaxFiles int `name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)" default:"${logMaxFiles}" placeholder:"N" env:"STLOGMAXOLDFILES"` + LogMaxSize int `help:"Maximum size of any file (zero to disable log rotation)" default:"${logMaxSize}" placeholder:"BYTES" env:"STLOGMAXSIZE"` + NoBrowser bool `help:"Do not start browser" env:"STNOBROWSER"` + NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"` + NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"` + NoRestart bool `help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash" env:"STNORESTART"` + NoUpgrade bool `help:"Disable automatic upgrades" env:"STNOUPGRADE"` + Paused bool `help:"Start with all devices and folders paused" env:"STPAUSED"` + Unpaused bool `help:"Start with all devices and folders unpaused" env:"STUNPAUSED"` + Verbose bool `help:"Print verbose log output" env:"STVERBOSE"` // Debug options below DebugGUIAssetsDir string `help:"Directory to load GUI assets from" placeholder:"PATH" env:"STGUIASSETS"` @@ -487,12 +488,12 @@ func (c *serveCmd) syncthingMain() { }) } - if err := syncthing.TryMigrateDatabase(); err != nil { + if err := syncthing.TryMigrateDatabase(c.DBDeleteRetentionInterval); err != nil { l.Warnln("Failed to migrate old-style database:", err) os.Exit(1) } - sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database)) + sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database), c.DBDeleteRetentionInterval) if err != nil { l.Warnln("Error opening database:", err) os.Exit(1) diff --git a/internal/db/sqlite/db.go b/internal/db/sqlite/db.go index 960218e1b..a0a6a5f1f 100644 --- a/internal/db/sqlite/db.go +++ b/internal/db/sqlite/db.go @@ -17,10 +17,12 @@ import ( ) type DB struct { - sql *sqlx.DB - localDeviceIdx int64 - updateLock sync.Mutex - updatePoints int + sql *sqlx.DB + localDeviceIdx int64 + deleteRetention time.Duration + + updateLock sync.Mutex + updatePoints int statementsMut sync.RWMutex statements map[string]*sqlx.Stmt @@ -29,6 +31,14 @@ type DB struct { var _ db.DB = (*DB)(nil) +type Option func(*DB) + +func WithDeleteRetention(d time.Duration) Option { + return func(s *DB) { + s.deleteRetention = d + } +} + func (s *DB) Close() error { s.updateLock.Lock() s.statementsMut.Lock() diff --git a/internal/db/sqlite/db_open.go b/internal/db/sqlite/db_open.go index 0a482f17c..b7d6346b7 100644 --- a/internal/db/sqlite/db_open.go +++ b/internal/db/sqlite/db_open.go @@ -21,7 +21,7 @@ import ( const maxDBConns = 128 -func Open(path string) (*DB, error) { +func Open(path string, opts ...Option) (*DB, error) { // Open the database with options to enable foreign keys and recursive // triggers (needed for the delete+insert triggers on row replace). sqlDB, err := sqlx.Open(dbDriver, "file:"+path+"?"+commonOptions) @@ -36,7 +36,7 @@ func Open(path string) (*DB, error) { // https://www.sqlite.org/pragma.html#pragma_optimize return nil, wrap(err, "PRAGMA optimize") } - return openCommon(sqlDB) + return openCommon(sqlDB, opts...) } // Open the database with options suitable for the migration inserts. This @@ -73,7 +73,7 @@ func OpenTemp() (*DB, error) { return Open(path) } -func openCommon(sqlDB *sqlx.DB) (*DB, error) { +func openCommon(sqlDB *sqlx.DB, opts ...Option) (*DB, error) { if _, err := sqlDB.Exec(`PRAGMA auto_vacuum = INCREMENTAL`); err != nil { return nil, wrap(err, "PRAGMA auto_vacuum") } @@ -85,8 +85,15 @@ func openCommon(sqlDB *sqlx.DB) (*DB, error) { } db := &DB{ - sql: sqlDB, - statements: make(map[string]*sqlx.Stmt), + sql: sqlDB, + deleteRetention: defaultDeleteRetention, + statements: make(map[string]*sqlx.Stmt), + } + for _, opt := range opts { + opt(db) + } + if db.deleteRetention > 0 && db.deleteRetention < minDeleteRetention { + db.deleteRetention = minDeleteRetention } if err := db.runScripts("sql/schema/*"); err != nil { diff --git a/internal/db/sqlite/db_service.go b/internal/db/sqlite/db_service.go index b4bd71a1b..c31acb19a 100644 --- a/internal/db/sqlite/db_service.go +++ b/internal/db/sqlite/db_service.go @@ -15,9 +15,10 @@ import ( ) const ( - internalMetaPrefix = "dbsvc" - lastMaintKey = "lastMaint" - MaxDeletedFileAge = 180 * 24 * time.Hour + internalMetaPrefix = "dbsvc" + lastMaintKey = "lastMaint" + defaultDeleteRetention = 180 * 24 * time.Hour + minDeleteRetention = 24 * time.Hour ) type Service struct { @@ -101,12 +102,18 @@ func (s *Service) periodic(ctx context.Context) error { } func (s *Service) garbageCollectOldDeletedLocked() error { + if s.sdb.deleteRetention <= 0 { + l.Debugln("Delete retention is infinite, skipping cleanup") + return nil + } + // Remove deleted files that are marked as not needed (we have processed // them) and they were deleted more than MaxDeletedFileAge ago. + l.Debugln("Forgetting deleted files older than", s.sdb.deleteRetention) res, err := s.sdb.stmt(` DELETE FROM files WHERE deleted AND modified < ? AND local_flags & {{.FlagLocalNeeded}} == 0 - `).Exec(time.Now().Add(-MaxDeletedFileAge).UnixNano()) + `).Exec(time.Now().Add(-s.sdb.deleteRetention).UnixNano()) if err != nil { return wrap(err) } diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index 019b59eaa..25ed23034 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -158,8 +158,8 @@ func copyFile(src, dst string) error { } // Opens a database -func OpenDatabase(path string) (newdb.DB, error) { - sql, err := sqlite.Open(path) +func OpenDatabase(path string, deleteRetention time.Duration) (newdb.DB, error) { + sql, err := sqlite.Open(path, sqlite.WithDeleteRetention(deleteRetention)) if err != nil { return nil, err } @@ -170,7 +170,7 @@ func OpenDatabase(path string) (newdb.DB, error) { } // Attempts migration of the old (LevelDB-based) database type to the new (SQLite-based) type -func TryMigrateDatabase() error { +func TryMigrateDatabase(deleteRetention time.Duration) error { oldDBDir := locations.Get(locations.LegacyDatabase) if _, err := os.Lstat(oldDBDir); err != nil { // No old database @@ -251,7 +251,7 @@ func TryMigrateDatabase() error { return err } _ = snap.WithHaveSequence(0, func(fi protocol.FileInfo) bool { - if fi.Deleted && time.Since(fi.ModTime()) > sqlite.MaxDeletedFileAge { + if deleteRetention > 0 && fi.Deleted && time.Since(fi.ModTime()) > deleteRetention { // Skip deleted files that match the garbage collection // criteria in the database return true From bae976905c5948446f042c66753a434e0496b3d3 Mon Sep 17 00:00:00 2001 From: bt90 Date: Thu, 3 Apr 2025 20:21:39 +0200 Subject: [PATCH 13/62] chore(db): fix debug logging (#10033) Looks like a copy&paste error --- internal/db/sqlite/db_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/db/sqlite/db_update.go b/internal/db/sqlite/db_update.go index 6bd34e6a4..c90d2e68a 100644 --- a/internal/db/sqlite/db_update.go +++ b/internal/db/sqlite/db_update.go @@ -585,7 +585,7 @@ func (s *DB) periodicCheckpointLocked(fs []protocol.FileInfo) { } defer conn.Close() if _, err := conn.ExecContext(context.Background(), `PRAGMA journal_size_limit = 67108864`); err != nil { - l.Debugln("PRAGMA journal_size_limit(RESTART):", err) + l.Debugln("PRAGMA journal_size_limit:", err) } if _, err := conn.ExecContext(context.Background(), `PRAGMA wal_checkpoint(RESTART)`); err != nil { l.Debugln("PRAGMA wal_checkpoint(RESTART):", err) From fa3b9acca368cf37652a77b118b327c1ffc89c77 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 3 Apr 2025 23:15:59 -0700 Subject: [PATCH 14/62] chore(db): buffer pulled files for smaller WAL (#10036) We can't hold a long select open while pulling. --- internal/db/sqlite/db_update.go | 9 +++++++-- lib/model/folder_sendrecv.go | 16 ++++++++++++---- lib/model/indexhandler.go | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/internal/db/sqlite/db_update.go b/internal/db/sqlite/db_update.go index c90d2e68a..7b8dd9905 100644 --- a/internal/db/sqlite/db_update.go +++ b/internal/db/sqlite/db_update.go @@ -577,7 +577,6 @@ func (s *DB) periodicCheckpointLocked(fs []protocol.FileInfo) { s.updatePoints += len(f.Blocks) * updatePointsPerBlock } if s.updatePoints > updatePointsThreshold { - l.Debugln("checkpoint at", s.updatePoints) conn, err := s.sql.Conn(context.Background()) if err != nil { l.Debugln("conn:", err) @@ -587,8 +586,14 @@ func (s *DB) periodicCheckpointLocked(fs []protocol.FileInfo) { if _, err := conn.ExecContext(context.Background(), `PRAGMA journal_size_limit = 67108864`); err != nil { l.Debugln("PRAGMA journal_size_limit:", err) } - if _, err := conn.ExecContext(context.Background(), `PRAGMA wal_checkpoint(RESTART)`); err != nil { + row := conn.QueryRowContext(context.Background(), `PRAGMA wal_checkpoint(RESTART)`) + var res, modified, moved int + if row.Err() != nil { l.Debugln("PRAGMA wal_checkpoint(RESTART):", err) + } else if err := row.Scan(&res, &modified, &moved); err != nil { + l.Debugln("PRAGMA wal_checkpoint(RESTART) (scan):", err) + } else { + l.Debugln("checkpoint at", s.updatePoints, "returned", res, modified, moved) } s.updatePoints = 0 } diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 5011cf9cd..d78c45d12 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -315,15 +315,23 @@ func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyC fileDeletions := map[string]protocol.FileInfo{} buckets := map[string][]protocol.FileInfo{} + // Buffer the full list of needed files. This is somewhat wasteful and + // uses a lot of memory, but we need to keep the duration of the + // database read short and not do a bunch of file and data I/O inside + // the loop. If we forego the ability for users to repriorize the pull + // queue on the fly we could do this in batches, though that would also + // be a bit slower and less efficient in other ways. + files, err := itererr.Collect(f.model.sdb.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, f.Order, 0, 0)) + if err != nil { + return changed, nil, nil, err + } + // Iterate the list of items that we need and sort them into piles. // Regular files to pull goes into the file queue, everything else // (directories, symlinks and deletes) goes into the "process directly" // pile. loop: - for file, err := range itererr.Zip(f.model.sdb.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, f.Order, 0, 0)) { - if err != nil { - return changed, nil, nil, err - } + for _, file := range files { select { case <-f.ctx.Done(): break loop diff --git a/lib/model/indexhandler.go b/lib/model/indexhandler.go index 2f4d2d9c7..bb61e686c 100644 --- a/lib/model/indexhandler.go +++ b/lib/model/indexhandler.go @@ -295,7 +295,7 @@ func (s *indexHandler) sendIndexTo(ctx context.Context) error { var f protocol.FileInfo previousWasDelete := false - for fi, err := range itererr.Zip(s.sdb.AllLocalFilesBySequence(s.folder, protocol.LocalDeviceID, s.localPrevSequence+1, 5000)) { + for fi, err := range itererr.Zip(s.sdb.AllLocalFilesBySequence(s.folder, protocol.LocalDeviceID, s.localPrevSequence+1, MaxBatchSizeFiles+1)) { if err != nil { return err } From cf1cf85ce60c87c01abd7162eb9cb28f31a8446a Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sun, 6 Apr 2025 05:30:43 -0700 Subject: [PATCH 15/62] chore(db): use one SQLite database per folder (#10042) This changes the database structure to use one database per folder, with a small main database to coordinate. Reverts the prior change to buffer all files in memory when pulling, meaning there is now a phase where the WAL file will grow significantly, at least for initial sync of folders with many directories. --------- Co-authored-by: bt90 --- internal/db/interface.go | 4 +- internal/db/metrics.go | 6 +- internal/db/sqlite/basedb.go | 251 ++++++++ internal/db/sqlite/db.go | 88 --- internal/db/sqlite/db_folderdb.go | 391 ++++++++++++ internal/db/sqlite/db_kv.go | 8 +- internal/db/sqlite/db_local_test.go | 41 +- internal/db/sqlite/db_mtimes.go | 54 -- internal/db/sqlite/db_open.go | 247 +++----- internal/db/sqlite/db_schema.go | 88 --- internal/db/sqlite/db_service.go | 56 +- internal/db/sqlite/db_test.go | 16 +- internal/db/sqlite/db_update.go | 564 +----------------- .../{db_counts.go => folderdb_counts.go} | 50 +- .../{db_global.go => folderdb_global.go} | 61 +- .../{db_indexid.go => folderdb_indexid.go} | 50 +- .../sqlite/{db_local.go => folderdb_local.go} | 72 +-- internal/db/sqlite/folderdb_mtimes.go | 45 ++ internal/db/sqlite/folderdb_open.go | 110 ++++ internal/db/sqlite/folderdb_update.go | 531 +++++++++++++++++ .../sql/schema/{ => common}/10-schema.sql | 0 .../sqlite/sql/schema/{ => common}/70-kv.sql | 0 .../sql/schema/{ => folder}/00-indexes.sql | 9 +- .../sql/schema/{ => folder}/20-files.sql | 12 +- .../sql/schema/{ => folder}/30-indexids.sql | 8 +- .../sql/schema/{ => folder}/40-counts.sql | 22 +- .../sql/schema/{ => folder}/50-blocks.sql | 0 .../sql/schema/{ => folder}/50-mtimes.sql | 4 +- .../db/sqlite/sql/schema/main/00-indexes.sql | 12 + lib/locations/locations.go | 2 +- lib/model/folder_sendrecv.go | 104 ++-- lib/model/folder_sendrecv_test.go | 9 +- 32 files changed, 1696 insertions(+), 1219 deletions(-) create mode 100644 internal/db/sqlite/basedb.go delete mode 100644 internal/db/sqlite/db.go create mode 100644 internal/db/sqlite/db_folderdb.go delete mode 100644 internal/db/sqlite/db_mtimes.go delete mode 100644 internal/db/sqlite/db_schema.go rename internal/db/sqlite/{db_counts.go => folderdb_counts.go} (60%) rename internal/db/sqlite/{db_global.go => folderdb_global.go} (63%) rename internal/db/sqlite/{db_indexid.go => folderdb_indexid.go} (64%) rename internal/db/sqlite/{db_local.go => folderdb_local.go} (59%) create mode 100644 internal/db/sqlite/folderdb_mtimes.go create mode 100644 internal/db/sqlite/folderdb_open.go create mode 100644 internal/db/sqlite/folderdb_update.go rename internal/db/sqlite/sql/schema/{ => common}/10-schema.sql (100%) rename internal/db/sqlite/sql/schema/{ => common}/70-kv.sql (100%) rename internal/db/sqlite/sql/schema/{ => folder}/00-indexes.sql (63%) rename internal/db/sqlite/sql/schema/{ => folder}/20-files.sql (88%) rename internal/db/sqlite/sql/schema/{ => folder}/30-indexids.sql (70%) rename internal/db/sqlite/sql/schema/{ => folder}/40-counts.sql (57%) rename internal/db/sqlite/sql/schema/{ => folder}/50-blocks.sql (100%) rename internal/db/sqlite/sql/schema/{ => folder}/50-mtimes.sql (76%) create mode 100644 internal/db/sqlite/sql/schema/main/00-indexes.sql diff --git a/internal/db/interface.go b/internal/db/interface.go index 550143d73..15252d33c 100644 --- a/internal/db/interface.go +++ b/internal/db/interface.go @@ -36,13 +36,13 @@ type DB interface { // required. AllGlobalFiles(folder string) (iter.Seq[FileMetadata], func() error) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[FileMetadata], func() error) - AllLocalBlocksWithHash(hash []byte) (iter.Seq[BlockMapEntry], func() error) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[FileMetadata], func() error) - AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, FileMetadata], func() error) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) + AllLocalBlocksWithHash(hash []byte) ([]BlockMapEntry, error) + AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]FileMetadata, error) // Cleanup DropAllFiles(folder string, device protocol.DeviceID) error diff --git a/internal/db/metrics.go b/internal/db/metrics.go index d59408dc4..f906bceed 100644 --- a/internal/db/metrics.go +++ b/internal/db/metrics.go @@ -67,9 +67,9 @@ func (m metricsDB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Se return m.DB.AllLocalFilesWithBlocksHash(folder, h) } -func (m metricsDB) AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, FileMetadata], func() error) { +func (m metricsDB) AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]FileMetadata, error) { defer m.account("-", "AllLocalFilesWithBlocksHashAnyFolder")() - return m.DB.AllLocalFilesWithBlocksHashAnyFolder(h) + return m.DB.AllLocalFilesWithBlocksHashAnyFolder(hash) } func (m metricsDB) AllGlobalFiles(folder string) (iter.Seq[FileMetadata], func() error) { @@ -107,7 +107,7 @@ func (m metricsDB) GetGlobalAvailability(folder, file string) ([]protocol.Device return m.DB.GetGlobalAvailability(folder, file) } -func (m metricsDB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[BlockMapEntry], func() error) { +func (m metricsDB) AllLocalBlocksWithHash(hash []byte) ([]BlockMapEntry, error) { defer m.account("-", "AllLocalBlocksWithHash")() return m.DB.AllLocalBlocksWithHash(hash) } diff --git a/internal/db/sqlite/basedb.go b/internal/db/sqlite/basedb.go new file mode 100644 index 000000000..63f09c160 --- /dev/null +++ b/internal/db/sqlite/basedb.go @@ -0,0 +1,251 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "database/sql" + "embed" + "io/fs" + "path/filepath" + "strconv" + "strings" + "sync" + "text/template" + "time" + + "github.com/jmoiron/sqlx" + "github.com/syncthing/syncthing/lib/build" + "github.com/syncthing/syncthing/lib/protocol" +) + +const currentSchemaVersion = 1 + +//go:embed sql/** +var embedded embed.FS + +type baseDB struct { + path string + baseName string + sql *sqlx.DB + + updateLock sync.Mutex + updatePoints int + checkpointsCount int + + statementsMut sync.RWMutex + statements map[string]*sqlx.Stmt + tplInput map[string]any +} + +func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScripts []string) (*baseDB, error) { + // Open the database with options to enable foreign keys and recursive + // triggers (needed for the delete+insert triggers on row replace). + sqlDB, err := sqlx.Open(dbDriver, "file:"+path+"?"+commonOptions) + if err != nil { + return nil, wrap(err) + } + + sqlDB.SetMaxOpenConns(maxConns) + + for _, pragma := range pragmas { + if _, err := sqlDB.Exec("PRAGMA " + pragma); err != nil { + return nil, wrap(err, "PRAGMA "+pragma) + } + } + + db := &baseDB{ + path: path, + baseName: filepath.Base(path), + sql: sqlDB, + statements: make(map[string]*sqlx.Stmt), + } + + for _, script := range schemaScripts { + if err := db.runScripts(script); err != nil { + return nil, wrap(err) + } + } + + ver, _ := db.getAppliedSchemaVersion() + if ver.SchemaVersion > 0 { + filter := func(scr string) bool { + scr = filepath.Base(scr) + nstr, _, ok := strings.Cut(scr, "-") + if !ok { + return false + } + n, err := strconv.ParseInt(nstr, 10, 32) + if err != nil { + return false + } + return int(n) > ver.SchemaVersion + } + for _, script := range migrationScripts { + if err := db.runScripts(script, filter); err != nil { + return nil, wrap(err) + } + } + } + + // Set the current schema version, if not already set + if err := db.setAppliedSchemaVersion(currentSchemaVersion); err != nil { + return nil, wrap(err) + } + + db.tplInput = map[string]any{ + "FlagLocalUnsupported": protocol.FlagLocalUnsupported, + "FlagLocalIgnored": protocol.FlagLocalIgnored, + "FlagLocalMustRescan": protocol.FlagLocalMustRescan, + "FlagLocalReceiveOnly": protocol.FlagLocalReceiveOnly, + "FlagLocalGlobal": protocol.FlagLocalGlobal, + "FlagLocalNeeded": protocol.FlagLocalNeeded, + "SyncthingVersion": build.LongVersion, + } + + return db, nil +} + +func (s *baseDB) Close() error { + s.updateLock.Lock() + s.statementsMut.Lock() + defer s.updateLock.Unlock() + defer s.statementsMut.Unlock() + for _, stmt := range s.statements { + stmt.Close() + } + return wrap(s.sql.Close()) +} + +var tplFuncs = template.FuncMap{ + "or": func(vs ...int) int { + v := vs[0] + for _, ov := range vs[1:] { + v |= ov + } + return v + }, +} + +// stmt returns a prepared statement for the given SQL string, after +// applying local template expansions. The statement is cached. +func (s *baseDB) stmt(tpl string) stmt { + tpl = strings.TrimSpace(tpl) + + // Fast concurrent lookup of cached statement + s.statementsMut.RLock() + stmt, ok := s.statements[tpl] + s.statementsMut.RUnlock() + if ok { + return stmt + } + + // On miss, take the full lock, check again + s.statementsMut.Lock() + defer s.statementsMut.Unlock() + stmt, ok = s.statements[tpl] + if ok { + return stmt + } + + // Apply template expansions + var sb strings.Builder + compTpl := template.Must(template.New("tpl").Funcs(tplFuncs).Parse(tpl)) + if err := compTpl.Execute(&sb, s.tplInput); err != nil { + panic("bug: bad template: " + err.Error()) + } + + // Prepare and cache + stmt, err := s.sql.Preparex(sb.String()) + if err != nil { + return failedStmt{err} + } + s.statements[tpl] = stmt + return stmt +} + +type stmt interface { + Exec(args ...any) (sql.Result, error) + Get(dest any, args ...any) error + Queryx(args ...any) (*sqlx.Rows, error) + Select(dest any, args ...any) error +} + +type failedStmt struct { + err error +} + +func (f failedStmt) Exec(_ ...any) (sql.Result, error) { return nil, f.err } +func (f failedStmt) Get(_ any, _ ...any) error { return f.err } +func (f failedStmt) Queryx(_ ...any) (*sqlx.Rows, error) { return nil, f.err } +func (f failedStmt) Select(_ any, _ ...any) error { return f.err } + +func (s *baseDB) runScripts(glob string, filter ...func(s string) bool) error { + scripts, err := fs.Glob(embedded, glob) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.Begin() + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + +nextScript: + for _, scr := range scripts { + for _, fn := range filter { + if !fn(scr) { + l.Debugln(s.baseName, "skipping script", scr) + continue nextScript + } + } + l.Debugln(s.baseName, "executing script", scr) + bs, err := fs.ReadFile(embedded, scr) + if err != nil { + return wrap(err, scr) + } + // SQLite requires one statement per exec, so we split the init + // files on lines containing only a semicolon and execute them + // separately. We require it on a separate line because there are + // also statement-internal semicolons in the triggers. + for _, stmt := range strings.Split(string(bs), "\n;") { + if _, err := tx.Exec(stmt); err != nil { + return wrap(err, stmt) + } + } + } + + return wrap(tx.Commit()) +} + +type schemaVersion struct { + SchemaVersion int + AppliedAt int64 + SyncthingVersion string +} + +func (s *schemaVersion) AppliedTime() time.Time { + return time.Unix(0, s.AppliedAt) +} + +func (s *baseDB) setAppliedSchemaVersion(ver int) error { + _, err := s.stmt(` + INSERT OR IGNORE INTO schemamigrations (schema_version, applied_at, syncthing_version) + VALUES (?, ?, ?) + `).Exec(ver, time.Now().UnixNano(), build.LongVersion) + return wrap(err) +} + +func (s *baseDB) getAppliedSchemaVersion() (schemaVersion, error) { + var v schemaVersion + err := s.stmt(` + SELECT schema_version as schemaversion, applied_at as appliedat, syncthing_version as syncthingversion FROM schemamigrations + ORDER BY schema_version DESC + LIMIT 1 + `).Get(&v) + return v, wrap(err) +} diff --git a/internal/db/sqlite/db.go b/internal/db/sqlite/db.go deleted file mode 100644 index a0a6a5f1f..000000000 --- a/internal/db/sqlite/db.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. - -package sqlite - -import ( - "sync" - "time" - - "github.com/jmoiron/sqlx" - "github.com/syncthing/syncthing/internal/db" - "github.com/syncthing/syncthing/lib/protocol" - "github.com/thejerf/suture/v4" -) - -type DB struct { - sql *sqlx.DB - localDeviceIdx int64 - deleteRetention time.Duration - - updateLock sync.Mutex - updatePoints int - - statementsMut sync.RWMutex - statements map[string]*sqlx.Stmt - tplInput map[string]any -} - -var _ db.DB = (*DB)(nil) - -type Option func(*DB) - -func WithDeleteRetention(d time.Duration) Option { - return func(s *DB) { - s.deleteRetention = d - } -} - -func (s *DB) Close() error { - s.updateLock.Lock() - s.statementsMut.Lock() - defer s.updateLock.Unlock() - defer s.statementsMut.Unlock() - for _, stmt := range s.statements { - stmt.Close() - } - return wrap(s.sql.Close()) -} - -func (s *DB) Service(maintenanceInterval time.Duration) suture.Service { - return newService(s, maintenanceInterval) -} - -func (s *DB) ListFolders() ([]string, error) { - var res []string - err := s.stmt(` - SELECT folder_id FROM folders - ORDER BY folder_id - `).Select(&res) - return res, wrap(err) -} - -func (s *DB) ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) { - var res []string - err := s.stmt(` - SELECT d.device_id FROM counts s - INNER JOIN folders o ON o.idx = s.folder_idx - INNER JOIN devices d ON d.idx = s.device_idx - WHERE o.folder_id = ? AND s.count > 0 AND s.device_idx != {{.LocalDeviceIdx}} - GROUP BY d.device_id - ORDER BY d.device_id - `).Select(&res, folder) - if err != nil { - return nil, wrap(err) - } - - devs := make([]protocol.DeviceID, len(res)) - for i, s := range res { - devs[i], err = protocol.DeviceIDFromString(s) - if err != nil { - return nil, wrap(err) - } - } - return devs, nil -} diff --git a/internal/db/sqlite/db_folderdb.go b/internal/db/sqlite/db_folderdb.go new file mode 100644 index 000000000..1af061a28 --- /dev/null +++ b/internal/db/sqlite/db_folderdb.go @@ -0,0 +1,391 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "database/sql" + "errors" + "fmt" + "iter" + "path/filepath" + "time" + + "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" +) + +var errNoSuchFolder = errors.New("no such folder") + +func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) { + // Check for an already open database + s.folderDBsMut.RLock() + fdb, ok := s.folderDBs[folder] + s.folderDBsMut.RUnlock() + if ok { + return fdb, nil + } + + // Check for an existing folder. If we're not supposed to create the + // folder, we don't move on if it doesn't already have an ID. + var idx int64 + if err := s.stmt(` + SELECT idx FROM folders + WHERE folder_id = ? + `).Get(&idx, folder); err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, wrap(err) + } + if idx == 0 && !create { + return nil, errNoSuchFolder + } + + // Create a folder ID and database if it does not already exist + s.folderDBsMut.Lock() + defer s.folderDBsMut.Unlock() + if fdb, ok := s.folderDBs[folder]; ok { + return fdb, nil + } + + var err error + if idx == 0 { + // First time we want to access this folder, need to create a new + // folder ID + idx, err = s.folderIdxLocked(folder) + if err != nil { + return nil, err + } + } + + name := fmt.Sprintf("folder.%04x.db", idx) + path := filepath.Join(s.pathBase, name) + fdb, err = s.folderDBOpener(folder, path, s.deleteRetention) + if err != nil { + return nil, err + } + s.folderDBs[folder] = fdb + return fdb, nil +} + +func (s *DB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error { + fdb, err := s.getFolderDB(folder, true) + if err != nil { + return err + } + return fdb.Update(device, fs) +} + +func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return protocol.FileInfo{}, false, nil + } + if err != nil { + return protocol.FileInfo{}, false, err + } + return fdb.GetDeviceFile(device, file) +} + +func (s *DB) GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return nil, nil + } + if err != nil { + return nil, err + } + return fdb.GetGlobalAvailability(file) +} + +func (s *DB) GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return protocol.FileInfo{}, false, nil + } + if err != nil { + return protocol.FileInfo{}, false, err + } + return fdb.GetGlobalFile(file) +} + +func (s *DB) AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(db.FileMetadata) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(db.FileMetadata) bool) {}, func() error { return err } + } + return fdb.AllGlobalFiles() +} + +func (s *DB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[db.FileMetadata], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(db.FileMetadata) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(db.FileMetadata) bool) {}, func() error { return err } + } + return fdb.AllGlobalFilesPrefix(prefix) +} + +func (s *DB) AllLocalBlocksWithHash(hash []byte) ([]db.BlockMapEntry, error) { + var entries []db.BlockMapEntry + err := s.forEachFolder(func(fdb *folderDB) error { + es, err := itererr.Collect(fdb.AllLocalBlocksWithHash(hash)) + entries = append(entries, es...) + return err + }) + return entries, err +} + +func (s *DB) AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]db.FileMetadata, error) { + res := make(map[string][]db.FileMetadata) + err := s.forEachFolder(func(fdb *folderDB) error { + files, err := itererr.Collect(fdb.AllLocalFilesWithBlocksHash(hash)) + res[fdb.folderID] = files + return err + }) + return res, err +} + +func (s *DB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return err } + } + return fdb.AllLocalFiles(device) +} + +func (s *DB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return err } + } + return fdb.AllLocalFilesBySequence(device, startSeq, limit) +} + +func (s *DB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return err } + } + return fdb.AllLocalFilesWithPrefix(device, prefix) +} + +func (s *DB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[db.FileMetadata], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(db.FileMetadata) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(db.FileMetadata) bool) {}, func() error { return err } + } + return fdb.AllLocalFilesWithBlocksHash(h) +} + +func (s *DB) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(protocol.FileInfo) bool) {}, func() error { return err } + } + return fdb.AllNeededGlobalFiles(device, order, limit, offset) +} + +func (s *DB) DropAllFiles(folder string, device protocol.DeviceID) error { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return nil + } + if err != nil { + return err + } + return fdb.DropAllFiles(device) +} + +func (s *DB) DropFilesNamed(folder string, device protocol.DeviceID, names []string) error { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return nil + } + if err != nil { + return err + } + return fdb.DropFilesNamed(device, names) +} + +func (s *DB) ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return nil, nil + } + if err != nil { + return nil, err + } + return fdb.ListDevicesForFolder() +} + +func (s *DB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return nil, nil + } + if err != nil { + return nil, err + } + return fdb.RemoteSequences() +} + +func (s *DB) CountGlobal(folder string) (db.Counts, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return db.Counts{}, nil + } + if err != nil { + return db.Counts{}, err + } + return fdb.CountGlobal() +} + +func (s *DB) CountLocal(folder string, device protocol.DeviceID) (db.Counts, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return db.Counts{}, nil + } + if err != nil { + return db.Counts{}, err + } + return fdb.CountLocal(device) +} + +func (s *DB) CountNeed(folder string, device protocol.DeviceID) (db.Counts, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return db.Counts{}, nil + } + if err != nil { + return db.Counts{}, err + } + return fdb.CountNeed(device) +} + +func (s *DB) CountReceiveOnlyChanged(folder string) (db.Counts, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return db.Counts{}, nil + } + if err != nil { + return db.Counts{}, err + } + return fdb.CountReceiveOnlyChanged() +} + +func (s *DB) DropAllIndexIDs() error { + return s.forEachFolder(func(fdb *folderDB) error { + return fdb.DropAllIndexIDs() + }) +} + +func (s *DB) GetIndexID(folder string, device protocol.DeviceID) (protocol.IndexID, error) { + fdb, err := s.getFolderDB(folder, true) + if err != nil { + return 0, err + } + return fdb.GetIndexID(device) +} + +func (s *DB) SetIndexID(folder string, device protocol.DeviceID, id protocol.IndexID) error { + fdb, err := s.getFolderDB(folder, true) + if err != nil { + return err + } + return fdb.SetIndexID(device, id) +} + +func (s *DB) GetDeviceSequence(folder string, device protocol.DeviceID) (int64, error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return 0, nil + } + if err != nil { + return 0, err + } + return fdb.GetDeviceSequence(device) +} + +func (s *DB) DeleteMtime(folder, name string) error { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return nil + } + if err != nil { + return err + } + return fdb.DeleteMtime(name) +} + +func (s *DB) GetMtime(folder, name string) (ondisk, virtual time.Time) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return time.Time{}, time.Time{} + } + if err != nil { + return time.Time{}, time.Time{} + } + return fdb.GetMtime(name) +} + +func (s *DB) PutMtime(folder, name string, ondisk, virtual time.Time) error { + fdb, err := s.getFolderDB(folder, true) + if err != nil { + return err + } + return fdb.PutMtime(name, ondisk, virtual) +} + +func (s *DB) DropDevice(device protocol.DeviceID) error { + return s.forEachFolder(func(fdb *folderDB) error { + return fdb.DropDevice(device) + }) +} + +// forEachFolder runs the function for each currently open folderDB, +// returning the first error that was encountered. +func (s *DB) forEachFolder(fn func(fdb *folderDB) error) error { + folders, err := s.ListFolders() + if err != nil { + return err + } + + var firstError error + for _, folder := range folders { + fdb, err := s.getFolderDB(folder, false) + if err != nil { + if firstError == nil { + firstError = err + } + continue + } + if err := fn(fdb); err != nil && firstError == nil { + firstError = err + } + } + return firstError +} diff --git a/internal/db/sqlite/db_kv.go b/internal/db/sqlite/db_kv.go index 077ad3609..8e81d7d72 100644 --- a/internal/db/sqlite/db_kv.go +++ b/internal/db/sqlite/db_kv.go @@ -13,7 +13,7 @@ import ( "github.com/syncthing/syncthing/internal/db" ) -func (s *DB) GetKV(key string) ([]byte, error) { +func (s *baseDB) GetKV(key string) ([]byte, error) { var val []byte if err := s.stmt(` SELECT value FROM kv @@ -24,7 +24,7 @@ func (s *DB) GetKV(key string) ([]byte, error) { return val, nil } -func (s *DB) PutKV(key string, val []byte) error { +func (s *baseDB) PutKV(key string, val []byte) error { s.updateLock.Lock() defer s.updateLock.Unlock() _, err := s.stmt(` @@ -34,7 +34,7 @@ func (s *DB) PutKV(key string, val []byte) error { return wrap(err) } -func (s *DB) DeleteKV(key string) error { +func (s *baseDB) DeleteKV(key string) error { s.updateLock.Lock() defer s.updateLock.Unlock() _, err := s.stmt(` @@ -43,7 +43,7 @@ func (s *DB) DeleteKV(key string) error { return wrap(err) } -func (s *DB) PrefixKV(prefix string) (iter.Seq[db.KeyValue], func() error) { +func (s *baseDB) PrefixKV(prefix string) (iter.Seq[db.KeyValue], func() error) { var rows *sqlx.Rows var err error if prefix == "" { diff --git a/internal/db/sqlite/db_local_test.go b/internal/db/sqlite/db_local_test.go index 8444a07d8..b1e98954e 100644 --- a/internal/db/sqlite/db_local_test.go +++ b/internal/db/sqlite/db_local_test.go @@ -9,8 +9,6 @@ package sqlite import ( "testing" - "github.com/syncthing/syncthing/internal/db" - "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/protocol" ) @@ -52,7 +50,7 @@ func TestBlocks(t *testing.T) { // Search for blocks - vals, err := itererr.Collect(db.AllLocalBlocksWithHash([]byte{1, 2, 3})) + vals, err := db.AllLocalBlocksWithHash([]byte{1, 2, 3}) if err != nil { t.Fatal(err) } @@ -66,27 +64,23 @@ func TestBlocks(t *testing.T) { // Get FileInfos for those blocks - found := 0 - it, errFn := db.AllLocalFilesWithBlocksHashAnyFolder(vals[0].BlocklistHash) - for folder, fileInfo := range it { - if folder != folderID { - t.Fatal("should be same folder") - } - if fileInfo.Name != "file1" { - t.Fatal("should be file1") - } - found++ - } - if err := errFn(); err != nil { + res, err := db.AllLocalFilesWithBlocksHashAnyFolder(vals[0].BlocklistHash) + if err != nil { t.Fatal(err) } - if found != 1 { + if len(res) != 1 { + t.Fatal("should return one folder") + } + if len(res[folderID]) != 1 { t.Fatal("should find one file") } + if res[folderID][0].Name != "file1" { + t.Fatal("should be file1") + } // Get the other blocks - vals, err = itererr.Collect(db.AllLocalBlocksWithHash([]byte{3, 4, 5})) + vals, err = db.AllLocalBlocksWithHash([]byte{3, 4, 5}) if err != nil { t.Fatal(err) } @@ -125,7 +119,10 @@ func TestBlocksDeleted(t *testing.T) { // We should find one entry for the block hash search := file.Blocks[0].Hash - es := mustCollect[db.BlockMapEntry](t)(sdb.AllLocalBlocksWithHash(search)) + es, err := sdb.AllLocalBlocksWithHash(search) + if err != nil { + t.Fatal(err) + } if len(es) != 1 { t.Fatal("expected one hit") } @@ -137,13 +134,17 @@ func TestBlocksDeleted(t *testing.T) { } // Searching for the old hash should yield no hits - if hits := mustCollect[db.BlockMapEntry](t)(sdb.AllLocalBlocksWithHash(search)); len(hits) != 0 { + if hits, err := sdb.AllLocalBlocksWithHash(search); err != nil { + t.Fatal(err) + } else if len(hits) != 0 { t.Log(hits) t.Error("expected no hits") } // Searching for the new hash should yield one hits - if hits := mustCollect[db.BlockMapEntry](t)(sdb.AllLocalBlocksWithHash(file.Blocks[0].Hash)); len(hits) != 1 { + if hits, err := sdb.AllLocalBlocksWithHash(file.Blocks[0].Hash); err != nil { + t.Fatal(err) + } else if len(hits) != 1 { t.Log(hits) t.Error("expected one hit") } diff --git a/internal/db/sqlite/db_mtimes.go b/internal/db/sqlite/db_mtimes.go deleted file mode 100644 index 1c3dc7765..000000000 --- a/internal/db/sqlite/db_mtimes.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. - -package sqlite - -import ( - "time" -) - -func (s *DB) GetMtime(folder, name string) (ondisk, virtual time.Time) { - var res struct { - Ondisk int64 - Virtual int64 - } - if err := s.stmt(` - SELECT m.ondisk, m.virtual FROM mtimes m - INNER JOIN folders o ON o.idx = m.folder_idx - WHERE o.folder_id = ? AND m.name = ? - `).Get(&res, folder, name); err != nil { - return time.Time{}, time.Time{} - } - return time.Unix(0, res.Ondisk), time.Unix(0, res.Virtual) -} - -func (s *DB) PutMtime(folder, name string, ondisk, virtual time.Time) error { - s.updateLock.Lock() - defer s.updateLock.Unlock() - folderIdx, err := s.folderIdxLocked(folder) - if err != nil { - return wrap(err) - } - _, err = s.stmt(` - INSERT OR REPLACE INTO mtimes (folder_idx, name, ondisk, virtual) - VALUES (?, ?, ?, ?) - `).Exec(folderIdx, name, ondisk.UnixNano(), virtual.UnixNano()) - return wrap(err) -} - -func (s *DB) DeleteMtime(folder, name string) error { - s.updateLock.Lock() - defer s.updateLock.Unlock() - folderIdx, err := s.folderIdxLocked(folder) - if err != nil { - return wrap(err) - } - _, err = s.stmt(` - DELETE FROM mtimes - WHERE folder_idx = ? AND name = ? - `).Exec(folderIdx, name) - return wrap(err) -} diff --git a/internal/db/sqlite/db_open.go b/internal/db/sqlite/db_open.go index b7d6346b7..45c3547a0 100644 --- a/internal/db/sqlite/db_open.go +++ b/internal/db/sqlite/db_open.go @@ -7,57 +7,104 @@ package sqlite import ( - "database/sql" "os" "path/filepath" - "strconv" - "strings" - "text/template" + "sync" + "time" - "github.com/jmoiron/sqlx" - "github.com/syncthing/syncthing/lib/build" - "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/internal/db" ) -const maxDBConns = 128 +const maxDBConns = 16 + +type DB struct { + pathBase string + deleteRetention time.Duration + + *baseDB + + folderDBsMut sync.RWMutex + folderDBs map[string]*folderDB + folderDBOpener func(folder, path string, deleteRetention time.Duration) (*folderDB, error) +} + +var _ db.DB = (*DB)(nil) + +type Option func(*DB) + +func WithDeleteRetention(d time.Duration) Option { + return func(s *DB) { + s.deleteRetention = d + } +} func Open(path string, opts ...Option) (*DB, error) { - // Open the database with options to enable foreign keys and recursive - // triggers (needed for the delete+insert triggers on row replace). - sqlDB, err := sqlx.Open(dbDriver, "file:"+path+"?"+commonOptions) + pragmas := []string{ + "journal_mode = WAL", + "optimize = 0x10002", + "auto_vacuum = INCREMENTAL", + "default_temp_store = MEMORY", + "temp_store = MEMORY", + } + schemas := []string{ + "sql/schema/common/*", + "sql/schema/main/*", + } + + os.MkdirAll(path, 0o700) + mainPath := filepath.Join(path, "main.db") + mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, nil) if err != nil { - return nil, wrap(err) + return nil, err } - sqlDB.SetMaxOpenConns(maxDBConns) - if _, err := sqlDB.Exec(`PRAGMA journal_mode = WAL`); err != nil { - return nil, wrap(err, "PRAGMA journal_mode") + + db := &DB{ + pathBase: path, + baseDB: mainBase, + folderDBs: make(map[string]*folderDB), + folderDBOpener: openFolderDB, } - if _, err := sqlDB.Exec(`PRAGMA optimize = 0x10002`); err != nil { - // https://www.sqlite.org/pragma.html#pragma_optimize - return nil, wrap(err, "PRAGMA optimize") - } - return openCommon(sqlDB, opts...) + + return db, nil } // Open the database with options suitable for the migration inserts. This // is not a safe mode of operation for normal processing, use only for bulk // inserts with a close afterwards. func OpenForMigration(path string) (*DB, error) { - sqlDB, err := sqlx.Open(dbDriver, "file:"+path+"?"+commonOptions) + pragmas := []string{ + "journal_mode = OFF", + "default_temp_store = MEMORY", + "temp_store = MEMORY", + "foreign_keys = 0", + "synchronous = 0", + "locking_mode = EXCLUSIVE", + } + schemas := []string{ + "sql/schema/common/*", + "sql/schema/main/*", + } + + os.MkdirAll(path, 0o700) + mainPath := filepath.Join(path, "main.db") + mainBase, err := openBase(mainPath, 1, pragmas, schemas, nil) if err != nil { - return nil, wrap(err, "open") + return nil, err } - sqlDB.SetMaxOpenConns(1) - if _, err := sqlDB.Exec(`PRAGMA foreign_keys = 0`); err != nil { - return nil, wrap(err, "PRAGMA foreign_keys") + + db := &DB{ + pathBase: path, + baseDB: mainBase, + folderDBs: make(map[string]*folderDB), + folderDBOpener: openFolderDBForMigration, } - if _, err := sqlDB.Exec(`PRAGMA journal_mode = OFF`); err != nil { - return nil, wrap(err, "PRAGMA journal_mode") - } - if _, err := sqlDB.Exec(`PRAGMA synchronous = 0`); err != nil { - return nil, wrap(err, "PRAGMA synchronous") - } - return openCommon(sqlDB) + + // // Touch device IDs that should always exist and have a low index + // // numbers, and will never change + // db.localDeviceIdx, _ = db.deviceIdxLocked(protocol.LocalDeviceID) + // db.tplInput["LocalDeviceIdx"] = db.localDeviceIdx + + return db, nil } func OpenTemp() (*DB, error) { @@ -73,134 +120,12 @@ func OpenTemp() (*DB, error) { return Open(path) } -func openCommon(sqlDB *sqlx.DB, opts ...Option) (*DB, error) { - if _, err := sqlDB.Exec(`PRAGMA auto_vacuum = INCREMENTAL`); err != nil { - return nil, wrap(err, "PRAGMA auto_vacuum") +func (s *DB) Close() error { + s.folderDBsMut.Lock() + defer s.folderDBsMut.Unlock() + for folder, fdb := range s.folderDBs { + fdb.Close() + delete(s.folderDBs, folder) } - if _, err := sqlDB.Exec(`PRAGMA default_temp_store = MEMORY`); err != nil { - return nil, wrap(err, "PRAGMA default_temp_store") - } - if _, err := sqlDB.Exec(`PRAGMA temp_store = MEMORY`); err != nil { - return nil, wrap(err, "PRAGMA temp_store") - } - - db := &DB{ - sql: sqlDB, - deleteRetention: defaultDeleteRetention, - statements: make(map[string]*sqlx.Stmt), - } - for _, opt := range opts { - opt(db) - } - if db.deleteRetention > 0 && db.deleteRetention < minDeleteRetention { - db.deleteRetention = minDeleteRetention - } - - if err := db.runScripts("sql/schema/*"); err != nil { - return nil, wrap(err) - } - - ver, _ := db.getAppliedSchemaVersion() - if ver.SchemaVersion > 0 { - filter := func(scr string) bool { - scr = filepath.Base(scr) - nstr, _, ok := strings.Cut(scr, "-") - if !ok { - return false - } - n, err := strconv.ParseInt(nstr, 10, 32) - if err != nil { - return false - } - return int(n) > ver.SchemaVersion - } - if err := db.runScripts("sql/migrations/*", filter); err != nil { - return nil, wrap(err) - } - } - - // Touch device IDs that should always exist and have a low index - // numbers, and will never change - db.localDeviceIdx, _ = db.deviceIdxLocked(protocol.LocalDeviceID) - - // Set the current schema version, if not already set - if err := db.setAppliedSchemaVersion(currentSchemaVersion); err != nil { - return nil, wrap(err) - } - - db.tplInput = map[string]any{ - "FlagLocalUnsupported": protocol.FlagLocalUnsupported, - "FlagLocalIgnored": protocol.FlagLocalIgnored, - "FlagLocalMustRescan": protocol.FlagLocalMustRescan, - "FlagLocalReceiveOnly": protocol.FlagLocalReceiveOnly, - "FlagLocalGlobal": protocol.FlagLocalGlobal, - "FlagLocalNeeded": protocol.FlagLocalNeeded, - "LocalDeviceIdx": db.localDeviceIdx, - "SyncthingVersion": build.LongVersion, - } - - return db, nil + return wrap(s.baseDB.Close()) } - -var tplFuncs = template.FuncMap{ - "or": func(vs ...int) int { - v := vs[0] - for _, ov := range vs[1:] { - v |= ov - } - return v - }, -} - -// stmt returns a prepared statement for the given SQL string, after -// applying local template expansions. The statement is cached. -func (s *DB) stmt(tpl string) stmt { - tpl = strings.TrimSpace(tpl) - - // Fast concurrent lookup of cached statement - s.statementsMut.RLock() - stmt, ok := s.statements[tpl] - s.statementsMut.RUnlock() - if ok { - return stmt - } - - // On miss, take the full lock, check again - s.statementsMut.Lock() - defer s.statementsMut.Unlock() - stmt, ok = s.statements[tpl] - if ok { - return stmt - } - - // Apply template expansions - var sb strings.Builder - compTpl := template.Must(template.New("tpl").Funcs(tplFuncs).Parse(tpl)) - if err := compTpl.Execute(&sb, s.tplInput); err != nil { - panic("bug: bad template: " + err.Error()) - } - - // Prepare and cache - stmt, err := s.sql.Preparex(sb.String()) - if err != nil { - return failedStmt{err} - } - s.statements[tpl] = stmt - return stmt -} - -type stmt interface { - Exec(args ...any) (sql.Result, error) - Get(dest any, args ...any) error - Queryx(args ...any) (*sqlx.Rows, error) - Select(dest any, args ...any) error -} - -type failedStmt struct { - err error -} - -func (f failedStmt) Exec(_ ...any) (sql.Result, error) { return nil, f.err } -func (f failedStmt) Get(_ any, _ ...any) error { return f.err } -func (f failedStmt) Queryx(_ ...any) (*sqlx.Rows, error) { return nil, f.err } -func (f failedStmt) Select(_ any, _ ...any) error { return f.err } diff --git a/internal/db/sqlite/db_schema.go b/internal/db/sqlite/db_schema.go deleted file mode 100644 index 89cebd7ac..000000000 --- a/internal/db/sqlite/db_schema.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. - -package sqlite - -import ( - "embed" - "io/fs" - "strings" - "time" - - "github.com/syncthing/syncthing/lib/build" -) - -const currentSchemaVersion = 1 - -//go:embed sql/** -var embedded embed.FS - -func (s *DB) runScripts(glob string, filter ...func(s string) bool) error { - scripts, err := fs.Glob(embedded, glob) - if err != nil { - return wrap(err) - } - - tx, err := s.sql.Begin() - if err != nil { - return wrap(err) - } - defer tx.Rollback() //nolint:errcheck - -nextScript: - for _, scr := range scripts { - for _, fn := range filter { - if !fn(scr) { - l.Debugln("Skipping script", scr) - continue nextScript - } - } - l.Debugln("Executing script", scr) - bs, err := fs.ReadFile(embedded, scr) - if err != nil { - return wrap(err, scr) - } - // SQLite requires one statement per exec, so we split the init - // files on lines containing only a semicolon and execute them - // separately. We require it on a separate line because there are - // also statement-internal semicolons in the triggers. - for _, stmt := range strings.Split(string(bs), "\n;") { - if _, err := tx.Exec(stmt); err != nil { - return wrap(err, stmt) - } - } - } - - return wrap(tx.Commit()) -} - -type schemaVersion struct { - SchemaVersion int - AppliedAt int64 - SyncthingVersion string -} - -func (s *schemaVersion) AppliedTime() time.Time { - return time.Unix(0, s.AppliedAt) -} - -func (s *DB) setAppliedSchemaVersion(ver int) error { - _, err := s.stmt(` - INSERT OR IGNORE INTO schemamigrations (schema_version, applied_at, syncthing_version) - VALUES (?, ?, ?) - `).Exec(ver, time.Now().UnixNano(), build.LongVersion) - return wrap(err) -} - -func (s *DB) getAppliedSchemaVersion() (schemaVersion, error) { - var v schemaVersion - err := s.stmt(` - SELECT schema_version as schemaversion, applied_at as appliedat, syncthing_version as syncthingversion FROM schemamigrations - ORDER BY schema_version DESC - LIMIT 1 - `).Get(&v) - return v, wrap(err) -} diff --git a/internal/db/sqlite/db_service.go b/internal/db/sqlite/db_service.go index c31acb19a..c28a6586c 100644 --- a/internal/db/sqlite/db_service.go +++ b/internal/db/sqlite/db_service.go @@ -11,7 +11,9 @@ import ( "fmt" "time" + "github.com/jmoiron/sqlx" "github.com/syncthing/syncthing/internal/db" + "github.com/thejerf/suture/v4" ) const ( @@ -21,6 +23,10 @@ const ( minDeleteRetention = 24 * time.Hour ) +func (s *DB) Service(maintenanceInterval time.Duration) suture.Service { + return newService(s, maintenanceInterval) +} + type Service struct { sdb *DB maintenanceInterval time.Duration @@ -80,14 +86,25 @@ func (s *Service) periodic(ctx context.Context) error { t1 := time.Now() defer func() { l.Debugln("Periodic done in", time.Since(t1), "+", t1.Sub(t0)) }() - if err := s.garbageCollectOldDeletedLocked(); err != nil { - return wrap(err) - } - if err := s.garbageCollectBlocklistsAndBlocksLocked(ctx); err != nil { - return wrap(err) - } + tidy(ctx, s.sdb.sql) - conn, err := s.sdb.sql.Conn(ctx) + return wrap(s.sdb.forEachFolder(func(fdb *folderDB) error { + fdb.updateLock.Lock() + defer fdb.updateLock.Unlock() + + if err := garbageCollectOldDeletedLocked(fdb); err != nil { + return wrap(err) + } + if err := garbageCollectBlocklistsAndBlocksLocked(ctx, fdb); err != nil { + return wrap(err) + } + tidy(ctx, fdb.sql) + return nil + })) +} + +func tidy(ctx context.Context, db *sqlx.DB) error { + conn, err := db.Conn(ctx) if err != nil { return wrap(err) } @@ -95,35 +112,34 @@ func (s *Service) periodic(ctx context.Context) error { _, _ = conn.ExecContext(ctx, `ANALYZE`) _, _ = conn.ExecContext(ctx, `PRAGMA optimize`) _, _ = conn.ExecContext(ctx, `PRAGMA incremental_vacuum`) - _, _ = conn.ExecContext(ctx, `PRAGMA journal_size_limit = 67108864`) + _, _ = conn.ExecContext(ctx, `PRAGMA journal_size_limit = 8388608`) _, _ = conn.ExecContext(ctx, `PRAGMA wal_checkpoint(TRUNCATE)`) - return nil } -func (s *Service) garbageCollectOldDeletedLocked() error { - if s.sdb.deleteRetention <= 0 { - l.Debugln("Delete retention is infinite, skipping cleanup") +func garbageCollectOldDeletedLocked(fdb *folderDB) error { + if fdb.deleteRetention <= 0 { + l.Debugln(fdb.baseName, "delete retention is infinite, skipping cleanup") return nil } // Remove deleted files that are marked as not needed (we have processed // them) and they were deleted more than MaxDeletedFileAge ago. - l.Debugln("Forgetting deleted files older than", s.sdb.deleteRetention) - res, err := s.sdb.stmt(` + l.Debugln(fdb.baseName, "forgetting deleted files older than", fdb.deleteRetention) + res, err := fdb.stmt(` DELETE FROM files WHERE deleted AND modified < ? AND local_flags & {{.FlagLocalNeeded}} == 0 - `).Exec(time.Now().Add(-s.sdb.deleteRetention).UnixNano()) + `).Exec(time.Now().Add(-fdb.deleteRetention).UnixNano()) if err != nil { return wrap(err) } if aff, err := res.RowsAffected(); err == nil { - l.Debugln("Removed old deleted file records:", aff) + l.Debugln(fdb.baseName, "removed old deleted file records:", aff) } return nil } -func (s *Service) garbageCollectBlocklistsAndBlocksLocked(ctx context.Context) error { +func garbageCollectBlocklistsAndBlocksLocked(ctx context.Context, fdb *folderDB) error { // Remove all blocklists not referred to by any files and, by extension, // any blocks not referred to by a blocklist. This is an expensive // operation when run normally, especially if there are a lot of blocks @@ -134,7 +150,7 @@ func (s *Service) garbageCollectBlocklistsAndBlocksLocked(ctx context.Context) e // an explicit connection and disabling foreign keys before starting the // transaction. We make sure to clean up on the way out. - conn, err := s.sdb.sql.Connx(ctx) + conn, err := fdb.sql.Connx(ctx) if err != nil { return wrap(err) } @@ -161,7 +177,7 @@ func (s *Service) garbageCollectBlocklistsAndBlocksLocked(ctx context.Context) e return wrap(err, "delete blocklists") } else if shouldDebug() { rows, err := res.RowsAffected() - l.Debugln("Blocklist GC:", rows, err) + l.Debugln(fdb.baseName, "blocklist GC:", rows, err) } if res, err := tx.ExecContext(ctx, ` @@ -172,7 +188,7 @@ func (s *Service) garbageCollectBlocklistsAndBlocksLocked(ctx context.Context) e return wrap(err, "delete blocks") } else if shouldDebug() { rows, err := res.RowsAffected() - l.Debugln("Blocks GC:", rows, err) + l.Debugln(fdb.baseName, "blocks GC:", rows, err) } return wrap(tx.Commit()) diff --git a/internal/db/sqlite/db_test.go b/internal/db/sqlite/db_test.go index 2fb7a6721..c6e2528e9 100644 --- a/internal/db/sqlite/db_test.go +++ b/internal/db/sqlite/db_test.go @@ -298,6 +298,7 @@ func TestBasics(t *testing.T) { t.Fatal(err) } if len(folders) != 1 || folders[0] != folderID { + t.Log(folders) t.Error("expected one folder") } }) @@ -1009,15 +1010,20 @@ func TestBlocklistGarbageCollection(t *testing.T) { // There should exist three blockslists and six blocks + fdb, err := sdb.getFolderDB(folderID, false) + if err != nil { + t.Fatal(err) + } + var count int - if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil { + if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil { t.Fatal(err) } if count != 3 { t.Log(count) t.Fatal("expected 3 blocklists") } - if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil { + if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil { t.Fatal(err) } if count != 6 { @@ -1039,14 +1045,14 @@ func TestBlocklistGarbageCollection(t *testing.T) { // There should exist two blockslists and four blocks - if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil { + if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocklists`); err != nil { t.Fatal(err) } if count != 2 { t.Log(count) t.Error("expected 2 blocklists") } - if err := sdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil { + if err := fdb.sql.Get(&count, `SELECT count(*) FROM blocks`); err != nil { t.Fatal(err) } if count != 3 { @@ -1078,7 +1084,7 @@ func TestInsertLargeFile(t *testing.T) { // Verify all the blocks are here for i, block := range files[0].Blocks { - bs, err := itererr.Collect(sdb.AllLocalBlocksWithHash(block.Hash)) + bs, err := sdb.AllLocalBlocksWithHash(block.Hash) if err != nil { t.Fatal(err) } diff --git a/internal/db/sqlite/db_update.go b/internal/db/sqlite/db_update.go index 7b8dd9905..df4e82892 100644 --- a/internal/db/sqlite/db_update.go +++ b/internal/db/sqlite/db_update.go @@ -7,477 +7,38 @@ package sqlite import ( - "cmp" - "context" "fmt" + "os" "runtime" - "slices" "strings" - - "github.com/jmoiron/sqlx" - "github.com/syncthing/syncthing/internal/gen/dbproto" - "github.com/syncthing/syncthing/internal/itererr" - "github.com/syncthing/syncthing/lib/osutil" - "github.com/syncthing/syncthing/lib/protocol" - "github.com/syncthing/syncthing/lib/sliceutil" - "google.golang.org/protobuf/proto" ) -const ( - // Arbitrarily chosen values for checkpoint frequency.... - updatePointsPerFile = 100 - updatePointsPerBlock = 1 - updatePointsThreshold = 250_000 -) - -func (s *DB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error { - s.updateLock.Lock() - defer s.updateLock.Unlock() - - folderIdx, err := s.folderIdxLocked(folder) - if err != nil { - return wrap(err) - } - deviceIdx, err := s.deviceIdxLocked(device) - if err != nil { - return wrap(err) - } - - tx, err := s.sql.BeginTxx(context.Background(), nil) - if err != nil { - return wrap(err) - } - defer tx.Rollback() //nolint:errcheck - txp := &txPreparedStmts{Tx: tx} - - //nolint:sqlclosecheck - insertFileStmt, err := txp.Preparex(` - INSERT OR REPLACE INTO files (folder_idx, device_idx, remote_sequence, name, type, modified, size, version, deleted, invalid, local_flags, blocklist_hash) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING sequence - `) - if err != nil { - return wrap(err, "prepare insert file") - } - - //nolint:sqlclosecheck - insertFileInfoStmt, err := txp.Preparex(` - INSERT INTO fileinfos (sequence, fiprotobuf) - VALUES (?, ?) - `) - if err != nil { - return wrap(err, "prepare insert fileinfo") - } - - //nolint:sqlclosecheck - insertBlockListStmt, err := txp.Preparex(` - INSERT OR IGNORE INTO blocklists (blocklist_hash, blprotobuf) - VALUES (?, ?) - `) - if err != nil { - return wrap(err, "prepare insert blocklist") - } - - var prevRemoteSeq int64 - for i, f := range fs { - f.Name = osutil.NormalizedFilename(f.Name) - - var blockshash *[]byte - if len(f.Blocks) > 0 { - f.BlocksHash = protocol.BlocksHash(f.Blocks) - blockshash = &f.BlocksHash - } else { - f.BlocksHash = nil - } - - if f.Type == protocol.FileInfoTypeDirectory { - f.Size = 128 // synthetic directory size - } - - // Insert the file. - // - // If it is a remote file, set remote_sequence otherwise leave it at - // null. Returns the new local sequence. - var remoteSeq *int64 - if device != protocol.LocalDeviceID { - if i > 0 && f.Sequence == prevRemoteSeq { - return fmt.Errorf("duplicate remote sequence number %d", prevRemoteSeq) - } - prevRemoteSeq = f.Sequence - remoteSeq = &f.Sequence - } - var localSeq int64 - if err := insertFileStmt.Get(&localSeq, folderIdx, deviceIdx, remoteSeq, f.Name, f.Type, f.ModTime().UnixNano(), f.Size, f.Version.String(), f.IsDeleted(), f.IsInvalid(), f.LocalFlags, blockshash); err != nil { - return wrap(err, "insert file") - } - - if len(f.Blocks) > 0 { - // Indirect the block list - blocks := sliceutil.Map(f.Blocks, protocol.BlockInfo.ToWire) - bs, err := proto.Marshal(&dbproto.BlockList{Blocks: blocks}) - if err != nil { - return wrap(err, "marshal blocklist") - } - if _, err := insertBlockListStmt.Exec(f.BlocksHash, bs); err != nil { - return wrap(err, "insert blocklist") - } - - if device == protocol.LocalDeviceID { - // Insert all blocks - if err := s.insertBlocksLocked(txp, f.BlocksHash, f.Blocks); err != nil { - return wrap(err, "insert blocks") - } - } - - f.Blocks = nil - } - - // Insert the fileinfo - if device == protocol.LocalDeviceID { - f.Sequence = localSeq - } - bs, err := proto.Marshal(f.ToWire(true)) - if err != nil { - return wrap(err, "marshal fileinfo") - } - if _, err := insertFileInfoStmt.Exec(localSeq, bs); err != nil { - return wrap(err, "insert fileinfo") - } - - // Update global and need - if err := s.recalcGlobalForFile(txp, folderIdx, f.Name); err != nil { - return wrap(err) - } - } - - if err := tx.Commit(); err != nil { - return wrap(err) - } - - s.periodicCheckpointLocked(fs) - return nil -} - func (s *DB) DropFolder(folder string) error { + s.folderDBsMut.Lock() + defer s.folderDBsMut.Unlock() s.updateLock.Lock() defer s.updateLock.Unlock() _, err := s.stmt(` DELETE FROM folders WHERE folder_id = ? `).Exec(folder) + if fdb, ok := s.folderDBs[folder]; ok { + fdb.Close() + _ = os.Remove(fdb.path) + _ = os.Remove(fdb.path + "-wal") + _ = os.Remove(fdb.path + "-shm") + delete(s.folderDBs, folder) + } return wrap(err) } -func (s *DB) DropDevice(device protocol.DeviceID) error { - if device == protocol.LocalDeviceID { - panic("bug: cannot drop local device") - } - - s.updateLock.Lock() - defer s.updateLock.Unlock() - - deviceIdx, err := s.deviceIdxLocked(device) - if err != nil { - return wrap(err) - } - - tx, err := s.sql.BeginTxx(context.Background(), nil) - if err != nil { - return wrap(err) - } - defer tx.Rollback() //nolint:errcheck - txp := &txPreparedStmts{Tx: tx} - - // Find all folders where the device is involved - var folderIdxs []int64 - if err := tx.Select(&folderIdxs, ` - SELECT folder_idx - FROM counts - WHERE device_idx = ? AND count > 0 - GROUP BY folder_idx - `, deviceIdx); err != nil { - return wrap(err) - } - - // Drop the device, which cascades to delete all files etc for it - if _, err := tx.Exec(`DELETE FROM devices WHERE device_id = ?`, device.String()); err != nil { - return wrap(err) - } - - // Recalc the globals for all affected folders - for _, idx := range folderIdxs { - if err := s.recalcGlobalForFolder(txp, idx); err != nil { - return wrap(err) - } - } - - return wrap(tx.Commit()) -} - -func (s *DB) DropAllFiles(folder string, device protocol.DeviceID) error { - s.updateLock.Lock() - defer s.updateLock.Unlock() - - // This is a two part operation, first dropping all the files and then - // recalculating the global state for the entire folder. - - folderIdx, err := s.folderIdxLocked(folder) - if err != nil { - return wrap(err) - } - deviceIdx, err := s.deviceIdxLocked(device) - if err != nil { - return wrap(err) - } - - tx, err := s.sql.BeginTxx(context.Background(), nil) - if err != nil { - return wrap(err) - } - defer tx.Rollback() //nolint:errcheck - txp := &txPreparedStmts{Tx: tx} - - // Drop all the file entries - - result, err := tx.Exec(` - DELETE FROM files - WHERE folder_idx = ? AND device_idx = ? - `, folderIdx, deviceIdx) - if err != nil { - return wrap(err) - } - if n, err := result.RowsAffected(); err == nil && n == 0 { - // The delete affected no rows, so we don't need to redo the entire - // global/need calculation. - return wrap(tx.Commit()) - } - - // Recalc global for the entire folder - - if err := s.recalcGlobalForFolder(txp, folderIdx); err != nil { - return wrap(err) - } - return wrap(tx.Commit()) -} - -func (s *DB) DropFilesNamed(folder string, device protocol.DeviceID, names []string) error { - for i := range names { - names[i] = osutil.NormalizedFilename(names[i]) - } - - s.updateLock.Lock() - defer s.updateLock.Unlock() - - folderIdx, err := s.folderIdxLocked(folder) - if err != nil { - return wrap(err) - } - deviceIdx, err := s.deviceIdxLocked(device) - if err != nil { - return wrap(err) - } - - tx, err := s.sql.BeginTxx(context.Background(), nil) - if err != nil { - return wrap(err) - } - defer tx.Rollback() //nolint:errcheck - txp := &txPreparedStmts{Tx: tx} - - // Drop the named files - - query, args, err := sqlx.In(` - DELETE FROM files - WHERE folder_idx = ? AND device_idx = ? AND name IN (?) - `, folderIdx, deviceIdx, names) - if err != nil { - return wrap(err) - } - if _, err := tx.Exec(query, args...); err != nil { - return wrap(err) - } - - // Recalc globals for the named files - - for _, name := range names { - if err := s.recalcGlobalForFile(txp, folderIdx, name); err != nil { - return wrap(err) - } - } - - return wrap(tx.Commit()) -} - -func (*DB) insertBlocksLocked(tx *txPreparedStmts, blocklistHash []byte, blocks []protocol.BlockInfo) error { - if len(blocks) == 0 { - return nil - } - bs := make([]map[string]any, len(blocks)) - for i, b := range blocks { - bs[i] = map[string]any{ - "hash": b.Hash, - "blocklist_hash": blocklistHash, - "idx": i, - "offset": b.Offset, - "size": b.Size, - } - } - - // Very large block lists (>8000 blocks) result in "too many variables" - // error. Chunk it to a reasonable size. - for chunk := range slices.Chunk(bs, 1000) { - if _, err := tx.NamedExec(` - INSERT OR IGNORE INTO blocks (hash, blocklist_hash, idx, offset, size) - VALUES (:hash, :blocklist_hash, :idx, :offset, :size) - `, chunk); err != nil { - return wrap(err) - } - } - return nil -} - -func (s *DB) recalcGlobalForFolder(txp *txPreparedStmts, folderIdx int64) error { - // Select files where there is no global, those are the ones we need to - // recalculate. - //nolint:sqlclosecheck - namesStmt, err := txp.Preparex(` - SELECT f.name FROM files f - WHERE f.folder_idx = ? AND NOT EXISTS ( - SELECT 1 FROM files g - WHERE g.folder_idx = ? AND g.name = f.name AND g.local_flags & ? != 0 - ) - GROUP BY name - `) - if err != nil { - return wrap(err) - } - rows, err := namesStmt.Queryx(folderIdx, folderIdx, protocol.FlagLocalGlobal) - if err != nil { - return wrap(err) - } - defer rows.Close() - for rows.Next() { - var name string - if err := rows.Scan(&name); err != nil { - return wrap(err) - } - if err := s.recalcGlobalForFile(txp, folderIdx, name); err != nil { - return wrap(err) - } - } - return wrap(rows.Err()) -} - -func (s *DB) recalcGlobalForFile(txp *txPreparedStmts, folderIdx int64, file string) error { - //nolint:sqlclosecheck - selStmt, err := txp.Preparex(` - SELECT name, folder_idx, device_idx, sequence, modified, version, deleted, invalid, local_flags FROM files - WHERE folder_idx = ? AND name = ? - `) - if err != nil { - return wrap(err) - } - es, err := itererr.Collect(iterStructs[fileRow](selStmt.Queryx(folderIdx, file))) - if err != nil { - return wrap(err) - } - if len(es) == 0 { - // shouldn't happen - return nil - } - - // Sort the entries; the global entry is at the head of the list - slices.SortFunc(es, fileRow.Compare) - - // The global version is the first one in the list that is not invalid, - // or just the first one in the list if all are invalid. - var global fileRow - globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.Invalid }) - if globIdx < 0 { - globIdx = 0 - } - global = es[globIdx] - - // We "have" the file if the position in the list of versions is at the - // global version or better, or if the version is the same as the global - // file (we might be further down the list due to invalid flags), or if - // the global is deleted and we don't have it at all... - localIdx := slices.IndexFunc(es, func(e fileRow) bool { return e.DeviceIdx == s.localDeviceIdx }) - hasLocal := localIdx >= 0 && localIdx <= globIdx || // have a better or equal version - localIdx >= 0 && es[localIdx].Version.Equal(global.Version.Vector) || // have an equal version but invalid/ignored - localIdx < 0 && global.Deleted // missing it, but the global is also deleted - - // Set the global flag on the global entry. Set the need flag if the - // local device needs this file, unless it's invalid. - global.LocalFlags |= protocol.FlagLocalGlobal - if hasLocal || global.Invalid { - global.LocalFlags &= ^protocol.FlagLocalNeeded - } else { - global.LocalFlags |= protocol.FlagLocalNeeded - } - //nolint:sqlclosecheck - upStmt, err := txp.Prepare(` - UPDATE files SET local_flags = ? - WHERE folder_idx = ? AND device_idx = ? AND sequence = ? - `) - if err != nil { - return wrap(err) - } - if _, err := upStmt.Exec(global.LocalFlags, global.FolderIdx, global.DeviceIdx, global.Sequence); err != nil { - return wrap(err) - } - - // Clear the need and global flags on all other entries - //nolint:sqlclosecheck - upStmt, err = txp.Prepare(` - UPDATE files SET local_flags = local_flags & ? - WHERE folder_idx = ? AND name = ? AND sequence != ? AND local_flags & ? != 0 - `) - if err != nil { - return wrap(err) - } - if _, err := upStmt.Exec(^(protocol.FlagLocalNeeded | protocol.FlagLocalGlobal), folderIdx, global.Name, global.Sequence, protocol.FlagLocalNeeded|protocol.FlagLocalGlobal); err != nil { - return wrap(err) - } - - return nil -} - -func (s *DB) folderIdxLocked(folderID string) (int64, error) { - if _, err := s.stmt(` - INSERT OR IGNORE INTO folders(folder_id) - VALUES (?) - `).Exec(folderID); err != nil { - return 0, wrap(err) - } - var idx int64 - if err := s.stmt(` - SELECT idx FROM folders - WHERE folder_id = ? - `).Get(&idx, folderID); err != nil { - return 0, wrap(err) - } - - return idx, nil -} - -func (s *DB) deviceIdxLocked(deviceID protocol.DeviceID) (int64, error) { - devStr := deviceID.String() - if _, err := s.stmt(` - INSERT OR IGNORE INTO devices(device_id) - VALUES (?) - `).Exec(devStr); err != nil { - return 0, wrap(err) - } - var idx int64 - if err := s.stmt(` - SELECT idx FROM devices - WHERE device_id = ? - `).Get(&idx, devStr); err != nil { - return 0, wrap(err) - } - - return idx, nil +func (s *DB) ListFolders() ([]string, error) { + var res []string + err := s.stmt(` + SELECT folder_id FROM folders + ORDER BY folder_id + `).Select(&res) + return res, wrap(err) } // wrap returns the error wrapped with the calling function name and @@ -507,94 +68,3 @@ func wrap(err error, context ...string) error { return fmt.Errorf("%s: %w", prefix, err) } - -type fileRow struct { - Name string - Version dbVector - FolderIdx int64 `db:"folder_idx"` - DeviceIdx int64 `db:"device_idx"` - Sequence int64 - Modified int64 - Size int64 - LocalFlags int64 `db:"local_flags"` - Deleted bool - Invalid bool -} - -func (e fileRow) Compare(other fileRow) int { - // From FileInfo.WinsConflict - vc := e.Version.Vector.Compare(other.Version.Vector) - switch vc { - case protocol.Equal: - if e.Invalid != other.Invalid { - if e.Invalid { - return 1 - } - return -1 - } - - // Compare the device ID index, lower is better. This is only - // deterministic to the extent that LocalDeviceID will always be the - // lowest one, order between remote devices is random (and - // irrelevant). - return cmp.Compare(e.DeviceIdx, other.DeviceIdx) - case protocol.Greater: // we are newer - return -1 - case protocol.Lesser: // we are older - return 1 - case protocol.ConcurrentGreater, protocol.ConcurrentLesser: // there is a conflict - if e.Invalid != other.Invalid { - if e.Invalid { // we are invalid, we lose - return 1 - } - return -1 // they are invalid, we win - } - if e.Deleted != other.Deleted { - if e.Deleted { // we are deleted, we lose - return 1 - } - return -1 // they are deleted, we win - } - if d := cmp.Compare(e.Modified, other.Modified); d != 0 { - return -d // positive d means we were newer, so we win (negative return) - } - if vc == protocol.ConcurrentGreater { - return -1 // we have a better device ID, we win - } - return 1 // they win - default: - return 0 - } -} - -func (s *DB) periodicCheckpointLocked(fs []protocol.FileInfo) { - // Induce periodic checkpoints. We add points for each file and block, - // and checkpoint when we've written more than a threshold of points. - // This ensures we do not go too long without a checkpoint, while also - // not doing it incessantly for every update. - s.updatePoints += updatePointsPerFile * len(fs) - for _, f := range fs { - s.updatePoints += len(f.Blocks) * updatePointsPerBlock - } - if s.updatePoints > updatePointsThreshold { - conn, err := s.sql.Conn(context.Background()) - if err != nil { - l.Debugln("conn:", err) - return - } - defer conn.Close() - if _, err := conn.ExecContext(context.Background(), `PRAGMA journal_size_limit = 67108864`); err != nil { - l.Debugln("PRAGMA journal_size_limit:", err) - } - row := conn.QueryRowContext(context.Background(), `PRAGMA wal_checkpoint(RESTART)`) - var res, modified, moved int - if row.Err() != nil { - l.Debugln("PRAGMA wal_checkpoint(RESTART):", err) - } else if err := row.Scan(&res, &modified, &moved); err != nil { - l.Debugln("PRAGMA wal_checkpoint(RESTART) (scan):", err) - } else { - l.Debugln("checkpoint at", s.updatePoints, "returned", res, modified, moved) - } - s.updatePoints = 0 - } -} diff --git a/internal/db/sqlite/db_counts.go b/internal/db/sqlite/folderdb_counts.go similarity index 60% rename from internal/db/sqlite/db_counts.go rename to internal/db/sqlite/folderdb_counts.go index 24700dd3f..0e019f15a 100644 --- a/internal/db/sqlite/db_counts.go +++ b/internal/db/sqlite/folderdb_counts.go @@ -19,95 +19,89 @@ type countsRow struct { LocalFlags int64 `db:"local_flags"` } -func (s *DB) CountLocal(folder string, device protocol.DeviceID) (db.Counts, error) { +func (s *folderDB) CountLocal(device protocol.DeviceID) (db.Counts, error) { var res []countsRow if err := s.stmt(` SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s - INNER JOIN folders o ON o.idx = s.folder_idx INNER JOIN devices d ON d.idx = s.device_idx - WHERE o.folder_id = ? AND d.device_id = ? AND s.local_flags & {{.FlagLocalIgnored}} = 0 - `).Select(&res, folder, device.String()); err != nil { + WHERE d.device_id = ? AND s.local_flags & {{.FlagLocalIgnored}} = 0 + `).Select(&res, device.String()); err != nil { return db.Counts{}, wrap(err) } return summarizeCounts(res), nil } -func (s *DB) CountNeed(folder string, device protocol.DeviceID) (db.Counts, error) { +func (s *folderDB) CountNeed(device protocol.DeviceID) (db.Counts, error) { if device == protocol.LocalDeviceID { - return s.needSizeLocal(folder) + return s.needSizeLocal() } - return s.needSizeRemote(folder, device) + return s.needSizeRemote(device) } -func (s *DB) CountGlobal(folder string) (db.Counts, error) { +func (s *folderDB) CountGlobal() (db.Counts, error) { // Exclude ignored and receive-only changed files from the global count // (legacy expectation? it's a bit weird since those files can in fact // be global and you can get them with GetGlobal etc.) var res []countsRow err := s.stmt(` SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s - INNER JOIN folders o ON o.idx = s.folder_idx - WHERE o.folder_id = ? AND s.local_flags & {{.FlagLocalGlobal}} != 0 AND s.local_flags & {{or .FlagLocalReceiveOnly .FlagLocalIgnored}} = 0 - `).Select(&res, folder) + WHERE s.local_flags & {{.FlagLocalGlobal}} != 0 AND s.local_flags & {{or .FlagLocalReceiveOnly .FlagLocalIgnored}} = 0 + `).Select(&res) if err != nil { return db.Counts{}, wrap(err) } return summarizeCounts(res), nil } -func (s *DB) CountReceiveOnlyChanged(folder string) (db.Counts, error) { +func (s *folderDB) CountReceiveOnlyChanged() (db.Counts, error) { var res []countsRow err := s.stmt(` SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s - INNER JOIN folders o ON o.idx = s.folder_idx - WHERE o.folder_id = ? AND local_flags & {{.FlagLocalReceiveOnly}} != 0 - `).Select(&res, folder) + WHERE local_flags & {{.FlagLocalReceiveOnly}} != 0 + `).Select(&res) if err != nil { return db.Counts{}, wrap(err) } return summarizeCounts(res), nil } -func (s *DB) needSizeLocal(folder string) (db.Counts, error) { +func (s *folderDB) needSizeLocal() (db.Counts, error) { // The need size for the local device is the sum of entries with the // need bit set. var res []countsRow err := s.stmt(` SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s - INNER JOIN folders o ON o.idx = s.folder_idx - WHERE o.folder_id = ? AND s.local_flags & {{.FlagLocalNeeded}} != 0 - `).Select(&res, folder) + WHERE s.local_flags & {{.FlagLocalNeeded}} != 0 + `).Select(&res) if err != nil { return db.Counts{}, wrap(err) } return summarizeCounts(res), nil } -func (s *DB) needSizeRemote(folder string, device protocol.DeviceID) (db.Counts, error) { +func (s *folderDB) needSizeRemote(device protocol.DeviceID) (db.Counts, error) { var res []countsRow // See neededGlobalFilesRemote for commentary as that is the same query without summing if err := s.stmt(` SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g - INNER JOIN folders o ON o.idx = g.folder_idx - WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND f.version = g.version AND f.folder_idx = g.folder_idx AND d.device_id = ? + WHERE f.name = g.name AND f.version = g.version AND d.device_id = ? ) GROUP BY g.type, g.local_flags, g.deleted UNION ALL SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g - INNER JOIN folders o ON o.idx = g.folder_idx - WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND f.folder_idx = g.folder_idx AND d.device_id = ? AND NOT f.deleted + WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted ) GROUP BY g.type, g.local_flags, g.deleted - `).Select(&res, folder, device.String(), - folder, device.String()); err != nil { + `).Select(&res, device.String(), + device.String()); err != nil { return db.Counts{}, wrap(err) } diff --git a/internal/db/sqlite/db_global.go b/internal/db/sqlite/folderdb_global.go similarity index 63% rename from internal/db/sqlite/db_global.go rename to internal/db/sqlite/folderdb_global.go index 2ce7a0c93..3fc71d53c 100644 --- a/internal/db/sqlite/db_global.go +++ b/internal/db/sqlite/folderdb_global.go @@ -19,7 +19,7 @@ import ( "github.com/syncthing/syncthing/lib/protocol" ) -func (s *DB) GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) { +func (s *folderDB) GetGlobalFile(file string) (protocol.FileInfo, bool, error) { file = osutil.NormalizedFilename(file) var ind indirectFI @@ -27,9 +27,8 @@ func (s *DB) GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi INNER JOIN files f on fi.sequence = f.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash - INNER JOIN folders o ON o.idx = f.folder_idx - WHERE o.folder_id = ? AND f.name = ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 - `).Get(&ind, folder, file) + WHERE f.name = ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 + `).Get(&ind, file) if errors.Is(err, sql.ErrNoRows) { return protocol.FileInfo{}, false, nil } @@ -43,18 +42,17 @@ func (s *DB) GetGlobalFile(folder string, file string) (protocol.FileInfo, bool, return fi, true, nil } -func (s *DB) GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error) { +func (s *folderDB) GetGlobalAvailability(file string) ([]protocol.DeviceID, error) { file = osutil.NormalizedFilename(file) var devStrs []string err := s.stmt(` SELECT d.device_id FROM files f INNER JOIN devices d ON d.idx = f.device_idx - INNER JOIN folders o ON o.idx = f.folder_idx - INNER JOIN files g ON f.folder_idx = g.folder_idx AND g.version = f.version AND g.name = f.name - WHERE o.folder_id = ? AND g.name = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND f.device_idx != {{.LocalDeviceIdx}} + INNER JOIN files g ON g.version = f.version AND g.name = f.name + WHERE g.name = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND f.device_idx != {{.LocalDeviceIdx}} ORDER BY d.device_id - `).Select(&devStrs, folder, file) + `).Select(&devStrs, file) if errors.Is(err, sql.ErrNoRows) { return nil, nil } @@ -74,22 +72,21 @@ func (s *DB) GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, er return devs, nil } -func (s *DB) AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) { +func (s *folderDB) AllGlobalFiles() (iter.Seq[db.FileMetadata], func() error) { it, errFn := iterStructs[db.FileMetadata](s.stmt(` SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f - INNER JOIN folders o ON o.idx = f.folder_idx - WHERE o.folder_id = ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 + WHERE f.local_flags & {{.FlagLocalGlobal}} != 0 ORDER BY f.name - `).Queryx(folder)) + `).Queryx()) return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) { m.Name = osutil.NativeFilename(m.Name) return m, nil }) } -func (s *DB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[db.FileMetadata], func() error) { +func (s *folderDB) AllGlobalFilesPrefix(prefix string) (iter.Seq[db.FileMetadata], func() error) { if prefix == "" { - return s.AllGlobalFiles(folder) + return s.AllGlobalFiles() } prefix = osutil.NormalizedFilename(prefix) @@ -97,17 +94,16 @@ func (s *DB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[db.Fil it, errFn := iterStructs[db.FileMetadata](s.stmt(` SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f - INNER JOIN folders o ON o.idx = f.folder_idx - WHERE o.folder_id = ? AND f.name >= ? AND f.name < ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 + WHERE f.name >= ? AND f.name < ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 ORDER BY f.name - `).Queryx(folder, prefix, end)) + `).Queryx(prefix, end)) return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) { m.Name = osutil.NativeFilename(m.Name) return m, nil }) } -func (s *DB) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) { +func (s *folderDB) AllNeededGlobalFiles(device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) { var selectOpts string switch order { case config.PullOrderRandom: @@ -132,25 +128,24 @@ func (s *DB) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order } if device == protocol.LocalDeviceID { - return s.neededGlobalFilesLocal(folder, selectOpts) + return s.neededGlobalFilesLocal(selectOpts) } - return s.neededGlobalFilesRemote(folder, device, selectOpts) + return s.neededGlobalFilesRemote(device, selectOpts) } -func (s *DB) neededGlobalFilesLocal(folder, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) { +func (s *folderDB) neededGlobalFilesLocal(selectOpts string) (iter.Seq[protocol.FileInfo], func() error) { // Select all the non-ignored files with the need bit set. it, errFn := iterStructs[indirectFI](s.stmt(` SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi INNER JOIN files g on fi.sequence = g.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash - INNER JOIN folders o ON o.idx = g.folder_idx - WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalIgnored}} = 0 AND g.local_flags & {{.FlagLocalNeeded}} != 0 - ` + selectOpts).Queryx(folder)) + WHERE g.local_flags & {{.FlagLocalIgnored}} = 0 AND g.local_flags & {{.FlagLocalNeeded}} != 0 + ` + selectOpts).Queryx()) return itererr.Map(it, errFn, indirectFI.FileInfo) } -func (s *DB) neededGlobalFilesRemote(folder string, device protocol.DeviceID, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) { +func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) { // Select: // // - all the valid, non-deleted global files that don't have a corresponding @@ -163,11 +158,10 @@ func (s *DB) neededGlobalFilesRemote(folder string, device protocol.DeviceID, se SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi INNER JOIN files g on fi.sequence = g.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash - INNER JOIN folders o ON o.idx = g.folder_idx - WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND f.version = g.version AND f.folder_idx = g.folder_idx AND d.device_id = ? + WHERE f.name = g.name AND f.version = g.version AND d.device_id = ? ) UNION ALL @@ -175,15 +169,14 @@ func (s *DB) neededGlobalFilesRemote(folder string, device protocol.DeviceID, se SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi INNER JOIN files g on fi.sequence = g.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash - INNER JOIN folders o ON o.idx = g.folder_idx - WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND f.folder_idx = g.folder_idx AND d.device_id = ? AND NOT f.deleted + WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted ) `+selectOpts).Queryx( - folder, device.String(), - folder, device.String(), + device.String(), + device.String(), )) return itererr.Map(it, errFn, indirectFI.FileInfo) } diff --git a/internal/db/sqlite/db_indexid.go b/internal/db/sqlite/folderdb_indexid.go similarity index 64% rename from internal/db/sqlite/db_indexid.go rename to internal/db/sqlite/folderdb_indexid.go index e8f3fa1ab..1a4f693f5 100644 --- a/internal/db/sqlite/db_indexid.go +++ b/internal/db/sqlite/folderdb_indexid.go @@ -16,16 +16,15 @@ import ( "github.com/syncthing/syncthing/lib/protocol" ) -func (s *DB) GetIndexID(folder string, device protocol.DeviceID) (protocol.IndexID, error) { +func (s *folderDB) GetIndexID(device protocol.DeviceID) (protocol.IndexID, error) { // Try a fast read-only query to begin with. If it does not find the ID // we'll do the full thing under a lock. var indexID string if err := s.stmt(` SELECT i.index_id FROM indexids i - INNER JOIN folders o ON o.idx = i.folder_idx INNER JOIN devices d ON d.idx = i.device_idx - WHERE o.folder_id = ? AND d.device_id = ? - `).Get(&indexID, folder, device.String()); err == nil && indexID != "" { + WHERE d.device_id = ? + `).Get(&indexID, device.String()); err == nil && indexID != "" { idx, err := indexIDFromHex(indexID) return idx, wrap(err, "select") } @@ -40,14 +39,9 @@ func (s *DB) GetIndexID(folder string, device protocol.DeviceID) (protocol.Index // We are now operating only for the local device ID - folderIdx, err := s.folderIdxLocked(folder) - if err != nil { - return 0, wrap(err) - } - if err := s.stmt(` - SELECT index_id FROM indexids WHERE folder_idx = ? AND device_idx = {{.LocalDeviceIdx}} - `).Get(&indexID, folderIdx); err != nil && !errors.Is(err, sql.ErrNoRows) { + SELECT index_id FROM indexids WHERE device_idx = {{.LocalDeviceIdx}} + `).Get(&indexID); err != nil && !errors.Is(err, sql.ErrNoRows) { return 0, wrap(err, "select local") } @@ -57,11 +51,11 @@ func (s *DB) GetIndexID(folder string, device protocol.DeviceID) (protocol.Index // any. id := protocol.NewIndexID() if _, err := s.stmt(` - INSERT INTO indexids (folder_idx, device_idx, index_id, sequence) - SELECT ?, {{.LocalDeviceIdx}}, ?, COALESCE(MAX(sequence), 0) FROM files - WHERE folder_idx = ? AND device_idx = {{.LocalDeviceIdx}} + INSERT INTO indexids (device_idx, index_id, sequence) + SELECT {{.LocalDeviceIdx}}, ?, COALESCE(MAX(sequence), 0) FROM files + WHERE device_idx = {{.LocalDeviceIdx}} ON CONFLICT DO UPDATE SET index_id = ? - `).Exec(folderIdx, indexIDToHex(id), folderIdx, indexIDToHex(id)); err != nil { + `).Exec(indexIDToHex(id), indexIDToHex(id)); err != nil { return 0, wrap(err, "insert") } return id, nil @@ -70,42 +64,37 @@ func (s *DB) GetIndexID(folder string, device protocol.DeviceID) (protocol.Index return indexIDFromHex(indexID) } -func (s *DB) SetIndexID(folder string, device protocol.DeviceID, id protocol.IndexID) error { +func (s *folderDB) SetIndexID(device protocol.DeviceID, id protocol.IndexID) error { s.updateLock.Lock() defer s.updateLock.Unlock() - folderIdx, err := s.folderIdxLocked(folder) - if err != nil { - return wrap(err, "folder idx") - } deviceIdx, err := s.deviceIdxLocked(device) if err != nil { return wrap(err, "device idx") } if _, err := s.stmt(` - INSERT OR REPLACE INTO indexids (folder_idx, device_idx, index_id, sequence) values (?, ?, ?, 0) - `).Exec(folderIdx, deviceIdx, indexIDToHex(id)); err != nil { + INSERT OR REPLACE INTO indexids (device_idx, index_id, sequence) values (?, ?, 0) + `).Exec(deviceIdx, indexIDToHex(id)); err != nil { return wrap(err, "insert") } return nil } -func (s *DB) DropAllIndexIDs() error { +func (s *folderDB) DropAllIndexIDs() error { s.updateLock.Lock() defer s.updateLock.Unlock() _, err := s.stmt(`DELETE FROM indexids`).Exec() return wrap(err) } -func (s *DB) GetDeviceSequence(folder string, device protocol.DeviceID) (int64, error) { +func (s *folderDB) GetDeviceSequence(device protocol.DeviceID) (int64, error) { var res sql.NullInt64 err := s.stmt(` SELECT sequence FROM indexids i - INNER JOIN folders o ON o.idx = i.folder_idx INNER JOIN devices d ON d.idx = i.device_idx - WHERE o.folder_id = ? AND d.device_id = ? - `).Get(&res, folder, device.String()) + WHERE d.device_id = ? + `).Get(&res, device.String()) if errors.Is(err, sql.ErrNoRows) { return 0, nil } @@ -118,7 +107,7 @@ func (s *DB) GetDeviceSequence(folder string, device protocol.DeviceID) (int64, return res.Int64, nil } -func (s *DB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) { +func (s *folderDB) RemoteSequences() (map[protocol.DeviceID]int64, error) { type row struct { Device string Seq int64 @@ -126,10 +115,9 @@ func (s *DB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) it, errFn := iterStructs[row](s.stmt(` SELECT d.device_id AS device, i.sequence AS seq FROM indexids i - INNER JOIN folders o ON o.idx = i.folder_idx INNER JOIN devices d ON d.idx = i.device_idx - WHERE o.folder_id = ? AND i.device_idx != {{.LocalDeviceIdx}} - `).Queryx(folder)) + WHERE i.device_idx != {{.LocalDeviceIdx}} + `).Queryx()) res := make(map[protocol.DeviceID]int64) for row, err := range itererr.Zip(it, errFn) { diff --git a/internal/db/sqlite/db_local.go b/internal/db/sqlite/folderdb_local.go similarity index 59% rename from internal/db/sqlite/db_local.go rename to internal/db/sqlite/folderdb_local.go index 4b4064757..58d35fb05 100644 --- a/internal/db/sqlite/db_local.go +++ b/internal/db/sqlite/folderdb_local.go @@ -18,7 +18,7 @@ import ( "github.com/syncthing/syncthing/lib/protocol" ) -func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) { +func (s *folderDB) GetDeviceFile(device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) { file = osutil.NormalizedFilename(file) var ind indirectFI @@ -27,9 +27,8 @@ func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) INNER JOIN files f on fi.sequence = f.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash INNER JOIN devices d ON f.device_idx = d.idx - INNER JOIN folders o ON f.folder_idx = o.idx - WHERE o.folder_id = ? AND d.device_id = ? AND f.name = ? - `).Get(&ind, folder, device.String(), file) + WHERE d.device_id = ? AND f.name = ? + `).Get(&ind, device.String(), file) if errors.Is(err, sql.ErrNoRows) { return protocol.FileInfo{}, false, nil } @@ -43,19 +42,18 @@ func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) return fi, true, nil } -func (s *DB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { +func (s *folderDB) AllLocalFiles(device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { it, errFn := iterStructs[indirectFI](s.stmt(` SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi INNER JOIN files f on fi.sequence = f.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash - INNER JOIN folders o ON o.idx = f.folder_idx INNER JOIN devices d ON d.idx = f.device_idx - WHERE o.folder_id = ? AND d.device_id = ? - `).Queryx(folder, device.String())) + WHERE d.device_id = ? + `).Queryx(device.String())) return itererr.Map(it, errFn, indirectFI.FileInfo) } -func (s *DB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) { +func (s *folderDB) AllLocalFilesBySequence(device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) { var limitStr string if limit > 0 { limitStr = fmt.Sprintf(" LIMIT %d", limit) @@ -64,17 +62,16 @@ func (s *DB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, st SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi INNER JOIN files f on fi.sequence = f.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash - INNER JOIN folders o ON o.idx = f.folder_idx INNER JOIN devices d ON d.idx = f.device_idx - WHERE o.folder_id = ? AND d.device_id = ? AND f.sequence >= ? + WHERE d.device_id = ? AND f.sequence >= ? ORDER BY f.sequence`+limitStr).Queryx( - folder, device.String(), startSeq)) + device.String(), startSeq)) return itererr.Map(it, errFn, indirectFI.FileInfo) } -func (s *DB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) { +func (s *folderDB) AllLocalFilesWithPrefix(device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) { if prefix == "" { - return s.AllLocalFiles(folder, device) + return s.AllLocalFiles(device) } prefix = osutil.NormalizedFilename(prefix) @@ -84,37 +81,20 @@ func (s *DB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, pr SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi INNER JOIN files f on fi.sequence = f.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash - INNER JOIN folders o ON o.idx = f.folder_idx INNER JOIN devices d ON d.idx = f.device_idx - WHERE o.folder_id = ? AND d.device_id = ? AND f.name >= ? AND f.name < ? - `, folder, device.String(), prefix, end)) + WHERE d.device_id = ? AND f.name >= ? AND f.name < ? + `, device.String(), prefix, end)) return itererr.Map(it, errFn, indirectFI.FileInfo) } -func (s *DB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[db.FileMetadata], func() error) { +func (s *folderDB) AllLocalFilesWithBlocksHash(h []byte) (iter.Seq[db.FileMetadata], func() error) { return iterStructs[db.FileMetadata](s.stmt(` SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f - INNER JOIN folders o ON o.idx = f.folder_idx - WHERE o.folder_id = ? AND f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ? - `).Queryx(folder, h)) -} - -func (s *DB) AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, db.FileMetadata], func() error) { - type row struct { - FolderID string `db:"folder_id"` - db.FileMetadata - } - it, errFn := iterStructs[row](s.stmt(` - SELECT o.folder_id, f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f - INNER JOIN folders o ON o.idx = f.folder_idx WHERE f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ? `).Queryx(h)) - return itererr.Map2(it, errFn, func(r row) (string, db.FileMetadata, error) { - return r.FolderID, r.FileMetadata, nil - }) } -func (s *DB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntry], func() error) { +func (s *folderDB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntry], func() error) { // We involve the files table in this select because deletion of blocks // & blocklists is deferred (garbage collected) while the files list is // not. This filters out blocks that are in fact deleted. @@ -124,3 +104,25 @@ func (s *DB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntry], fu WHERE f.device_idx = {{.LocalDeviceIdx}} AND b.hash = ? `).Queryx(hash)) } + +func (s *folderDB) ListDevicesForFolder() ([]protocol.DeviceID, error) { + var res []string + err := s.stmt(` + SELECT DISTINCT d.device_id FROM counts s + INNER JOIN devices d ON d.idx = s.device_idx + WHERE s.count > 0 AND s.device_idx != {{.LocalDeviceIdx}} + ORDER BY d.device_id + `).Select(&res) + if err != nil { + return nil, wrap(err) + } + + devs := make([]protocol.DeviceID, len(res)) + for i, s := range res { + devs[i], err = protocol.DeviceIDFromString(s) + if err != nil { + return nil, wrap(err) + } + } + return devs, nil +} diff --git a/internal/db/sqlite/folderdb_mtimes.go b/internal/db/sqlite/folderdb_mtimes.go new file mode 100644 index 000000000..051c933ad --- /dev/null +++ b/internal/db/sqlite/folderdb_mtimes.go @@ -0,0 +1,45 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "time" +) + +func (s *folderDB) GetMtime(name string) (ondisk, virtual time.Time) { + var res struct { + Ondisk int64 + Virtual int64 + } + if err := s.stmt(` + SELECT m.ondisk, m.virtual FROM mtimes m + WHERE m.name = ? + `).Get(&res, name); err != nil { + return time.Time{}, time.Time{} + } + return time.Unix(0, res.Ondisk), time.Unix(0, res.Virtual) +} + +func (s *folderDB) PutMtime(name string, ondisk, virtual time.Time) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + _, err := s.stmt(` + INSERT OR REPLACE INTO mtimes (name, ondisk, virtual) + VALUES (?, ?, ?) + `).Exec(name, ondisk.UnixNano(), virtual.UnixNano()) + return wrap(err) +} + +func (s *folderDB) DeleteMtime(name string) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + _, err := s.stmt(` + DELETE FROM mtimes + WHERE name = ? + `).Exec(name) + return wrap(err) +} diff --git a/internal/db/sqlite/folderdb_open.go b/internal/db/sqlite/folderdb_open.go new file mode 100644 index 000000000..f0b96bd28 --- /dev/null +++ b/internal/db/sqlite/folderdb_open.go @@ -0,0 +1,110 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "time" + + "github.com/syncthing/syncthing/lib/protocol" +) + +type folderDB struct { + folderID string + *baseDB + + localDeviceIdx int64 + deleteRetention time.Duration +} + +func openFolderDB(folder, path string, deleteRetention time.Duration) (*folderDB, error) { + pragmas := []string{ + "journal_mode = WAL", + "optimize = 0x10002", + "auto_vacuum = INCREMENTAL", + "default_temp_store = MEMORY", + "temp_store = MEMORY", + } + schemas := []string{ + "sql/schema/common/*", + "sql/schema/folder/*", + } + + base, err := openBase(path, maxDBConns, pragmas, schemas, nil) + if err != nil { + return nil, err + } + + fdb := &folderDB{ + folderID: folder, + baseDB: base, + deleteRetention: deleteRetention, + } + + _ = fdb.PutKV("folderID", []byte(folder)) + + // Touch device IDs that should always exist and have a low index + // numbers, and will never change + fdb.localDeviceIdx, _ = fdb.deviceIdxLocked(protocol.LocalDeviceID) + fdb.tplInput["LocalDeviceIdx"] = fdb.localDeviceIdx + + return fdb, nil +} + +// Open the database with options suitable for the migration inserts. This +// is not a safe mode of operation for normal processing, use only for bulk +// inserts with a close afterwards. +func openFolderDBForMigration(folder, path string, deleteRetention time.Duration) (*folderDB, error) { + pragmas := []string{ + "journal_mode = OFF", + "default_temp_store = MEMORY", + "temp_store = MEMORY", + "foreign_keys = 0", + "synchronous = 0", + "locking_mode = EXCLUSIVE", + } + schemas := []string{ + "sql/schema/common/*", + "sql/schema/folder/*", + } + + base, err := openBase(path, 1, pragmas, schemas, nil) + if err != nil { + return nil, err + } + + fdb := &folderDB{ + folderID: folder, + baseDB: base, + deleteRetention: deleteRetention, + } + + // Touch device IDs that should always exist and have a low index + // numbers, and will never change + fdb.localDeviceIdx, _ = fdb.deviceIdxLocked(protocol.LocalDeviceID) + fdb.tplInput["LocalDeviceIdx"] = fdb.localDeviceIdx + + return fdb, nil +} + +func (s *folderDB) deviceIdxLocked(deviceID protocol.DeviceID) (int64, error) { + devStr := deviceID.String() + if _, err := s.stmt(` + INSERT OR IGNORE INTO devices(device_id) + VALUES (?) + `).Exec(devStr); err != nil { + return 0, wrap(err) + } + var idx int64 + if err := s.stmt(` + SELECT idx FROM devices + WHERE device_id = ? + `).Get(&idx, devStr); err != nil { + return 0, wrap(err) + } + + return idx, nil +} diff --git a/internal/db/sqlite/folderdb_update.go b/internal/db/sqlite/folderdb_update.go new file mode 100644 index 000000000..8376b3b92 --- /dev/null +++ b/internal/db/sqlite/folderdb_update.go @@ -0,0 +1,531 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "cmp" + "context" + "fmt" + "slices" + + "github.com/jmoiron/sqlx" + "github.com/syncthing/syncthing/internal/gen/dbproto" + "github.com/syncthing/syncthing/internal/itererr" + "github.com/syncthing/syncthing/lib/osutil" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/sliceutil" + "google.golang.org/protobuf/proto" +) + +const ( + // Arbitrarily chosen values for checkpoint frequency.... + updatePointsPerFile = 100 + updatePointsPerBlock = 1 + updatePointsThreshold = 250_000 +) + +func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + //nolint:sqlclosecheck + insertFileStmt, err := txp.Preparex(` + INSERT OR REPLACE INTO files (device_idx, remote_sequence, name, type, modified, size, version, deleted, invalid, local_flags, blocklist_hash) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + RETURNING sequence + `) + if err != nil { + return wrap(err, "prepare insert file") + } + + //nolint:sqlclosecheck + insertFileInfoStmt, err := txp.Preparex(` + INSERT INTO fileinfos (sequence, fiprotobuf) + VALUES (?, ?) + `) + if err != nil { + return wrap(err, "prepare insert fileinfo") + } + + //nolint:sqlclosecheck + insertBlockListStmt, err := txp.Preparex(` + INSERT OR IGNORE INTO blocklists (blocklist_hash, blprotobuf) + VALUES (?, ?) + `) + if err != nil { + return wrap(err, "prepare insert blocklist") + } + + var prevRemoteSeq int64 + for i, f := range fs { + f.Name = osutil.NormalizedFilename(f.Name) + + var blockshash *[]byte + if len(f.Blocks) > 0 { + f.BlocksHash = protocol.BlocksHash(f.Blocks) + blockshash = &f.BlocksHash + } else { + f.BlocksHash = nil + } + + if f.Type == protocol.FileInfoTypeDirectory { + f.Size = 128 // synthetic directory size + } + + // Insert the file. + // + // If it is a remote file, set remote_sequence otherwise leave it at + // null. Returns the new local sequence. + var remoteSeq *int64 + if device != protocol.LocalDeviceID { + if i > 0 && f.Sequence == prevRemoteSeq { + return fmt.Errorf("duplicate remote sequence number %d", prevRemoteSeq) + } + prevRemoteSeq = f.Sequence + remoteSeq = &f.Sequence + } + var localSeq int64 + if err := insertFileStmt.Get(&localSeq, deviceIdx, remoteSeq, f.Name, f.Type, f.ModTime().UnixNano(), f.Size, f.Version.String(), f.IsDeleted(), f.IsInvalid(), f.LocalFlags, blockshash); err != nil { + return wrap(err, "insert file") + } + + if len(f.Blocks) > 0 { + // Indirect the block list + blocks := sliceutil.Map(f.Blocks, protocol.BlockInfo.ToWire) + bs, err := proto.Marshal(&dbproto.BlockList{Blocks: blocks}) + if err != nil { + return wrap(err, "marshal blocklist") + } + if _, err := insertBlockListStmt.Exec(f.BlocksHash, bs); err != nil { + return wrap(err, "insert blocklist") + } + + if device == protocol.LocalDeviceID { + // Insert all blocks + if err := s.insertBlocksLocked(txp, f.BlocksHash, f.Blocks); err != nil { + return wrap(err, "insert blocks") + } + } + + f.Blocks = nil + } + + // Insert the fileinfo + if device == protocol.LocalDeviceID { + f.Sequence = localSeq + } + bs, err := proto.Marshal(f.ToWire(true)) + if err != nil { + return wrap(err, "marshal fileinfo") + } + if _, err := insertFileInfoStmt.Exec(localSeq, bs); err != nil { + return wrap(err, "insert fileinfo") + } + + // Update global and need + if err := s.recalcGlobalForFile(txp, f.Name); err != nil { + return wrap(err) + } + } + + if err := tx.Commit(); err != nil { + return wrap(err) + } + + s.periodicCheckpointLocked(fs) + return nil +} + +func (s *folderDB) DropDevice(device protocol.DeviceID) error { + if device == protocol.LocalDeviceID { + panic("bug: cannot drop local device") + } + + s.updateLock.Lock() + defer s.updateLock.Unlock() + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + // Drop the device, which cascades to delete all files etc for it + if _, err := tx.Exec(`DELETE FROM devices WHERE device_id = ?`, device.String()); err != nil { + return wrap(err) + } + + // Recalc the globals for all affected folders + if err := s.recalcGlobalForFolder(txp); err != nil { + return wrap(err) + } + + return wrap(tx.Commit()) +} + +func (s *folderDB) DropAllFiles(device protocol.DeviceID) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + + // This is a two part operation, first dropping all the files and then + // recalculating the global state for the entire folder. + + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + // Drop all the file entries + + result, err := tx.Exec(` + DELETE FROM files + WHERE device_idx = ? + `, deviceIdx) + if err != nil { + return wrap(err) + } + if n, err := result.RowsAffected(); err == nil && n == 0 { + // The delete affected no rows, so we don't need to redo the entire + // global/need calculation. + return wrap(tx.Commit()) + } + + // Recalc global for the entire folder + + if err := s.recalcGlobalForFolder(txp); err != nil { + return wrap(err) + } + return wrap(tx.Commit()) +} + +func (s *folderDB) DropFilesNamed(device protocol.DeviceID, names []string) error { + for i := range names { + names[i] = osutil.NormalizedFilename(names[i]) + } + + s.updateLock.Lock() + defer s.updateLock.Unlock() + + deviceIdx, err := s.deviceIdxLocked(device) + if err != nil { + return wrap(err) + } + + tx, err := s.sql.BeginTxx(context.Background(), nil) + if err != nil { + return wrap(err) + } + defer tx.Rollback() //nolint:errcheck + txp := &txPreparedStmts{Tx: tx} + + // Drop the named files + + query, args, err := sqlx.In(` + DELETE FROM files + WHERE device_idx = ? AND name IN (?) + `, deviceIdx, names) + if err != nil { + return wrap(err) + } + if _, err := tx.Exec(query, args...); err != nil { + return wrap(err) + } + + // Recalc globals for the named files + + for _, name := range names { + if err := s.recalcGlobalForFile(txp, name); err != nil { + return wrap(err) + } + } + + return wrap(tx.Commit()) +} + +func (*folderDB) insertBlocksLocked(tx *txPreparedStmts, blocklistHash []byte, blocks []protocol.BlockInfo) error { + if len(blocks) == 0 { + return nil + } + bs := make([]map[string]any, len(blocks)) + for i, b := range blocks { + bs[i] = map[string]any{ + "hash": b.Hash, + "blocklist_hash": blocklistHash, + "idx": i, + "offset": b.Offset, + "size": b.Size, + } + } + + // Very large block lists (>8000 blocks) result in "too many variables" + // error. Chunk it to a reasonable size. + for chunk := range slices.Chunk(bs, 1000) { + if _, err := tx.NamedExec(` + INSERT OR IGNORE INTO blocks (hash, blocklist_hash, idx, offset, size) + VALUES (:hash, :blocklist_hash, :idx, :offset, :size) + `, chunk); err != nil { + return wrap(err) + } + } + return nil +} + +func (s *folderDB) recalcGlobalForFolder(txp *txPreparedStmts) error { + // Select files where there is no global, those are the ones we need to + // recalculate. + //nolint:sqlclosecheck + namesStmt, err := txp.Preparex(` + SELECT f.name FROM files f + WHERE NOT EXISTS ( + SELECT 1 FROM files g + WHERE g.name = f.name AND g.local_flags & ? != 0 + ) + GROUP BY name + `) + if err != nil { + return wrap(err) + } + rows, err := namesStmt.Queryx(protocol.FlagLocalGlobal) + if err != nil { + return wrap(err) + } + defer rows.Close() + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return wrap(err) + } + if err := s.recalcGlobalForFile(txp, name); err != nil { + return wrap(err) + } + } + return wrap(rows.Err()) +} + +func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error { + //nolint:sqlclosecheck + selStmt, err := txp.Preparex(` + SELECT name, device_idx, sequence, modified, version, deleted, invalid, local_flags FROM files + WHERE name = ? + `) + if err != nil { + return wrap(err) + } + es, err := itererr.Collect(iterStructs[fileRow](selStmt.Queryx(file))) + if err != nil { + return wrap(err) + } + if len(es) == 0 { + // shouldn't happen + return nil + } + + // Sort the entries; the global entry is at the head of the list + slices.SortFunc(es, fileRow.Compare) + + // The global version is the first one in the list that is not invalid, + // or just the first one in the list if all are invalid. + var global fileRow + globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.Invalid }) + if globIdx < 0 { + globIdx = 0 + } + global = es[globIdx] + + // We "have" the file if the position in the list of versions is at the + // global version or better, or if the version is the same as the global + // file (we might be further down the list due to invalid flags), or if + // the global is deleted and we don't have it at all... + localIdx := slices.IndexFunc(es, func(e fileRow) bool { return e.DeviceIdx == s.localDeviceIdx }) + hasLocal := localIdx >= 0 && localIdx <= globIdx || // have a better or equal version + localIdx >= 0 && es[localIdx].Version.Equal(global.Version.Vector) || // have an equal version but invalid/ignored + localIdx < 0 && global.Deleted // missing it, but the global is also deleted + + // Set the global flag on the global entry. Set the need flag if the + // local device needs this file, unless it's invalid. + global.LocalFlags |= protocol.FlagLocalGlobal + if hasLocal || global.Invalid { + global.LocalFlags &= ^protocol.FlagLocalNeeded + } else { + global.LocalFlags |= protocol.FlagLocalNeeded + } + //nolint:sqlclosecheck + upStmt, err := txp.Prepare(` + UPDATE files SET local_flags = ? + WHERE device_idx = ? AND sequence = ? + `) + if err != nil { + return wrap(err) + } + if _, err := upStmt.Exec(global.LocalFlags, global.DeviceIdx, global.Sequence); err != nil { + return wrap(err) + } + + // Clear the need and global flags on all other entries + //nolint:sqlclosecheck + upStmt, err = txp.Prepare(` + UPDATE files SET local_flags = local_flags & ? + WHERE name = ? AND sequence != ? AND local_flags & ? != 0 + `) + if err != nil { + return wrap(err) + } + if _, err := upStmt.Exec(^(protocol.FlagLocalNeeded | protocol.FlagLocalGlobal), global.Name, global.Sequence, protocol.FlagLocalNeeded|protocol.FlagLocalGlobal); err != nil { + return wrap(err) + } + + return nil +} + +func (s *DB) folderIdxLocked(folderID string) (int64, error) { + if _, err := s.stmt(` + INSERT OR IGNORE INTO folders(folder_id) + VALUES (?) + `).Exec(folderID); err != nil { + return 0, wrap(err) + } + var idx int64 + if err := s.stmt(` + SELECT idx FROM folders + WHERE folder_id = ? + `).Get(&idx, folderID); err != nil { + return 0, wrap(err) + } + + return idx, nil +} + +type fileRow struct { + Name string + Version dbVector + DeviceIdx int64 `db:"device_idx"` + Sequence int64 + Modified int64 + Size int64 + LocalFlags int64 `db:"local_flags"` + Deleted bool + Invalid bool +} + +func (e fileRow) Compare(other fileRow) int { + // From FileInfo.WinsConflict + vc := e.Version.Vector.Compare(other.Version.Vector) + switch vc { + case protocol.Equal: + if e.Invalid != other.Invalid { + if e.Invalid { + return 1 + } + return -1 + } + + // Compare the device ID index, lower is better. This is only + // deterministic to the extent that LocalDeviceID will always be the + // lowest one, order between remote devices is random (and + // irrelevant). + return cmp.Compare(e.DeviceIdx, other.DeviceIdx) + case protocol.Greater: // we are newer + return -1 + case protocol.Lesser: // we are older + return 1 + case protocol.ConcurrentGreater, protocol.ConcurrentLesser: // there is a conflict + if e.Invalid != other.Invalid { + if e.Invalid { // we are invalid, we lose + return 1 + } + return -1 // they are invalid, we win + } + if e.Deleted != other.Deleted { + if e.Deleted { // we are deleted, we lose + return 1 + } + return -1 // they are deleted, we win + } + if d := cmp.Compare(e.Modified, other.Modified); d != 0 { + return -d // positive d means we were newer, so we win (negative return) + } + if vc == protocol.ConcurrentGreater { + return -1 // we have a better device ID, we win + } + return 1 // they win + default: + return 0 + } +} + +func (s *folderDB) periodicCheckpointLocked(fs []protocol.FileInfo) { + // Induce periodic checkpoints. We add points for each file and block, + // and checkpoint when we've written more than a threshold of points. + // This ensures we do not go too long without a checkpoint, while also + // not doing it incessantly for every update. + s.updatePoints += updatePointsPerFile * len(fs) + for _, f := range fs { + s.updatePoints += len(f.Blocks) * updatePointsPerBlock + } + if s.updatePoints > updatePointsThreshold { + conn, err := s.sql.Conn(context.Background()) + if err != nil { + l.Debugln(s.baseName, "conn:", err) + return + } + defer conn.Close() + if _, err := conn.ExecContext(context.Background(), `PRAGMA journal_size_limit = 8388608`); err != nil { + l.Debugln(s.baseName, "PRAGMA journal_size_limit:", err) + } + + // Every 50th checkpoint becomes a truncate, in an effort to bring + // down the size now and then. + checkpointType := "RESTART" + if s.checkpointsCount > 50 { + checkpointType = "TRUNCATE" + } + cmd := fmt.Sprintf(`PRAGMA wal_checkpoint(%s)`, checkpointType) + row := conn.QueryRowContext(context.Background(), cmd) + + var res, modified, moved int + if row.Err() != nil { + l.Debugln(s.baseName, cmd+":", err) + } else if err := row.Scan(&res, &modified, &moved); err != nil { + l.Debugln(s.baseName, cmd+" (scan):", err) + } else { + l.Debugln(s.baseName, cmd, s.checkpointsCount, "at", s.updatePoints, "returned", res, modified, moved) + } + + // Reset the truncate counter when a truncate succeeded. If it + // failed, we'll keep trying it until we succeed. Increase it faster + // when we fail to checkpoint, as it's more likely the WAL is + // growing and will need truncation when we get out of this state. + if res == 1 { + s.checkpointsCount += 10 + } else if res == 0 && checkpointType == "TRUNCATE" { + s.checkpointsCount = 0 + } else { + s.checkpointsCount++ + } + s.updatePoints = 0 + } +} diff --git a/internal/db/sqlite/sql/schema/10-schema.sql b/internal/db/sqlite/sql/schema/common/10-schema.sql similarity index 100% rename from internal/db/sqlite/sql/schema/10-schema.sql rename to internal/db/sqlite/sql/schema/common/10-schema.sql diff --git a/internal/db/sqlite/sql/schema/70-kv.sql b/internal/db/sqlite/sql/schema/common/70-kv.sql similarity index 100% rename from internal/db/sqlite/sql/schema/70-kv.sql rename to internal/db/sqlite/sql/schema/common/70-kv.sql diff --git a/internal/db/sqlite/sql/schema/00-indexes.sql b/internal/db/sqlite/sql/schema/folder/00-indexes.sql similarity index 63% rename from internal/db/sqlite/sql/schema/00-indexes.sql rename to internal/db/sqlite/sql/schema/folder/00-indexes.sql index 1152d3b36..61e4a26ee 100644 --- a/internal/db/sqlite/sql/schema/00-indexes.sql +++ b/internal/db/sqlite/sql/schema/folder/00-indexes.sql @@ -4,16 +4,9 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this file, -- You can obtain one at https://mozilla.org/MPL/2.0/. --- folders map folder IDs as used by Syncthing to database folder indexes -CREATE TABLE IF NOT EXISTS folders ( - idx INTEGER NOT NULL PRIMARY KEY, - folder_id TEXT NOT NULL UNIQUE COLLATE BINARY -) STRICT -; - -- devices map device IDs as used by Syncthing to database device indexes CREATE TABLE IF NOT EXISTS devices ( - idx INTEGER NOT NULL PRIMARY KEY, + idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL UNIQUE COLLATE BINARY ) STRICT ; diff --git a/internal/db/sqlite/sql/schema/20-files.sql b/internal/db/sqlite/sql/schema/folder/20-files.sql similarity index 88% rename from internal/db/sqlite/sql/schema/20-files.sql rename to internal/db/sqlite/sql/schema/folder/20-files.sql index ca16be9c3..d826f6340 100644 --- a/internal/db/sqlite/sql/schema/20-files.sql +++ b/internal/db/sqlite/sql/schema/folder/20-files.sql @@ -22,7 +22,6 @@ -- Need bit. This allows for very efficient lookup of files needing handling -- on this device, which is a common query. CREATE TABLE IF NOT EXISTS files ( - folder_idx INTEGER NOT NULL, device_idx INTEGER NOT NULL, -- actual device ID or LocalDeviceID sequence INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, -- our local database sequence, for each and every entry remote_sequence INTEGER, -- remote device's sequence number, null for local or synthetic entries @@ -35,8 +34,7 @@ CREATE TABLE IF NOT EXISTS files ( invalid INTEGER NOT NULL, -- boolean local_flags INTEGER NOT NULL, blocklist_hash BLOB, -- null when there are no blocks - FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE, - FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE + FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE ) STRICT ; -- FileInfos store the actual protobuf object. We do this separately to keep @@ -48,15 +46,15 @@ CREATE TABLE IF NOT EXISTS fileinfos ( ) STRICT ; -- There can be only one file per folder, device, and remote sequence number -CREATE UNIQUE INDEX IF NOT EXISTS files_remote_sequence ON files (folder_idx, device_idx, remote_sequence) +CREATE UNIQUE INDEX IF NOT EXISTS files_remote_sequence ON files (device_idx, remote_sequence) WHERE remote_sequence IS NOT NULL ; -- There can be only one file per folder, device, and name -CREATE UNIQUE INDEX IF NOT EXISTS files_device_name ON files (folder_idx, device_idx, name) +CREATE UNIQUE INDEX IF NOT EXISTS files_device_name ON files (device_idx, name) ; -- We want to be able to look up & iterate files based on just folder and name -CREATE INDEX IF NOT EXISTS files_name_only ON files (folder_idx, name) +CREATE INDEX IF NOT EXISTS files_name_only ON files (name) ; -- We want to be able to look up & iterate files based on blocks hash -CREATE INDEX IF NOT EXISTS files_blocklist_hash_only ON files (blocklist_hash, device_idx, folder_idx) WHERE blocklist_hash IS NOT NULL +CREATE INDEX IF NOT EXISTS files_blocklist_hash_only ON files (blocklist_hash, device_idx) WHERE blocklist_hash IS NOT NULL ; diff --git a/internal/db/sqlite/sql/schema/30-indexids.sql b/internal/db/sqlite/sql/schema/folder/30-indexids.sql similarity index 70% rename from internal/db/sqlite/sql/schema/30-indexids.sql rename to internal/db/sqlite/sql/schema/folder/30-indexids.sql index dde9851bc..e6a40bb53 100644 --- a/internal/db/sqlite/sql/schema/30-indexids.sql +++ b/internal/db/sqlite/sql/schema/folder/30-indexids.sql @@ -7,18 +7,16 @@ -- indexids holds the index ID and maximum sequence for a given device and folder CREATE TABLE IF NOT EXISTS indexids ( device_idx INTEGER NOT NULL, - folder_idx INTEGER NOT NULL, index_id TEXT NOT NULL COLLATE BINARY, sequence INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY(device_idx, folder_idx), - FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE, + PRIMARY KEY(device_idx), FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE ) STRICT, WITHOUT ROWID ; CREATE TRIGGER IF NOT EXISTS indexids_seq AFTER INSERT ON files BEGIN - INSERT INTO indexids (folder_idx, device_idx, index_id, sequence) - VALUES (NEW.folder_idx, NEW.device_idx, "", COALESCE(NEW.remote_sequence, NEW.sequence)) + INSERT INTO indexids (device_idx, index_id, sequence) + VALUES (NEW.device_idx, "", COALESCE(NEW.remote_sequence, NEW.sequence)) ON CONFLICT DO UPDATE SET sequence = COALESCE(NEW.remote_sequence, NEW.sequence); END ; diff --git a/internal/db/sqlite/sql/schema/40-counts.sql b/internal/db/sqlite/sql/schema/folder/40-counts.sql similarity index 57% rename from internal/db/sqlite/sql/schema/40-counts.sql rename to internal/db/sqlite/sql/schema/folder/40-counts.sql index cac1851fb..9b123c725 100644 --- a/internal/db/sqlite/sql/schema/40-counts.sql +++ b/internal/db/sqlite/sql/schema/folder/40-counts.sql @@ -9,16 +9,14 @@ -- Counts and sizes are maintained for each device, folder, type, flag bits -- combination. CREATE TABLE IF NOT EXISTS counts ( - folder_idx INTEGER NOT NULL, device_idx INTEGER NOT NULL, type INTEGER NOT NULL, local_flags INTEGER NOT NULL, count INTEGER NOT NULL, size INTEGER NOT NULL, deleted INTEGER NOT NULL, -- boolean - PRIMARY KEY(folder_idx, device_idx, type, local_flags, deleted), - FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE, - FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE + PRIMARY KEY(device_idx, type, local_flags, deleted), + FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE ) STRICT, WITHOUT ROWID ; @@ -26,28 +24,24 @@ CREATE TABLE IF NOT EXISTS counts ( CREATE TRIGGER IF NOT EXISTS counts_insert AFTER INSERT ON files BEGIN - INSERT INTO counts (folder_idx, device_idx, type, local_flags, count, size, deleted) - VALUES (NEW.folder_idx, NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) + INSERT INTO counts (device_idx, type, local_flags, count, size, deleted) + VALUES (NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size; END ; CREATE TRIGGER IF NOT EXISTS counts_delete AFTER DELETE ON files BEGIN UPDATE counts SET count = count - 1, size = size - OLD.size - WHERE folder_idx = OLD.folder_idx AND device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted; + WHERE device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted; END ; CREATE TRIGGER IF NOT EXISTS counts_update AFTER UPDATE OF local_flags ON files WHEN NEW.local_flags != OLD.local_flags BEGIN - INSERT INTO counts (folder_idx, device_idx, type, local_flags, count, size, deleted) - VALUES (NEW.folder_idx, NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) + INSERT INTO counts (device_idx, type, local_flags, count, size, deleted) + VALUES (NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size; UPDATE counts SET count = count - 1, size = size - OLD.size - WHERE folder_idx = OLD.folder_idx AND device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted; + WHERE device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted; END ; -DROP TRIGGER IF EXISTS counts_update_add -- tmp migration -; -DROP TRIGGER IF EXISTS counts_update_del -- tmp migration -; diff --git a/internal/db/sqlite/sql/schema/50-blocks.sql b/internal/db/sqlite/sql/schema/folder/50-blocks.sql similarity index 100% rename from internal/db/sqlite/sql/schema/50-blocks.sql rename to internal/db/sqlite/sql/schema/folder/50-blocks.sql diff --git a/internal/db/sqlite/sql/schema/50-mtimes.sql b/internal/db/sqlite/sql/schema/folder/50-mtimes.sql similarity index 76% rename from internal/db/sqlite/sql/schema/50-mtimes.sql rename to internal/db/sqlite/sql/schema/folder/50-mtimes.sql index 1d6f6b5ba..5b3313c10 100644 --- a/internal/db/sqlite/sql/schema/50-mtimes.sql +++ b/internal/db/sqlite/sql/schema/folder/50-mtimes.sql @@ -6,11 +6,9 @@ --- Backing for the MtimeFS CREATE TABLE IF NOT EXISTS mtimes ( - folder_idx INTEGER NOT NULL, name TEXT NOT NULL, ondisk INTEGER NOT NULL, -- unix nanos virtual INTEGER NOT NULL, -- unix nanos - PRIMARY KEY(folder_idx, name), - FOREIGN KEY(folder_idx) REFERENCES folders(idx) ON DELETE CASCADE + PRIMARY KEY(name) ) STRICT, WITHOUT ROWID ; diff --git a/internal/db/sqlite/sql/schema/main/00-indexes.sql b/internal/db/sqlite/sql/schema/main/00-indexes.sql new file mode 100644 index 000000000..0a0d6a8dd --- /dev/null +++ b/internal/db/sqlite/sql/schema/main/00-indexes.sql @@ -0,0 +1,12 @@ +-- Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +-- folders map folder IDs as used by Syncthing to database folder indexes +CREATE TABLE IF NOT EXISTS folders ( + idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + folder_id TEXT NOT NULL UNIQUE COLLATE BINARY +) STRICT +; diff --git a/lib/locations/locations.go b/lib/locations/locations.go index 3c38c7511..153b89d1d 100644 --- a/lib/locations/locations.go +++ b/lib/locations/locations.go @@ -48,7 +48,7 @@ const ( UserHomeBaseDir BaseDirEnum = "userHome" levelDBDir = "index-v0.14.0.db" - databaseName = "index-v2.db" + databaseName = "index-v2" configFileName = "config.xml" defaultStateDir = ".local/state/syncthing" oldDefaultConfigDir = ".config/syncthing" diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 3500616eb..03718fb91 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -315,23 +315,15 @@ func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyC fileDeletions := map[string]protocol.FileInfo{} buckets := map[string][]protocol.FileInfo{} - // Buffer the full list of needed files. This is somewhat wasteful and - // uses a lot of memory, but we need to keep the duration of the - // database read short and not do a bunch of file and data I/O inside - // the loop. If we forego the ability for users to repriorize the pull - // queue on the fly we could do this in batches, though that would also - // be a bit slower and less efficient in other ways. - files, err := itererr.Collect(f.model.sdb.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, f.Order, 0, 0)) - if err != nil { - return changed, nil, nil, err - } - // Iterate the list of items that we need and sort them into piles. // Regular files to pull goes into the file queue, everything else // (directories, symlinks and deletes) goes into the "process directly" // pile. loop: - for _, file := range files { + for file, err := range itererr.Zip(f.model.sdb.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, f.Order, 0, 0)) { + if err != nil { + return changed, nil, nil, err + } select { case <-f.ctx.Done(): break loop @@ -1353,58 +1345,58 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) found := false - for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(block.Hash)) { + blocks, _ := f.model.sdb.AllLocalBlocksWithHash(block.Hash) + for _, e := range blocks { + res, err := f.model.sdb.AllLocalFilesWithBlocksHashAnyFolder(e.BlocklistHash) if err != nil { - break + continue } - it, errFn := f.model.sdb.AllLocalFilesWithBlocksHashAnyFolder(e.BlocklistHash) - for folderID, fi := range it { + for folderID, files := range res { ffs := folderFilesystems[folderID] - fd, err := ffs.Open(fi.Name) - if err != nil { - continue - } - defer fd.Close() - - _, err = fd.ReadAt(buf, e.Offset) - if err != nil { - fd.Close() - continue - } - - // Hash is not SHA256 as it's an encrypted hash token. In that - // case we can't verify the block integrity so we'll take it on - // trust. (The other side can and will verify.) - if f.Type != config.FolderTypeReceiveEncrypted { - if err := f.verifyBuffer(buf, block); err != nil { - l.Debugln("Finder failed to verify buffer", err) + for _, fi := range files { + fd, err := ffs.Open(fi.Name) + if err != nil { continue } - } + defer fd.Close() - if f.CopyRangeMethod != config.CopyRangeMethodStandard { - err = f.withLimiter(func() error { - dstFd.mut.Lock() - defer dstFd.mut.Unlock() - return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, e.Offset, block.Offset, int64(block.Size)) - }) - } else { - err = f.limitedWriteAt(dstFd, buf, block.Offset) - } - if err != nil { - state.fail(fmt.Errorf("dst write: %w", err)) + _, err = fd.ReadAt(buf, e.Offset) + if err != nil { + fd.Close() + continue + } + + // Hash is not SHA256 as it's an encrypted hash token. In that + // case we can't verify the block integrity so we'll take it on + // trust. (The other side can and will verify.) + if f.Type != config.FolderTypeReceiveEncrypted { + if err := f.verifyBuffer(buf, block); err != nil { + l.Debugln("Finder failed to verify buffer", err) + continue + } + } + + if f.CopyRangeMethod != config.CopyRangeMethodStandard { + err = f.withLimiter(func() error { + dstFd.mut.Lock() + defer dstFd.mut.Unlock() + return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, e.Offset, block.Offset, int64(block.Size)) + }) + } else { + err = f.limitedWriteAt(dstFd, buf, block.Offset) + } + if err != nil { + state.fail(fmt.Errorf("dst write: %w", err)) + break + } + if fi.Name == state.file.Name { + state.copiedFromOrigin(block.Size) + } else { + state.copiedFromElsewhere(block.Size) + } + found = true break } - if fi.Name == state.file.Name { - state.copiedFromOrigin(block.Size) - } else { - state.copiedFromElsewhere(block.Size) - } - found = true - break - } - if err := errFn(); err != nil { - l.Warnln(err) } } diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index bd1376262..e307670da 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -19,7 +19,6 @@ import ( "testing" "time" - "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" @@ -326,11 +325,11 @@ func TestCopierCleanup(t *testing.T) { // Update index (removing old blocks) f.updateLocalsFromScanning([]protocol.FileInfo{file}) - if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[0].Hash)); err != nil || len(vals) > 0 { + if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[0].Hash); err != nil || len(vals) > 0 { t.Error("Unexpected block found") } - if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[1].Hash)); err != nil || len(vals) == 0 { + if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[1].Hash); err != nil || len(vals) == 0 { t.Error("Expected block not found") } @@ -339,11 +338,11 @@ func TestCopierCleanup(t *testing.T) { // Update index (removing old blocks) f.updateLocalsFromScanning([]protocol.FileInfo{file}) - if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[0].Hash)); err != nil || len(vals) == 0 { + if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[0].Hash); err != nil || len(vals) == 0 { t.Error("Unexpected block found") } - if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(blocks[1].Hash)); err != nil || len(vals) > 0 { + if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[1].Hash); err != nil || len(vals) > 0 { t.Error("Expected block not found") } } From c9650fc7d5e3f5ecaf2169691682c6fd3a77f460 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sun, 6 Apr 2025 05:31:02 -0700 Subject: [PATCH 16/62] chore(model): delay starting a pull while there are incoming index updates (#10041) This adds a simple delay to the process for starting the pull, by default one second. In practice this means we're likely to wait for initial index transfer, or multiple messages sent as part of a larger change. This is better because we're more likely to have the whole change for the purpose of handling renames etc, and also it's more efficient to do one larger puller iteration instead of multiple while also processing changes. It does however introduce a certain amount of delay into the sync process, so it can be tuned down or turned off entirely. --- lib/config/config_test.go | 2 ++ lib/config/folderconfiguration.go | 1 + lib/model/folder.go | 13 +++++++++++++ lib/model/testutils_test.go | 1 + 4 files changed, 17 insertions(+) diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 27a6d8546..daf50eb57 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -108,6 +108,7 @@ func TestDefaultValues(t *testing.T) { FSWatcherEnabled: true, FSWatcherDelayS: 10, IgnorePerms: false, + PullerDelayS: 1, AutoNormalize: true, MinDiskFree: size, Versioning: VersioningConfiguration{ @@ -184,6 +185,7 @@ func TestDeviceConfig(t *testing.T) { FSWatcherDelayS: 10, Copiers: 0, Hashers: 0, + PullerDelayS: 1, AutoNormalize: true, MinDiskFree: Size{1, "%"}, MaxConflicts: -1, diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index 1bfc898db..9d4876dde 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -68,6 +68,7 @@ type FolderConfiguration struct { IgnoreDelete bool `json:"ignoreDelete" xml:"ignoreDelete"` ScanProgressIntervalS int `json:"scanProgressIntervalS" xml:"scanProgressIntervalS"` PullerPauseS int `json:"pullerPauseS" xml:"pullerPauseS"` + PullerDelayS float64 `json:"pullerDelayS" xml:"pullerDelayS" default:"1"` MaxConflicts int `json:"maxConflicts" xml:"maxConflicts" default:"10"` DisableSparseFiles bool `json:"disableSparseFiles" xml:"disableSparseFiles"` DisableTempIndexes bool `json:"disableTempIndexes" xml:"disableTempIndexes"` diff --git a/lib/model/folder.go b/lib/model/folder.go index 05a5b1032..10f2ac40c 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -173,6 +173,8 @@ func (f *folder) Serve(ctx context.Context) error { } initialCompleted := f.initialScanFinished + pullTimer := time.NewTimer(0) + pullTimer.Stop() for { var err error @@ -183,6 +185,17 @@ func (f *folder) Serve(ctx context.Context) error { return nil case <-f.pullScheduled: + if f.PullerDelayS > 0 { + // Wait for incoming updates to settle before doing the + // actual pull + f.setState(FolderSyncWaiting) + pullTimer.Reset(time.Duration(float64(time.Second) * f.PullerDelayS)) + } else { + _, err = f.pull() + } + + case <-pullTimer.C: + f.setState(FolderIdle) _, err = f.pull() case <-f.pullFailTimer.C: diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go index 7949bc617..8d6140ec5 100644 --- a/lib/model/testutils_test.go +++ b/lib/model/testutils_test.go @@ -103,6 +103,7 @@ func newDefaultCfgWrapper() (config.Wrapper, config.FolderConfiguration, context func newFolderConfig() config.FolderConfiguration { cfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", config.FilesystemTypeFake, rand.String(32)+"?content=true") cfg.FSWatcherEnabled = false + cfg.PullerDelayS = 0 cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1}) return cfg } From 55da878452cdc2ed3d3369f08d2578a850dd00f8 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 7 Apr 2025 08:39:06 +0200 Subject: [PATCH 17/62] chore: improved perf stats --- cmd/syncthing/perfstats_unix.go | 85 +++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/cmd/syncthing/perfstats_unix.go b/cmd/syncthing/perfstats_unix.go index 6da5a2116..81cf2fe7a 100644 --- a/cmd/syncthing/perfstats_unix.go +++ b/cmd/syncthing/perfstats_unix.go @@ -16,7 +16,9 @@ import ( "syscall" "time" + "github.com/syncthing/syncthing/lib/locations" "github.com/syncthing/syncthing/lib/protocol" + "golang.org/x/exp/constraints" ) func startPerfStats() { @@ -29,37 +31,68 @@ func savePerfStats(file string) { panic(err) } - var prevUsage int64 - var prevTime int64 - var rusage syscall.Rusage - var memstats runtime.MemStats + var prevTime time.Time + var curRus, prevRus syscall.Rusage + var curMem, prevMem runtime.MemStats var prevIn, prevOut int64 t0 := time.Now() + syscall.Getrusage(syscall.RUSAGE_SELF, &prevRus) + runtime.ReadMemStats(&prevMem) + + fmt.Fprintf(fd, "TIME_S\tCPU_S\tHEAP_KIB\tRSS_KIB\tNETIN_KBPS\tNETOUT_KBPS\tDBSIZE_KIB\n") + for t := range time.NewTicker(250 * time.Millisecond).C { - if err := syscall.Getrusage(syscall.RUSAGE_SELF, &rusage); err != nil { - continue - } - - curTime := time.Now().UnixNano() - timeDiff := curTime - prevTime - curUsage := rusage.Utime.Nano() + rusage.Stime.Nano() - usageDiff := curUsage - prevUsage - cpuUsagePercent := 100 * float64(usageDiff) / float64(timeDiff) - prevTime = curTime - prevUsage = curUsage + syscall.Getrusage(syscall.RUSAGE_SELF, &curRus) + runtime.ReadMemStats(&curMem) in, out := protocol.TotalInOut() - var inRate, outRate float64 - if timeDiff > 0 { - inRate = float64(in-prevIn) / (float64(timeDiff) / 1e9) // bytes per second - outRate = float64(out-prevOut) / (float64(timeDiff) / 1e9) // bytes per second - } + timeDiff := t.Sub(prevTime) + + fmt.Fprintf(fd, "%.03f\t%f\t%d\t%d\t%.0f\t%.0f\t%d\n", + t.Sub(t0).Seconds(), + rate(cpusec(&prevRus), cpusec(&curRus), timeDiff, 1), + (curMem.Sys-curMem.HeapReleased)/1024, + curRus.Maxrss/1024, + rate(prevIn, in, timeDiff, 1e3), + rate(prevOut, out, timeDiff, 1e3), + dirsize(locations.Get(locations.Database))/1024, + ) + + prevTime = t + prevRus = curRus + prevMem = curMem prevIn, prevOut = in, out - - runtime.ReadMemStats(&memstats) - - startms := int(t.Sub(t0).Seconds() * 1000) - - fmt.Fprintf(fd, "%d\t%f\t%d\t%d\t%.0f\t%.0f\n", startms, cpuUsagePercent, memstats.Alloc, memstats.Sys-memstats.HeapReleased, inRate, outRate) } } + +func cpusec(r *syscall.Rusage) float64 { + return float64(r.Utime.Nano()+r.Stime.Nano()) / float64(time.Second) +} + +type number interface { + constraints.Float | constraints.Integer +} + +func rate[T number](prev, cur T, d time.Duration, div float64) float64 { + diff := cur - prev + rate := float64(diff) / d.Seconds() / div + return rate +} + +func dirsize(location string) int64 { + entries, err := os.ReadDir(location) + if err != nil { + return 0 + } + + var size int64 + for _, entry := range entries { + fi, err := entry.Info() + if err != nil { + continue + } + size += fi.Size() + } + + return size +} From 05210d03254c9e577066eac207ac9a160abda703 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 7 Apr 2025 10:58:14 +0200 Subject: [PATCH 18/62] fix(db): wrong prepare method --- internal/db/sqlite/folderdb_update.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/db/sqlite/folderdb_update.go b/internal/db/sqlite/folderdb_update.go index 8376b3b92..3e5befb9b 100644 --- a/internal/db/sqlite/folderdb_update.go +++ b/internal/db/sqlite/folderdb_update.go @@ -374,7 +374,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error global.LocalFlags |= protocol.FlagLocalNeeded } //nolint:sqlclosecheck - upStmt, err := txp.Prepare(` + upStmt, err := txp.Preparex(` UPDATE files SET local_flags = ? WHERE device_idx = ? AND sequence = ? `) @@ -387,7 +387,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error // Clear the need and global flags on all other entries //nolint:sqlclosecheck - upStmt, err = txp.Prepare(` + upStmt, err = txp.Preparex(` UPDATE files SET local_flags = local_flags & ? WHERE name = ? AND sequence != ? AND local_flags & ? != 0 `) From d5aa991b7304beb6d823e9b4a04c53d7753ef5ec Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 7 Apr 2025 11:35:31 +0200 Subject: [PATCH 19/62] chore(db): use pseudo random naming for folder databases --- internal/db/sqlite/basedb.go | 2 - internal/db/sqlite/db_folderdb.go | 39 ++++++++++++------- .../folder/{00-indexes.sql => 00-devices.sql} | 0 .../main/{00-indexes.sql => 00-folders.sql} | 6 ++- 4 files changed, 30 insertions(+), 17 deletions(-) rename internal/db/sqlite/sql/schema/folder/{00-indexes.sql => 00-devices.sql} (100%) rename internal/db/sqlite/sql/schema/main/{00-indexes.sql => 00-folders.sql} (62%) diff --git a/internal/db/sqlite/basedb.go b/internal/db/sqlite/basedb.go index 63f09c160..c2d1b8808 100644 --- a/internal/db/sqlite/basedb.go +++ b/internal/db/sqlite/basedb.go @@ -199,11 +199,9 @@ nextScript: for _, scr := range scripts { for _, fn := range filter { if !fn(scr) { - l.Debugln(s.baseName, "skipping script", scr) continue nextScript } } - l.Debugln(s.baseName, "executing script", scr) bs, err := fs.ReadFile(embedded, scr) if err != nil { return wrap(err, scr) diff --git a/internal/db/sqlite/db_folderdb.go b/internal/db/sqlite/db_folderdb.go index 1af061a28..f82946d2a 100644 --- a/internal/db/sqlite/db_folderdb.go +++ b/internal/db/sqlite/db_folderdb.go @@ -12,12 +12,14 @@ import ( "fmt" "iter" "path/filepath" + "strings" "time" "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/rand" ) var errNoSuchFolder = errors.New("no such folder") @@ -31,16 +33,16 @@ func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) { return fdb, nil } - // Check for an existing folder. If we're not supposed to create the - // folder, we don't move on if it doesn't already have an ID. - var idx int64 + // Check for an existing database. If we're not supposed to create the + // folder, we don't move on if it doesn't already have a database name. + var dbName string if err := s.stmt(` - SELECT idx FROM folders + SELECT database_name FROM folders WHERE folder_id = ? - `).Get(&idx, folder); err != nil && !errors.Is(err, sql.ErrNoRows) { + `).Get(&dbName, folder); err != nil && !errors.Is(err, sql.ErrNoRows) { return nil, wrap(err) } - if idx == 0 && !create { + if dbName == "" && !create { return nil, errNoSuchFolder } @@ -51,21 +53,30 @@ func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) { return fdb, nil } - var err error - if idx == 0 { + if dbName == "" { // First time we want to access this folder, need to create a new // folder ID - idx, err = s.folderIdxLocked(folder) + idx, err := s.folderIdxLocked(folder) if err != nil { - return nil, err + return nil, wrap(err) + } + + // The database name is the folder index ID and a random slug. + slug := strings.ToLower(rand.String(8)) + dbName = fmt.Sprintf("folder.%04x-%s.db", idx, slug) + if _, err := s.stmt(`UPDATE folders SET database_name = ? WHERE idx = ?`).Exec(dbName, idx); err != nil { + return nil, wrap(err, "set name") } } - name := fmt.Sprintf("folder.%04x.db", idx) - path := filepath.Join(s.pathBase, name) - fdb, err = s.folderDBOpener(folder, path, s.deleteRetention) + l.Debugf("Folder %s in database %s", folder, dbName) + path := dbName + if !filepath.IsAbs(path) { + path = filepath.Join(s.pathBase, dbName) + } + fdb, err := s.folderDBOpener(folder, path, s.deleteRetention) if err != nil { - return nil, err + return nil, wrap(err) } s.folderDBs[folder] = fdb return fdb, nil diff --git a/internal/db/sqlite/sql/schema/folder/00-indexes.sql b/internal/db/sqlite/sql/schema/folder/00-devices.sql similarity index 100% rename from internal/db/sqlite/sql/schema/folder/00-indexes.sql rename to internal/db/sqlite/sql/schema/folder/00-devices.sql diff --git a/internal/db/sqlite/sql/schema/main/00-indexes.sql b/internal/db/sqlite/sql/schema/main/00-folders.sql similarity index 62% rename from internal/db/sqlite/sql/schema/main/00-indexes.sql rename to internal/db/sqlite/sql/schema/main/00-folders.sql index 0a0d6a8dd..72f20ab13 100644 --- a/internal/db/sqlite/sql/schema/main/00-indexes.sql +++ b/internal/db/sqlite/sql/schema/main/00-folders.sql @@ -7,6 +7,10 @@ -- folders map folder IDs as used by Syncthing to database folder indexes CREATE TABLE IF NOT EXISTS folders ( idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - folder_id TEXT NOT NULL UNIQUE COLLATE BINARY + folder_id TEXT NOT NULL UNIQUE COLLATE BINARY, + database_name TEXT COLLATE BINARY -- initially null ) STRICT ; +-- The database_name is unique, when set +CREATE INDEX IF NOT EXISTS folders_database_name ON folders (database_name) WHERE database_name IS NOT NULL +; From e0c1abc5fefe2ffc58b552248c28b0a03cf03021 Mon Sep 17 00:00:00 2001 From: Tommy van der Vorst Date: Wed, 9 Apr 2025 06:29:46 +0200 Subject: [PATCH 20/62] fix(sqlite): apply options (#10049) @calmh it seems `sqlite.Open` forgets to apply options supplied to it (currently only the delete retention interval). --- internal/db/sqlite/db_open.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/db/sqlite/db_open.go b/internal/db/sqlite/db_open.go index 45c3547a0..ce8fe7851 100644 --- a/internal/db/sqlite/db_open.go +++ b/internal/db/sqlite/db_open.go @@ -65,6 +65,10 @@ func Open(path string, opts ...Option) (*DB, error) { folderDBOpener: openFolderDB, } + for _, opt := range opts { + opt(db) + } + return db, nil } From 7cfa871d58375facc19d29b61f0b4e042708bde5 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 9 Apr 2025 16:24:17 +0200 Subject: [PATCH 21/62] fix(db): skip invalid files as remote need when local is deleted --- internal/db/sqlite/db_global_test.go | 28 ++++++++++++++++++++++++++- internal/db/sqlite/folderdb_counts.go | 2 +- internal/db/sqlite/folderdb_global.go | 10 +++++----- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/internal/db/sqlite/db_global_test.go b/internal/db/sqlite/db_global_test.go index 44130ac77..3a3bae606 100644 --- a/internal/db/sqlite/db_global_test.go +++ b/internal/db/sqlite/db_global_test.go @@ -268,7 +268,7 @@ func TestDontNeedIgnored(t *testing.T) { } } -func TestRemoveDontNeedLocalIgnored(t *testing.T) { +func TestRemoteDontNeedLocalIgnored(t *testing.T) { t.Parallel() db, err := OpenTemp() @@ -392,6 +392,32 @@ func TestRemoteDontNeedDeletedMissing(t *testing.T) { t.Log(names) t.Error("need no files") } + + // Another remote has announced it, but has set the invalid bit, + // presumably it's being ignored. + file = genFile("test1", 1, 103) + file.SetIgnored() + err = db.Update(folderID, protocol.DeviceID{43}, []protocol.FileInfo{file}) + if err != nil { + t.Fatal(err) + } + + // They don't need it, either + s, err = db.CountNeed(folderID, protocol.DeviceID{43}) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 { + t.Log(s) + t.Error("bad need") + } + + // It shouldn't show up in their need list + names = mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0)) + if len(names) != 0 { + t.Log(names) + t.Error("need no files") + } } func TestNeedRemoteSymlinkAndDir(t *testing.T) { diff --git a/internal/db/sqlite/folderdb_counts.go b/internal/db/sqlite/folderdb_counts.go index 0e019f15a..1c6dfd56f 100644 --- a/internal/db/sqlite/folderdb_counts.go +++ b/internal/db/sqlite/folderdb_counts.go @@ -97,7 +97,7 @@ func (s *folderDB) needSizeRemote(device protocol.DeviceID) (db.Counts, error) { WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted + WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND NOT f.invalid ) GROUP BY g.type, g.local_flags, g.deleted `).Select(&res, device.String(), diff --git a/internal/db/sqlite/folderdb_global.go b/internal/db/sqlite/folderdb_global.go index 3fc71d53c..5dd6c42a3 100644 --- a/internal/db/sqlite/folderdb_global.go +++ b/internal/db/sqlite/folderdb_global.go @@ -148,11 +148,11 @@ func (s *folderDB) neededGlobalFilesLocal(selectOpts string) (iter.Seq[protocol. func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) { // Select: // - // - all the valid, non-deleted global files that don't have a corresponding - // remote file with the same version. + // - all the valid, non-deleted global files that don't have a + // corresponding remote file with the same version. // - // - all the valid, deleted global files that have a corresponding non-deleted - // remote file (of any version) + // - all the valid, deleted global files that have a corresponding + // non-deleted and valid remote file (of any version) it, errFn := iterStructs[indirectFI](s.stmt(` SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi @@ -172,7 +172,7 @@ func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted + WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND NOT f.invalid ) `+selectOpts).Queryx( device.String(), From ddea2e449cca884b51ecf53722c004b606a98dcd Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 9 Apr 2025 08:46:49 -0700 Subject: [PATCH 22/62] fix(db): version vector serialisation :( (#10050) ffs --- internal/db/sqlite/db_test.go | 48 +++++++++++++++++++++++++++++++++ internal/db/sqlite/util.go | 7 +++++ internal/db/sqlite/util_test.go | 33 +++++++++++++++++++++++ lib/protocol/vector.go | 4 ++- 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 internal/db/sqlite/util_test.go diff --git a/internal/db/sqlite/db_test.go b/internal/db/sqlite/db_test.go index c6e2528e9..44c9a5d68 100644 --- a/internal/db/sqlite/db_test.go +++ b/internal/db/sqlite/db_test.go @@ -1109,6 +1109,54 @@ func TestErrorWrap(t *testing.T) { } } +func TestStrangeDeletedGlobalBug(t *testing.T) { + // This exercises an edge case with serialisation and ordering of + // version vectors. It does not need to make sense, it just needs to + // pass. + + t.Parallel() + + sdb, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := sdb.Close(); err != nil { + t.Fatal(err) + } + }) + + // One remote device announces the original version of the file + + file := genFile("test", 1, 1) + file.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 35494436325452, Value: 1742900373}}} + t.Log("orig", file.Version) + sdb.Update(folderID, protocol.DeviceID{42}, []protocol.FileInfo{file}) + + // Another one announces a newer one that is deleted + + del := file + del.SetDeleted(43) + del.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 55445057455644, Value: 1742918457}, {ID: 35494436325452, Value: 1742900373}}} + t.Log("del", del.Version) + sdb.Update(folderID, protocol.DeviceID{43}, []protocol.FileInfo{del}) + + // We have an instance of the original file + + sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file}) + + // Which one is the global? It should be the deleted one, clearly. + + g, _, err := sdb.GetGlobalFile(folderID, "test") + if err != nil { + t.Fatal(err) + } + if !g.Deleted { + t.Log(g) + t.Fatal("should be deleted") + } +} + func mustCollect[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) []T { t.Helper() return func(it iter.Seq[T], errFn func() error) []T { diff --git a/internal/db/sqlite/util.go b/internal/db/sqlite/util.go index 3d734d711..dfed2857c 100644 --- a/internal/db/sqlite/util.go +++ b/internal/db/sqlite/util.go @@ -7,9 +7,11 @@ package sqlite import ( + "cmp" "database/sql/driver" "errors" "iter" + "slices" "github.com/jmoiron/sqlx" "github.com/syncthing/syncthing/internal/gen/bep" @@ -71,6 +73,11 @@ func (v *dbVector) Scan(value any) error { if err != nil { return wrap(err) } + + // This is only necessary because I messed up counter serialisation and + // thereby ordering in 2.0.0 betas, and can be removed in the future. + slices.SortFunc(vec.Counters, func(a, b protocol.Counter) int { return cmp.Compare(a.ID, b.ID) }) + v.Vector = vec return nil diff --git a/internal/db/sqlite/util_test.go b/internal/db/sqlite/util_test.go new file mode 100644 index 000000000..eb78a5782 --- /dev/null +++ b/internal/db/sqlite/util_test.go @@ -0,0 +1,33 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +import ( + "testing" + + "github.com/syncthing/syncthing/lib/protocol" +) + +func TestDbvector(t *testing.T) { + vec := protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 7}, {ID: 123456789, Value: 42424242}}} + dbVec := dbVector{vec} + val, err := dbVec.Value() + if err != nil { + t.Fatal(val) + } + + var dbVec2 dbVector + if err := dbVec2.Scan(val); err != nil { + t.Fatal(err) + } + + if !dbVec2.Vector.Equal(vec) { + t.Log(vec) + t.Log(dbVec2.Vector) + t.Fatal("should match") + } +} diff --git a/lib/protocol/vector.go b/lib/protocol/vector.go index 91ee274f7..adaaa7494 100644 --- a/lib/protocol/vector.go +++ b/lib/protocol/vector.go @@ -31,7 +31,9 @@ func (v *Vector) String() string { if i > 0 { buf.WriteRune(',') } - fmt.Fprintf(&buf, "%x:%d", c.ID, c.Value) + var idbs [8]byte + binary.BigEndian.PutUint64(idbs[:], uint64(c.ID)) + fmt.Fprintf(&buf, "%x:%d", idbs, c.Value) } return buf.String() } From 50480b89fc1880d8b412d577e14000c020b37918 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 23 Apr 2025 12:59:24 +0700 Subject: [PATCH 23/62] chore(syncthing): remove "default" folder concept (#10068) This removes the creation of the `default` folder on startup. My feeling is that the concept is not widely used or appreciated. --- cmd/syncthing/generate/generate.go | 13 ++++++------- cmd/syncthing/main.go | 3 +-- lib/syncthing/utils.go | 24 ++++++------------------ 3 files changed, 13 insertions(+), 27 deletions(-) diff --git a/cmd/syncthing/generate/generate.go b/cmd/syncthing/generate/generate.go index 0d3479c22..f9d9814f0 100644 --- a/cmd/syncthing/generate/generate.go +++ b/cmd/syncthing/generate/generate.go @@ -24,10 +24,9 @@ import ( ) type CLI struct { - GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"` - GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"` - NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"` - NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"` + GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"` + GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"` + NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"` } func (c *CLI) Run(l logger.Logger) error { @@ -41,13 +40,13 @@ func (c *CLI) Run(l logger.Logger) error { c.GUIPassword = string(password) } - if err := Generate(l, locations.GetBaseDir(locations.ConfigBaseDir), c.GUIUser, c.GUIPassword, c.NoDefaultFolder, c.NoPortProbing); err != nil { + if err := Generate(l, locations.GetBaseDir(locations.ConfigBaseDir), c.GUIUser, c.GUIPassword, c.NoPortProbing); err != nil { return fmt.Errorf("failed to generate config and keys: %w", err) } return nil } -func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFolder, skipPortProbing bool) error { +func Generate(l logger.Logger, confDir, guiUser, guiPassword string, skipPortProbing bool) error { dir, err := fs.ExpandTilde(confDir) if err != nil { return err @@ -75,7 +74,7 @@ func Generate(l logger.Logger, confDir, guiUser, guiPassword string, noDefaultFo cfgFile := locations.Get(locations.ConfigFile) cfg, _, err := config.Load(cfgFile, myID, events.NoopLogger) if fs.IsNotExist(err) { - if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder, skipPortProbing); err != nil { + if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, skipPortProbing); err != nil { return fmt.Errorf("create config: %w", err) } } else if err != nil { diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index d666377c5..9968fbfc1 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -172,7 +172,6 @@ type serveCmd struct { LogMaxFiles int `name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)" default:"${logMaxFiles}" placeholder:"N" env:"STLOGMAXOLDFILES"` LogMaxSize int `help:"Maximum size of any file (zero to disable log rotation)" default:"${logMaxSize}" placeholder:"BYTES" env:"STLOGMAXSIZE"` NoBrowser bool `help:"Do not start browser" env:"STNOBROWSER"` - NoDefaultFolder bool `help:"Don't create the \"default\" folder on first startup" env:"STNODEFAULTFOLDER"` NoPortProbing bool `help:"Don't try to find free ports for GUI and listen addresses on first startup" env:"STNOPORTPROBING"` NoRestart bool `help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash" env:"STNORESTART"` NoUpgrade bool `help:"Disable automatic upgrades" env:"STNOUPGRADE"` @@ -463,7 +462,7 @@ func (c *serveCmd) syncthingMain() { evLogger := events.NewLogger() earlyService.Add(evLogger) - cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, c.AllowNewerConfig, c.NoDefaultFolder, c.NoPortProbing) + cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, c.AllowNewerConfig, c.NoPortProbing) if err != nil { l.Warnln("Failed to initialize config:", err) os.Exit(svcutil.ExitError.AsInt()) diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index 25ed23034..e471a2403 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -65,7 +65,7 @@ func GenerateCertificate(certFile, keyFile string) (tls.Certificate, error) { return tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays) } -func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder, skipPortProbing bool) (config.Wrapper, error) { +func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, skipPortProbing bool) (config.Wrapper, error) { newCfg := config.New(myID) if skipPortProbing { @@ -76,30 +76,18 @@ func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, return nil, err } - if noDefaultFolder { - l.Infoln("We will skip creation of a default folder on first start") - return config.Wrap(path, newCfg, myID, evLogger), nil - } - - fcfg := newCfg.Defaults.Folder.Copy() - fcfg.ID = "default" - fcfg.Label = "Default Folder" - fcfg.FilesystemType = config.FilesystemTypeBasic - fcfg.Path = locations.Get(locations.DefFolder) - newCfg.Folders = append(newCfg.Folders, fcfg) - l.Infoln("Default folder created and/or linked to new config") return config.Wrap(path, newCfg, myID, evLogger), nil } // LoadConfigAtStartup loads an existing config. If it doesn't yet exist, it -// creates a default one, without the default folder if noDefaultFolder is true. -// Otherwise it checks the version, and archives and upgrades the config if -// necessary or returns an error, if the version isn't compatible. -func LoadConfigAtStartup(path string, cert tls.Certificate, evLogger events.Logger, allowNewerConfig, noDefaultFolder, skipPortProbing bool) (config.Wrapper, error) { +// creates a default one. Otherwise it checks the version, and archives and +// upgrades the config if necessary or returns an error, if the version +// isn't compatible. +func LoadConfigAtStartup(path string, cert tls.Certificate, evLogger events.Logger, allowNewerConfig, skipPortProbing bool) (config.Wrapper, error) { myID := protocol.NewDeviceID(cert.Certificate[0]) cfg, originalVersion, err := config.Load(path, myID, evLogger) if fs.IsNotExist(err) { - cfg, err = DefaultConfig(path, myID, evLogger, noDefaultFolder, skipPortProbing) + cfg, err = DefaultConfig(path, myID, evLogger, skipPortProbing) if err != nil { return nil, fmt.Errorf("failed to generate default config: %w", err) } From be002362b35bd57e66700374edc7a85f23335654 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Thu, 24 Apr 2025 03:29:30 +0200 Subject: [PATCH 24/62] fix(model): loop-break regression while block copying in puller (#10069) --- lib/model/folder_sendrecv.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 03718fb91..fe48a1b4b 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -1346,6 +1346,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch found := false blocks, _ := f.model.sdb.AllLocalBlocksWithHash(block.Hash) + innerBlocks: for _, e := range blocks { res, err := f.model.sdb.AllLocalFilesWithBlocksHashAnyFolder(e.BlocklistHash) if err != nil { @@ -1387,7 +1388,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } if err != nil { state.fail(fmt.Errorf("dst write: %w", err)) - break + break innerBlocks } if fi.Name == state.file.Name { state.copiedFromOrigin(block.Size) @@ -1395,7 +1396,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch state.copiedFromElsewhere(block.Size) } found = true - break + break innerBlocks } } } From ed252ed6d7ebe26a867121b418d21b120ae48f39 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sun, 27 Apr 2025 23:19:07 +0530 Subject: [PATCH 25/62] fix(sqlite): hold update lock while generating folder idx --- internal/db/sqlite/db_folderdb.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/db/sqlite/db_folderdb.go b/internal/db/sqlite/db_folderdb.go index f82946d2a..772d322f4 100644 --- a/internal/db/sqlite/db_folderdb.go +++ b/internal/db/sqlite/db_folderdb.go @@ -56,6 +56,10 @@ func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) { if dbName == "" { // First time we want to access this folder, need to create a new // folder ID + + s.updateLock.Lock() + defer s.updateLock.Unlock() + idx, err := s.folderIdxLocked(folder) if err != nil { return nil, wrap(err) From 47e3147d0b7137e54bacf468f506c22399e35c9c Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 30 Apr 2025 14:28:05 -0500 Subject: [PATCH 26/62] fix: don't hold main database update lock when tidying folder databases --- internal/db/sqlite/db_service.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/db/sqlite/db_service.go b/internal/db/sqlite/db_service.go index c28a6586c..635542ad5 100644 --- a/internal/db/sqlite/db_service.go +++ b/internal/db/sqlite/db_service.go @@ -80,13 +80,12 @@ func (s *Service) periodic(ctx context.Context) error { t0 := time.Now() l.Debugln("Periodic start") - s.sdb.updateLock.Lock() - defer s.sdb.updateLock.Unlock() - t1 := time.Now() defer func() { l.Debugln("Periodic done in", time.Since(t1), "+", t1.Sub(t0)) }() + s.sdb.updateLock.Lock() tidy(ctx, s.sdb.sql) + s.sdb.updateLock.Unlock() return wrap(s.sdb.forEachFolder(func(fdb *folderDB) error { fdb.updateLock.Lock() From d28be1b7117cfc0696f58167990b0b9fc78d2ce5 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 30 Apr 2025 14:34:49 -0500 Subject: [PATCH 27/62] fix: handle null database name in getGolderDB --- internal/db/sqlite/db_folderdb.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/db/sqlite/db_folderdb.go b/internal/db/sqlite/db_folderdb.go index 772d322f4..90e85d2ab 100644 --- a/internal/db/sqlite/db_folderdb.go +++ b/internal/db/sqlite/db_folderdb.go @@ -35,13 +35,18 @@ func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) { // Check for an existing database. If we're not supposed to create the // folder, we don't move on if it doesn't already have a database name. - var dbName string + var dbns sql.NullString if err := s.stmt(` SELECT database_name FROM folders WHERE folder_id = ? - `).Get(&dbName, folder); err != nil && !errors.Is(err, sql.ErrNoRows) { + `).Get(&dbns, folder); err != nil && !errors.Is(err, sql.ErrNoRows) { return nil, wrap(err) } + + var dbName string + if dbns.Valid { + dbName = dbns.String + } if dbName == "" && !create { return nil, errNoSuchFolder } From 58bf2b551554ae5a5060d2aca090f7b4f9d7d109 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Thu, 1 May 2025 17:15:02 +0200 Subject: [PATCH 28/62] fix(model): close fd immediately in copier (#10079) --- lib/model/folder_sendrecv.go | 83 +++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index fe48a1b4b..ab1f6affd 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -1344,7 +1344,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) - found := false + copied := false blocks, _ := f.model.sdb.AllLocalBlocksWithHash(block.Hash) innerBlocks: for _, e := range blocks { @@ -1355,47 +1355,19 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch for folderID, files := range res { ffs := folderFilesystems[folderID] for _, fi := range files { - fd, err := ffs.Open(fi.Name) + copied, err = f.copyBlock(fi.Name, e.Offset, dstFd, ffs, block, buf) if err != nil { - continue - } - defer fd.Close() - - _, err = fd.ReadAt(buf, e.Offset) - if err != nil { - fd.Close() - continue - } - - // Hash is not SHA256 as it's an encrypted hash token. In that - // case we can't verify the block integrity so we'll take it on - // trust. (The other side can and will verify.) - if f.Type != config.FolderTypeReceiveEncrypted { - if err := f.verifyBuffer(buf, block); err != nil { - l.Debugln("Finder failed to verify buffer", err) - continue - } - } - - if f.CopyRangeMethod != config.CopyRangeMethodStandard { - err = f.withLimiter(func() error { - dstFd.mut.Lock() - defer dstFd.mut.Unlock() - return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, e.Offset, block.Offset, int64(block.Size)) - }) - } else { - err = f.limitedWriteAt(dstFd, buf, block.Offset) - } - if err != nil { - state.fail(fmt.Errorf("dst write: %w", err)) + state.fail(err) break innerBlocks } + if !copied { + continue + } if fi.Name == state.file.Name { state.copiedFromOrigin(block.Size) } else { state.copiedFromElsewhere(block.Size) } - found = true break innerBlocks } } @@ -1405,7 +1377,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch break } - if !found { + if !copied { state.pullStarted() ps := pullBlockState{ sharedPullerState: state.sharedPullerState, @@ -1421,6 +1393,47 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } } +// Returns true, if the block was successfully copied to buf. The returned +// errors is *only* non-nil for errors that are fatal for pulling this entire +// file (i.e. writing to dstFd fails). For other errors that mean the block +// wasn't copied, but we can continue trying, the error is nil and the bool +// false. +// The buffer must be big enough to hold the block. It's only purpose is +// efficiency resp. reuse, the caller must not use/rely on its contents. +func (f *sendReceiveFolder) copyBlock(srcName string, srcOffset int64, dstFd *lockedWriterAt, ffs fs.Filesystem, block protocol.BlockInfo, buf []byte) (bool, error) { + fd, err := ffs.Open(srcName) + if err != nil { + l.Debugf("Failed to open file %v trying to copy block %v (folderID %v): %v", srcName, block.Hash, f.folderID, err) + return false, nil + } + defer fd.Close() + + _, err = fd.ReadAt(buf, srcOffset) + if err != nil { + l.Debugf("Failed to read block from file %v in copier (folderID: %v, hash: %v): %v", srcName, f.folderID, block.Hash, err) + return false, nil + } + + // Hash is not SHA256 as it's an encrypted hash token. In that + // case we can't verify the block integrity so we'll take it on + // trust. (The other side can and will verify.) + if f.Type != config.FolderTypeReceiveEncrypted { + if err := f.verifyBuffer(buf, block); err != nil { + l.Debugf("Failed to verify buffer in copier (folderID: %v): %v", f.folderID, err) + return false, nil + } + } + + if f.CopyRangeMethod != config.CopyRangeMethodStandard { + return true, f.withLimiter(func() error { + dstFd.mut.Lock() + defer dstFd.mut.Unlock() + return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, srcOffset, block.Offset, int64(block.Size)) + }) + } + return true, f.limitedWriteAt(dstFd, buf, block.Offset) +} + func (*sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) error { if len(buf) != int(block.Size) { return fmt.Errorf("length mismatch %d != %d", len(buf), block.Size) From f183d1cbec27f778dfe8e39c6f7db880bd09b9c5 Mon Sep 17 00:00:00 2001 From: xjtdy888 Date: Fri, 2 May 2025 02:36:35 +0800 Subject: [PATCH 29/62] chore(syncthing): ensure migrated database is closed before exiting (#10076) After opening the database, we performed some checks, such as whether the migration had already been successfully completed. If so, the function returned immediately, and the database was not closed. --------- Co-authored-by: Jakob Borg --- lib/syncthing/utils.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index e471a2403..9ab4b2ee3 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -170,11 +170,13 @@ func TryMigrateDatabase(deleteRetention time.Duration) error { // Apparently, not a valid old database return nil } + defer be.Close() sdb, err := sqlite.OpenForMigration(locations.Get(locations.Database)) if err != nil { return err } + defer sdb.Close() miscDB := db.NewMiscDB(sdb) if when, ok, err := miscDB.Time("migrated-from-leveldb-at"); err == nil && ok { @@ -265,8 +267,7 @@ func TryMigrateDatabase(deleteRetention time.Duration) error { _ = miscDB.PutTime("migrated-from-leveldb-at", time.Now()) _ = miscDB.PutString("migrated-from-leveldb-by", build.LongVersion) - be.Close() - sdb.Close() + _ = be.Close() _ = os.Rename(oldDBDir, oldDBDir+"-migrated") l.Infof("Migration complete, %d files and %dk blocks in %s", totFiles, totBlocks/1000, time.Since(t0).Truncate(time.Second)) From 6b945994677de7bb82ead2059681bb67493e8df4 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Thu, 1 May 2025 20:44:25 +0200 Subject: [PATCH 30/62] chore(db, model): simplify per hash DB lookup in copier (#10080) This is a draft because I haven't adjusted all the tests yet, I'd like to get feedback on the change overall first, before spending time on that. In my opinion the main win of this change is in it's lower complexity resp. fewer moving parts. It should also be faster as it only does one query instead of two, but I have no idea if that's practically relevant. This also mirrors the v1 DB, where a block map key had the name appended. Not that this is an argument for the change, it was mostly reassuring me that I might not be missing something key here conceptually (I might still be of course, please tell me :) ). And the change isn't mainly intrinsically motivated, instead it came up while fixing a bug in the copier. And the nested nature of that code makes the fix harder, and "un-nesting" it required me to understand what's happening. This change fell out of that. --- internal/db/interface.go | 4 +-- internal/db/metrics.go | 9 ++--- internal/db/sqlite/db_folderdb.go | 28 +++++----------- internal/db/sqlite/db_local_test.go | 26 ++++----------- internal/db/sqlite/db_test.go | 2 +- internal/db/sqlite/folderdb_local.go | 2 +- lib/model/folder_sendrecv.go | 50 ++++++++++++++-------------- lib/model/folder_sendrecv_test.go | 9 ++--- 8 files changed, 52 insertions(+), 78 deletions(-) diff --git a/internal/db/interface.go b/internal/db/interface.go index 15252d33c..2fc43ec23 100644 --- a/internal/db/interface.go +++ b/internal/db/interface.go @@ -41,8 +41,7 @@ type DB interface { AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[FileMetadata], func() error) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) - AllLocalBlocksWithHash(hash []byte) ([]BlockMapEntry, error) - AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]FileMetadata, error) + AllLocalBlocksWithHash(folder string, hash []byte) (iter.Seq[BlockMapEntry], func() error) // Cleanup DropAllFiles(folder string, device protocol.DeviceID) error @@ -88,6 +87,7 @@ type BlockMapEntry struct { Offset int64 BlockIndex int Size int + FileName string } type KeyValue struct { diff --git a/internal/db/metrics.go b/internal/db/metrics.go index f906bceed..fb41124cf 100644 --- a/internal/db/metrics.go +++ b/internal/db/metrics.go @@ -67,11 +67,6 @@ func (m metricsDB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Se return m.DB.AllLocalFilesWithBlocksHash(folder, h) } -func (m metricsDB) AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]FileMetadata, error) { - defer m.account("-", "AllLocalFilesWithBlocksHashAnyFolder")() - return m.DB.AllLocalFilesWithBlocksHashAnyFolder(hash) -} - func (m metricsDB) AllGlobalFiles(folder string) (iter.Seq[FileMetadata], func() error) { defer m.account(folder, "AllGlobalFiles")() return m.DB.AllGlobalFiles(folder) @@ -107,9 +102,9 @@ func (m metricsDB) GetGlobalAvailability(folder, file string) ([]protocol.Device return m.DB.GetGlobalAvailability(folder, file) } -func (m metricsDB) AllLocalBlocksWithHash(hash []byte) ([]BlockMapEntry, error) { +func (m metricsDB) AllLocalBlocksWithHash(folder string, hash []byte) (iter.Seq[BlockMapEntry], func() error) { defer m.account("-", "AllLocalBlocksWithHash")() - return m.DB.AllLocalBlocksWithHash(hash) + return m.DB.AllLocalBlocksWithHash(folder, hash) } func (m metricsDB) Close() error { diff --git a/internal/db/sqlite/db_folderdb.go b/internal/db/sqlite/db_folderdb.go index 90e85d2ab..5b45713f0 100644 --- a/internal/db/sqlite/db_folderdb.go +++ b/internal/db/sqlite/db_folderdb.go @@ -16,7 +16,6 @@ import ( "time" "github.com/syncthing/syncthing/internal/db" - "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/rand" @@ -154,24 +153,15 @@ func (s *DB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[db.Fil return fdb.AllGlobalFilesPrefix(prefix) } -func (s *DB) AllLocalBlocksWithHash(hash []byte) ([]db.BlockMapEntry, error) { - var entries []db.BlockMapEntry - err := s.forEachFolder(func(fdb *folderDB) error { - es, err := itererr.Collect(fdb.AllLocalBlocksWithHash(hash)) - entries = append(entries, es...) - return err - }) - return entries, err -} - -func (s *DB) AllLocalFilesWithBlocksHashAnyFolder(hash []byte) (map[string][]db.FileMetadata, error) { - res := make(map[string][]db.FileMetadata) - err := s.forEachFolder(func(fdb *folderDB) error { - files, err := itererr.Collect(fdb.AllLocalFilesWithBlocksHash(hash)) - res[fdb.folderID] = files - return err - }) - return res, err +func (s *DB) AllLocalBlocksWithHash(folder string, hash []byte) (iter.Seq[db.BlockMapEntry], func() error) { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return func(yield func(db.BlockMapEntry) bool) {}, func() error { return nil } + } + if err != nil { + return func(yield func(db.BlockMapEntry) bool) {}, func() error { return err } + } + return fdb.AllLocalBlocksWithHash(hash) } func (s *DB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) { diff --git a/internal/db/sqlite/db_local_test.go b/internal/db/sqlite/db_local_test.go index b1e98954e..6932b7f3d 100644 --- a/internal/db/sqlite/db_local_test.go +++ b/internal/db/sqlite/db_local_test.go @@ -9,6 +9,7 @@ package sqlite import ( "testing" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/protocol" ) @@ -50,7 +51,7 @@ func TestBlocks(t *testing.T) { // Search for blocks - vals, err := db.AllLocalBlocksWithHash([]byte{1, 2, 3}) + vals, err := itererr.Collect(db.AllLocalBlocksWithHash(folderID, []byte{1, 2, 3})) if err != nil { t.Fatal(err) } @@ -61,26 +62,13 @@ func TestBlocks(t *testing.T) { t.Log(vals[0]) t.Fatal("bad entry") } - - // Get FileInfos for those blocks - - res, err := db.AllLocalFilesWithBlocksHashAnyFolder(vals[0].BlocklistHash) - if err != nil { - t.Fatal(err) - } - if len(res) != 1 { - t.Fatal("should return one folder") - } - if len(res[folderID]) != 1 { - t.Fatal("should find one file") - } - if res[folderID][0].Name != "file1" { + if vals[0].FileName != "file1" { t.Fatal("should be file1") } // Get the other blocks - vals, err = db.AllLocalBlocksWithHash([]byte{3, 4, 5}) + vals, err = itererr.Collect(db.AllLocalBlocksWithHash(folderID, []byte{3, 4, 5})) if err != nil { t.Fatal(err) } @@ -119,7 +107,7 @@ func TestBlocksDeleted(t *testing.T) { // We should find one entry for the block hash search := file.Blocks[0].Hash - es, err := sdb.AllLocalBlocksWithHash(search) + es, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, search)) if err != nil { t.Fatal(err) } @@ -134,7 +122,7 @@ func TestBlocksDeleted(t *testing.T) { } // Searching for the old hash should yield no hits - if hits, err := sdb.AllLocalBlocksWithHash(search); err != nil { + if hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, search)); err != nil { t.Fatal(err) } else if len(hits) != 0 { t.Log(hits) @@ -142,7 +130,7 @@ func TestBlocksDeleted(t *testing.T) { } // Searching for the new hash should yield one hits - if hits, err := sdb.AllLocalBlocksWithHash(file.Blocks[0].Hash); err != nil { + if hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, file.Blocks[0].Hash)); err != nil { t.Fatal(err) } else if len(hits) != 1 { t.Log(hits) diff --git a/internal/db/sqlite/db_test.go b/internal/db/sqlite/db_test.go index 44c9a5d68..e1ccc44a3 100644 --- a/internal/db/sqlite/db_test.go +++ b/internal/db/sqlite/db_test.go @@ -1084,7 +1084,7 @@ func TestInsertLargeFile(t *testing.T) { // Verify all the blocks are here for i, block := range files[0].Blocks { - bs, err := sdb.AllLocalBlocksWithHash(block.Hash) + bs, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, block.Hash)) if err != nil { t.Fatal(err) } diff --git a/internal/db/sqlite/folderdb_local.go b/internal/db/sqlite/folderdb_local.go index 58d35fb05..563174a73 100644 --- a/internal/db/sqlite/folderdb_local.go +++ b/internal/db/sqlite/folderdb_local.go @@ -99,7 +99,7 @@ func (s *folderDB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntr // & blocklists is deferred (garbage collected) while the files list is // not. This filters out blocks that are in fact deleted. return iterStructs[db.BlockMapEntry](s.stmt(` - SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size FROM files f + SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size, f.name as filename FROM files f LEFT JOIN blocks b ON f.blocklist_hash = b.blocklist_hash WHERE f.device_idx = {{.LocalDeviceIdx}} AND b.hash = ? `).Queryx(hash)) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index ab1f6affd..bff10380a 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -1343,33 +1343,33 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) - copied := false - blocks, _ := f.model.sdb.AllLocalBlocksWithHash(block.Hash) - innerBlocks: - for _, e := range blocks { - res, err := f.model.sdb.AllLocalFilesWithBlocksHashAnyFolder(e.BlocklistHash) - if err != nil { - continue - } - for folderID, files := range res { - ffs := folderFilesystems[folderID] - for _, fi := range files { - copied, err = f.copyBlock(fi.Name, e.Offset, dstFd, ffs, block, buf) - if err != nil { - state.fail(err) - break innerBlocks - } - if !copied { - continue - } - if fi.Name == state.file.Name { - state.copiedFromOrigin(block.Size) - } else { - state.copiedFromElsewhere(block.Size) - } - break innerBlocks + + folders: + for folderID, ffs := range folderFilesystems { + for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(folderID, block.Hash)) { + if err != nil { + // We just ignore this and continue pulling instead (though + // there's a good chance that will fail too, if the DB is + // unhealthy). + l.Debugf("Failed to get information from DB about block %v in copier (folderID %v, file %v): %v", block.Hash, f.folderID, state.file.Name) + break } + + copied, err = f.copyBlock(e.FileName, e.Offset, dstFd, ffs, block, buf) + if err != nil { + state.fail(err) + break folders + } + if !copied { + continue + } + if e.FileName == state.file.Name { + state.copiedFromOrigin(block.Size) + } else { + state.copiedFromElsewhere(block.Size) + } + break folders } } diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index e307670da..a2094d3f0 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/syncthing/syncthing/internal/itererr" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" @@ -325,11 +326,11 @@ func TestCopierCleanup(t *testing.T) { // Update index (removing old blocks) f.updateLocalsFromScanning([]protocol.FileInfo{file}) - if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[0].Hash); err != nil || len(vals) > 0 { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(f.ID, blocks[0].Hash)); err != nil || len(vals) > 0 { t.Error("Unexpected block found") } - if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[1].Hash); err != nil || len(vals) == 0 { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(f.ID, blocks[1].Hash)); err != nil || len(vals) == 0 { t.Error("Expected block not found") } @@ -338,11 +339,11 @@ func TestCopierCleanup(t *testing.T) { // Update index (removing old blocks) f.updateLocalsFromScanning([]protocol.FileInfo{file}) - if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[0].Hash); err != nil || len(vals) == 0 { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(f.ID, blocks[0].Hash)); err != nil || len(vals) == 0 { t.Error("Unexpected block found") } - if vals, err := m.sdb.AllLocalBlocksWithHash(blocks[1].Hash); err != nil || len(vals) > 0 { + if vals, err := itererr.Collect(m.sdb.AllLocalBlocksWithHash(f.ID, blocks[1].Hash)); err != nil || len(vals) > 0 { t.Error("Expected block not found") } } From fa7b81e1cf3a38350253b3583e3326fc25dc2c41 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Fri, 2 May 2025 13:15:26 +0200 Subject: [PATCH 31/62] fix(model): use same folder first in copier (#10093) Where `folderFilesystems` and `folders` is built, there's a comment spelling out the purpose: To have the same folder first, as that's the most likely to get hits. Plus a copy is possibly more efficient than from another folder, e.g. if that's on a different filesystem. We lost that behaviour during some unrelated change. (Also sneaking in a comment fix on yesterdays change.) --- lib/model/folder_sendrecv.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index bff10380a..101324b9e 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -1346,7 +1346,10 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch copied := false folders: - for folderID, ffs := range folderFilesystems { + // Intentionally not iterating over `folderFilesystems` directly, + // to preserve order (same folder first). + for _, folderID := range folders { + ffs := folderFilesystems[folderID] for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(folderID, block.Hash)) { if err != nil { // We just ignore this and continue pulling instead (though @@ -1393,7 +1396,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } } -// Returns true, if the block was successfully copied to buf. The returned +// Returns true, if the block was successfully copied. The returned // errors is *only* non-nil for errors that are fatal for pulling this entire // file (i.e. writing to dstFd fails). For other errors that mean the block // wasn't copied, but we can continue trying, the error is nil and the bool From 821d6f43ac3520d7ab3e7dadb9c740e27ea64b93 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Sun, 4 May 2025 09:23:57 +0200 Subject: [PATCH 32/62] chore(model): refactor copier for more flatness (#10094) Flattened the copier code more. Also removing and moving some parameters/return values to simplify things. Generally rely less on return values, e.g. by handling errors right away and using `state` to do the right thing (e.g. abort on failure). Supposed to be a refactor without any behaviour changes, except for fixing a tiny regression on folder order: We used to try copying from the same folder first, but lost that property at some point (also sent a PR fixing only that, I'd merge that first making this refactor only). --- lib/model/folder_sendrecv.go | 161 +++++++++++++++++------------- lib/model/folder_sendrecv_test.go | 12 +++ 2 files changed, 106 insertions(+), 67 deletions(-) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 101324b9e..eb1cf83b4 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -1291,14 +1291,12 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch protocol.BufferPool.Put(buf) }() - folderFilesystems := make(map[string]fs.Filesystem) - // Hope that it's usually in the same folder, so start with that one. - folders := []string{f.folderID} + otherFolderFilesystems := make(map[string]fs.Filesystem) for folder, cfg := range f.model.cfg.Folders() { - folderFilesystems[folder] = cfg.Filesystem() - if folder != f.folderID { - folders = append(folders, folder) + if folder == f.ID { + continue } + otherFolderFilesystems[folder] = cfg.Filesystem() } for state := range in { @@ -1309,13 +1307,6 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch continue } - dstFd, err := state.tempFile() - if err != nil { - // Nothing more to do for this failed file, since we couldn't create a temporary for it. - out <- state.sharedPullerState - continue - } - if f.Type != config.FolderTypeReceiveEncrypted { f.model.progressEmitter.Register(state.sharedPullerState) } @@ -1342,53 +1333,27 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch continue } - buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) - copied := false - - folders: - // Intentionally not iterating over `folderFilesystems` directly, - // to preserve order (same folder first). - for _, folderID := range folders { - ffs := folderFilesystems[folderID] - for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(folderID, block.Hash)) { - if err != nil { - // We just ignore this and continue pulling instead (though - // there's a good chance that will fail too, if the DB is - // unhealthy). - l.Debugf("Failed to get information from DB about block %v in copier (folderID %v, file %v): %v", block.Hash, f.folderID, state.file.Name) - break - } - - copied, err = f.copyBlock(e.FileName, e.Offset, dstFd, ffs, block, buf) - if err != nil { - state.fail(err) - break folders - } - if !copied { - continue - } - if e.FileName == state.file.Name { - state.copiedFromOrigin(block.Size) - } else { - state.copiedFromElsewhere(block.Size) - } - break folders - } + if f.copyBlock(block, state, otherFolderFilesystems, buf) { + state.copyDone(block) + continue } if state.failed() != nil { break } - if !copied { - state.pullStarted() - ps := pullBlockState{ - sharedPullerState: state.sharedPullerState, - block: block, - } - pullChan <- ps - } else { - state.copyDone(block) + state.pullStarted() + ps := pullBlockState{ + sharedPullerState: state.sharedPullerState, + block: block, + } + pullChan <- ps + } + // If there are no blocks to pull/copy, we still need the temporary file in place. + if len(state.blocks) == 0 { + _, err := state.tempFile() + if err != nil { + state.fail(err) } } @@ -1396,25 +1361,75 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } } -// Returns true, if the block was successfully copied. The returned -// errors is *only* non-nil for errors that are fatal for pulling this entire -// file (i.e. writing to dstFd fails). For other errors that mean the block -// wasn't copied, but we can continue trying, the error is nil and the bool -// false. -// The buffer must be big enough to hold the block. It's only purpose is -// efficiency resp. reuse, the caller must not use/rely on its contents. -func (f *sendReceiveFolder) copyBlock(srcName string, srcOffset int64, dstFd *lockedWriterAt, ffs fs.Filesystem, block protocol.BlockInfo, buf []byte) (bool, error) { +// Returns true when the block was successfully copied. +func (f *sendReceiveFolder) copyBlock(block protocol.BlockInfo, state copyBlocksState, otherFolderFilesystems map[string]fs.Filesystem, buf []byte) bool { + buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) + + // Hope that it's usually in the same folder, so start with that + // one. Also possibly more efficient copy (same filesystem). + if f.copyBlockFromFolder(f.ID, block, state, f.mtimefs, buf) { + return true + } + if state.failed() != nil { + return false + } + + for folderID, ffs := range otherFolderFilesystems { + if f.copyBlockFromFolder(folderID, block, state, ffs, buf) { + return true + } + if state.failed() != nil { + return false + } + } + + return false +} + +// Returns true when the block was successfully copied. +// The passed buffer must be large enough to accommodate the block. +func (f *sendReceiveFolder) copyBlockFromFolder(folderID string, block protocol.BlockInfo, state copyBlocksState, ffs fs.Filesystem, buf []byte) bool { + for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(folderID, block.Hash)) { + if err != nil { + // We just ignore this and continue pulling instead (though + // there's a good chance that will fail too, if the DB is + // unhealthy). + l.Debugf("Failed to get information from DB about block %v in copier (folderID %v, file %v): %v", block.Hash, f.folderID, state.file.Name) + return false + } + + if !f.copyBlockFromFile(e.FileName, e.Offset, state, ffs, block, buf) { + if state.failed() != nil { + return false + } + continue + } + + if e.FileName == state.file.Name { + state.copiedFromOrigin(block.Size) + } else { + state.copiedFromElsewhere(block.Size) + } + return true + } + + return false +} + +// Returns true when the block was successfully copied. +// The passed buffer must be large enough to accommodate the block. +func (f *sendReceiveFolder) copyBlockFromFile(srcName string, srcOffset int64, state copyBlocksState, ffs fs.Filesystem, block protocol.BlockInfo, buf []byte) bool { fd, err := ffs.Open(srcName) if err != nil { l.Debugf("Failed to open file %v trying to copy block %v (folderID %v): %v", srcName, block.Hash, f.folderID, err) - return false, nil + return false } defer fd.Close() _, err = fd.ReadAt(buf, srcOffset) if err != nil { l.Debugf("Failed to read block from file %v in copier (folderID: %v, hash: %v): %v", srcName, f.folderID, block.Hash, err) - return false, nil + return false } // Hash is not SHA256 as it's an encrypted hash token. In that @@ -1423,18 +1438,30 @@ func (f *sendReceiveFolder) copyBlock(srcName string, srcOffset int64, dstFd *lo if f.Type != config.FolderTypeReceiveEncrypted { if err := f.verifyBuffer(buf, block); err != nil { l.Debugf("Failed to verify buffer in copier (folderID: %v): %v", f.folderID, err) - return false, nil + return false } } + dstFd, err := state.tempFile() + if err != nil { + // State is already marked as failed when an error is returned here. + return false + } + if f.CopyRangeMethod != config.CopyRangeMethodStandard { - return true, f.withLimiter(func() error { + err = f.withLimiter(func() error { dstFd.mut.Lock() defer dstFd.mut.Unlock() return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, srcOffset, block.Offset, int64(block.Size)) }) + } else { + err = f.limitedWriteAt(dstFd, buf, block.Offset) } - return true, f.limitedWriteAt(dstFd, buf, block.Offset) + if err != nil { + state.fail(fmt.Errorf("dst write: %w", err)) + return false + } + return true } func (*sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) error { diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index a2094d3f0..822a567a4 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -14,6 +14,7 @@ import ( "io" "os" "path/filepath" + "runtime/pprof" "strconv" "strings" "testing" @@ -680,6 +681,17 @@ func TestCopyOwner(t *testing.T) { expGroup = 5678 ) + // This test hung on a regression, taking a long time to fail - speed that up. + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + go func() { + <-ctx.Done() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + pprof.Lookup("goroutine").WriteTo(os.Stdout, 2) + panic("timed out before test finished") + } + }() + // Set up a folder with the CopyParentOwner bit and backed by a fake // filesystem. From 964c8d7d6522a3bebcf5e8de61f2bb914f987e50 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 16 May 2025 22:50:13 +0200 Subject: [PATCH 33/62] fix(model): correct bufferpool handling; simplify (#10113) The copier routine refactor resulted in bad buffer pool handling, putting a buffer back into the pool twice. This simplifies and removes the danger prone Upgrade() method. --- lib/model/folder_sendrecv.go | 15 +++++++-------- lib/protocol/bufferpool.go | 16 +--------------- lib/protocol/protocol.go | 13 +++++-------- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index eb1cf83b4..8ab78400f 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -1286,11 +1286,6 @@ func (f *sendReceiveFolder) shortcutFile(file protocol.FileInfo, dbUpdateChan ch // copierRoutine reads copierStates until the in channel closes and performs // the relevant copies when possible, or passes it to the puller routine. func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBlockState, out chan<- *sharedPullerState) { - buf := protocol.BufferPool.Get(protocol.MinBlockSize) - defer func() { - protocol.BufferPool.Put(buf) - }() - otherFolderFilesystems := make(map[string]fs.Filesystem) for folder, cfg := range f.model.cfg.Folders() { if folder == f.ID { @@ -1333,7 +1328,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch continue } - if f.copyBlock(block, state, otherFolderFilesystems, buf) { + if f.copyBlock(block, state, otherFolderFilesystems) { state.copyDone(block) continue } @@ -1362,8 +1357,9 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } // Returns true when the block was successfully copied. -func (f *sendReceiveFolder) copyBlock(block protocol.BlockInfo, state copyBlocksState, otherFolderFilesystems map[string]fs.Filesystem, buf []byte) bool { - buf = protocol.BufferPool.Upgrade(buf, int(block.Size)) +func (f *sendReceiveFolder) copyBlock(block protocol.BlockInfo, state copyBlocksState, otherFolderFilesystems map[string]fs.Filesystem) bool { + buf := protocol.BufferPool.Get(int(block.Size)) + defer protocol.BufferPool.Put(buf) // Hope that it's usually in the same folder, so start with that // one. Also possibly more efficient copy (same filesystem). @@ -1978,6 +1974,9 @@ func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, scanChan c return nil } cf, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, path) + if err != nil { + return err + } switch { case !ok || cf.IsDeleted(): // Something appeared in the dir that we either are not diff --git a/lib/protocol/bufferpool.go b/lib/protocol/bufferpool.go index cf7b797b6..fa8722d65 100644 --- a/lib/protocol/bufferpool.go +++ b/lib/protocol/bufferpool.go @@ -59,7 +59,7 @@ func (p *bufferPool) Get(size int) []byte { } // Put makes the given byte slice available again in the global pool. -// You must only Put() slices that were returned by Get() or Upgrade(). +// You must only Put() slices that were returned by Get(). func (p *bufferPool) Put(bs []byte) { // Don't buffer slices outside of our pool range if cap(bs) > MaxBlockSize || cap(bs) < MinBlockSize { @@ -72,20 +72,6 @@ func (p *bufferPool) Put(bs []byte) { p.pools[bkt].Put(&bs) } -// Upgrade grows the buffer to the requested size, while attempting to reuse -// it if possible. -func (p *bufferPool) Upgrade(bs []byte, size int) []byte { - if cap(bs) >= size { - // Reslicing is enough, lets go! - return bs[:size] - } - - // It was too small. But it pack into the pool and try to get another - // buffer. - p.Put(bs) - return p.Get(size) -} - // getBucketForLen returns the bucket where we should get a slice of a // certain length. Each bucket is guaranteed to hold slices that are // precisely the block size for that bucket, so if the block size is larger diff --git a/lib/protocol/protocol.go b/lib/protocol/protocol.go index bf35f50b6..5a0390172 100644 --- a/lib/protocol/protocol.go +++ b/lib/protocol/protocol.go @@ -512,8 +512,9 @@ func (c *rawConnection) readMessageAfterHeader(hdr *bep.Header, fourByteBuf []by // Then comes the message buf := BufferPool.Get(int(msgLen)) + defer BufferPool.Put(buf) + if _, err := io.ReadFull(c.cr, buf); err != nil { - BufferPool.Put(buf) return nil, fmt.Errorf("reading message: %w", err) } @@ -525,7 +526,6 @@ func (c *rawConnection) readMessageAfterHeader(hdr *bep.Header, fourByteBuf []by case bep.MessageCompression_MESSAGE_COMPRESSION_LZ4: decomp, err := lz4Decompress(buf) - BufferPool.Put(buf) if err != nil { return nil, fmt.Errorf("decompressing message: %w", err) } @@ -541,14 +541,11 @@ func (c *rawConnection) readMessageAfterHeader(hdr *bep.Header, fourByteBuf []by msg, err := newMessage(hdr.Type) if err != nil { - BufferPool.Put(buf) return nil, err } if err := proto.Unmarshal(buf, msg); err != nil { - BufferPool.Put(buf) return nil, fmt.Errorf("unmarshalling message: %w", err) } - BufferPool.Put(buf) return msg, nil } @@ -567,16 +564,16 @@ func (c *rawConnection) readHeader(fourByteBuf []byte) (*bep.Header, error) { // Then comes the header buf := BufferPool.Get(int(hdrLen)) + defer BufferPool.Put(buf) + if _, err := io.ReadFull(c.cr, buf); err != nil { - BufferPool.Put(buf) return nil, fmt.Errorf("reading header: %w", err) } var hdr bep.Header err := proto.Unmarshal(buf, &hdr) - BufferPool.Put(buf) if err != nil { - return nil, fmt.Errorf("unmarshalling header: %w %x", err, buf) + return nil, fmt.Errorf("unmarshalling header: %w", err) } metricDeviceRecvDecompressedBytes.WithLabelValues(c.idString).Add(float64(2 + len(buf))) From 4bc17bc58802905353f52e5182335fe3922de49a Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 17 May 2025 00:37:36 +0200 Subject: [PATCH 34/62] build: use Go 1.24.2 specifically https://github.com/golang/go/issues/73617 --- .github/workflows/build-syncthing.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index c4a64d4c3..e66721c17 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -10,7 +10,7 @@ env: # The go version to use for builds. We set check-latest to true when # installing, so we get the latest patch version that matches the # expression. - GO_VERSION: "~1.24.0" + GO_VERSION: "1.24.2" # Optimize compatibility on the slow archictures. GOMIPS: softfloat From a4db309b394d1049d2bce37f58222f5df6336680 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 17 May 2025 16:54:02 +0200 Subject: [PATCH 35/62] chore: refine author list generation Now applies a slightly narrower definition of "author", being one that has touched code in a file that exists today (tracking renames). --- AUTHORS | 101 ++---- .../syncthing/core/aboutModalView.html | 2 +- script/authors.go | 329 +++++++++--------- 3 files changed, 183 insertions(+), 249 deletions(-) diff --git a/AUTHORS b/AUTHORS index bda3bb73c..617acadc7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,103 +13,96 @@ # contents of this file. # -Aaron Bieber (qbit) +Jakob Borg (calmh) +Audrius Butkevicius (AudriusButkevicius) +Simon Frei (imsodin) +Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com> +Alexander Graf (alex2108) +Alexandre Viau (aviau) +Anderson Mesquita (andersonvom) +André Colomb (acolomb) +Antony Male (canton7) +Ben Schulz (uok) +bt90 +Caleb Callaway (cqcallaw) +Daniel Harte (norgeous) +Emil Lundberg +Eric P +Evgeny Kuznetsov +greatroar <61184462+greatroar@users.noreply.github.com> +Lars K.W. Gohlke (lkwg82) +Lode Hoste (Zillode) +Michael Ploujnikov (plouj) +Ross Smith II (rasa) +Stefan Tatschner (rumpelsepp) +Wulf Weich (wweich) Adam Piggott (ProactiveServices) Adel Qalieh (adelq) -Alan Pope -Alberto Donato Aleksey Vasenev Alessandro G. (alessandro.g89) Alex Ionescu Alex Lindeman <139387+aelindeman@users.noreply.github.com> Alex Xu -Alexander Graf (alex2108) Alexander Seiler Alexandre Alves -Alexandre Viau (aviau) Aman Gupta -Anatoli Babenia -Anderson Mesquita (andersonvom) Andreas Sommer andresvia -Andrew Dunham (andrew-d) -Andrew Meyer Andrew Rabert (nvllsvm) <6550543+nvllsvm@users.noreply.github.com> Andrey D (scienmind) -André Colomb (acolomb) andyleap Anjan Momi Anthony Goeckner Antoine Lamielle (0x010C) -Antony Male (canton7) Anur Aranjedeath Arkadiusz Tymiński Aroun Arthur Axel fREW Schmidt (frioux) Artur Zubilewicz -Audrius Butkevicius (AudriusButkevicius) Aurélien Rainone <476650+arl@users.noreply.github.com> BAHADIR YILMAZ Bart De Vries (mogwa1) Beat Reichenbach <44111292+beatreichenbach@users.noreply.github.com> -Ben Curthoys (bencurthoys) -Ben Schulz (uok) Ben Shepherd (benshep) Ben Sidhom (bsidhom) Benedikt Heine (bebehei) -Benedikt Morbach -Benjamin Nater <17193640+bn4t@users.noreply.github.com> Benno Fünfstück Benny Ng (tpng) boomsquared <54829195+boomsquared@users.noreply.github.com> Boqin Qin Boris Rybalkin -Brandon Philips (philips) Brendan Long (brendanlong) -Brian R. Becker (brbecker) -bt90 -Caleb Callaway (cqcallaw) -Carsten Hagemann (carstenhag) Catfriend1 <16361913+Catfriend1@users.noreply.github.com> Cathryne Linenweaver (Cathryne) Cedric Staniewski (xduugu) -chenrui Chih-Hsuan Yen <1937689+yan12125@users.noreply.github.com> Choongkyu Chris Howie (cdhowie) Chris Joel (cdata) -Chris Tonkinson Christian Kujau Christian Prescott chucic cjc7373 Colin Kennedy (moshen) Cromefire_ <26320625+cromefire@users.noreply.github.com> -cui fliter Cyprien Devillez d-volution <49024624+d-volution@users.noreply.github.com> -Dale Visser Dan Daniel Barczyk <46358936+DanielBarczyk@users.noreply.github.com> Daniel Bergmann (brgmnn) -Daniel Harte (norgeous) Daniel Martí (mvdan) Daniel Padrta <64928366+danpadcz@users.noreply.github.com> Darshil Chanpura (dtchanpura) dashangcun <907225865@qq.com> David Rimmer (dinosore) -deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com> DeflateAwning <11021263+DeflateAwning@users.noreply.github.com> Denis A. (dva) Dennis Wilson (snnd) -dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> -dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> derekriemer DerRockWolf <50499906+DerRockWolf@users.noreply.github.com> desbma Devon G. Redekopp -diemade digital Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> Dmitry Saveliev (dsaveliev) @@ -119,14 +112,11 @@ Dominik Heidler (asdil12) Elias Jarlebring (jarlebring) Elliot Huffman Emil Hessman (ceh) -Emil Lundberg Eng Zer Jun entity0xfe <109791748+entity0xfe@users.noreply.github.com> Eric Lesiuta -Eric P Erik Meitner (WSGCSysadmin) Evan Spensley <94762716+0evan@users.noreply.github.com> -Evgeny Kuznetsov Federico Castagnini (facastagnini) Felix <53702818+f-eliks@users.noreply.github.com> Felix Ableitner (Nutomic) @@ -140,7 +130,6 @@ ghjklw Gilli Sigurdsson (gillisig) Gleb Sinyavskiy Graham Miln (grahammiln) -greatroar <61184462+greatroar@users.noreply.github.com> Greg guangwu gudvinr @@ -155,50 +144,35 @@ Hugo Locurcio Iain Barnett Ian Johnson (anonymouse64) ignacy123 -Ikko Ashimine -Ilya Brin <464157+ilyabrin@users.noreply.github.com> Iskander Sharipov (Alex) Jaakko Hannikainen (jgke) -Jacek Szafarkiewicz (hadogenes) Jack Croft Jacob Jake Peterson (acogdev) -Jakob Borg (calmh) James O'Beirne James Patterson (jpjp) -janost Jaroslav Lichtblau Jaroslav Malec (dzarda) -jaseg Jaspitta -Jauder Ho Jaya Chithra (jayachithra) Jaya Kumar Jeffery To jelle van der Waa Jens Diemer (jedie) -Jerry Jacobs (xor-gate) -Jesse Lucas Jochen Voss (seehuhn) -Johan Andersson Johan Vromans (sciurius) John Rinehart (fuzzybear3965) Jonas Thelemann Jonathan -Jonathan Cross -Jonta <359397+Jonta@users.noreply.github.com> Jose Manuel Delicado (jmdaweb) jtagcat Julian Lehrhuber Jörg Thalheim Jędrzej Kula -K.B.Dharun Krishna -Kalle Laine Kapil Sareen Karol Różycki (krozycki) Kebin Liu Keith Harrison -Keith Turner Kelong Cong (kc1212) Ken'ichi Kamada (kamadak) Kevin Allen (ironmig) @@ -207,30 +181,23 @@ Kevin White, Jr. (kwhite17) klemens Kurt Fitzner (Kudalufi) kylosus <33132401+kylosus@users.noreply.github.com> -Lars K.W. Gohlke (lkwg82) Lars Lehtonen -Laurent Arnoud Laurent Etiemble (letiemble) Leo Arias (elopio) Liu Siyuan (liusy182) -Lode Hoste (Zillode) Lord Landon Agahnim (LordLandon) LSmithx2 <42276854+lsmithx2@users.noreply.github.com> -luchenhan <168071714+luchenhan@users.noreply.github.com> Lukas Lihotzki Luke Hamburg <1992842+luckman212@users.noreply.github.com> luzpaz Majed Abdulaziz (majedev) Marc Laporte (marclaporte) -Marc Pujol (kilburn) Marcin Dziadus (marcindziadus) -marco-m Marcus B Spencer Marcus Legendre Mario Majila Mark Pulford (mpx) Martchus -Martin Polehla Mateusz Naściszewski (mateon1) Mateusz Ż mathias4833 <67101597+mathias4833@users.noreply.github.com> @@ -241,15 +208,10 @@ Matteo Ruina Maurizio Tomasi Max Max Schulze (kralo) -maxice8 <30738253+maxice8@users.noreply.github.com> MaximAL -Maxime Thirouin Maximilian -mclang <1721600+mclang@users.noreply.github.com> Michael Jephcote (Rewt0r) -Michael Ploujnikov (plouj) Michael Rienstra -Michael Tilli (pyfisch) MichaIng Migelo Mike Boone @@ -258,7 +220,6 @@ MikolajTwarog <43782609+MikolajTwarog@users.noreply.github.com> Mingxuan Lin mv1005 <49659413+mv1005@users.noreply.github.com> Nate Morrison (nrm21) -Naveen <172697+naveensrinivasan@users.noreply.github.com> nf Nicholas Rishel (PrototypeNM1) Nick Busey @@ -273,7 +234,6 @@ NoLooseEnds Oliver Freyermuth orangekame3 otbutz -Otiel overkill <22098433+0verk1ll@users.noreply.github.com> Oyebanji Jacob Mayowa Pablo @@ -281,7 +241,6 @@ Pascal Jungblut (pascalj) Paul Brit Paul Donald Pawel Palenica (qepasa) -Paweł Rozlach perewa Peter Badida Peter Dave Hello @@ -291,19 +250,15 @@ Phani Rithvij Phil Davis Philippe Schommers (filoozoom) Phill Luby (pluby) -Pier Paolo Ramon Piotr Bejda (piobpl) polyfloyd -Pramodh KP (pramodhkp) <1507241+pramodhkp@users.noreply.github.com> Quentin Hibon Rahmi Pruitt red_led -Richard Hartmann Robert Carosi (nov1n) Roberto Santalla Robin Schoonover Roman Zaynetdinov (zaynetro) -Ross Smith II (rasa) rubenbe Ruslan Yevdokymov <38809160+ruslanye@users.noreply.github.com> Ryan Qian @@ -315,18 +270,14 @@ Sergey Mishin (ralder) Sertonix <83883937+Sertonix@users.noreply.github.com> Severin von Wnuck-Lipinski Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> -Simon Frei (imsodin) Simon Mwepu Simon Pickup Sly_tom_cat Sonu Kumar Saw <31889738+dev-saw99@users.noreply.github.com> Stefan Kuntz (Stefan-Code) -Stefan Tatschner (rumpelsepp) Steven Eckhoff Suhas Gundimeda (snugghash) Sven Bachmann -Syncthing Automation -Syncthing Release Automation Sébastien WENSKE Taylor Khan (nelsonkhan) Terrance @@ -335,14 +286,11 @@ Thomas <9749173+uhthomas@users.noreply.github.com> Thomas Hipp Tim Abell (timabell) Tim Howes (timhowes) -Tim Nordenfur Tobias Frölich <40638719+tobifroe@users.noreply.github.com> Tobias Klauser Tobias Nygren (tnn2) Tobias Tom (tobiastom) Tom Jakubowski -Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com> -Tommy Thorn Tommy van der Vorst Tully Robinson (tojrobinson) Tyler Brazier (tylerbrazier) @@ -360,10 +308,9 @@ WangXi Will Rouesnel William A. Kennington III (wkennington) wouter bolsterlee -Wulf Weich (wweich) xarx00 Xavier O. (damajor) -xjtdy888 (xjtdy888) +xjtdy888 (xjtdy888) Yannic A. (eipiminus1) 佛跳墙 落心 diff --git a/gui/default/syncthing/core/aboutModalView.html b/gui/default/syncthing/core/aboutModalView.html index d040801fa..3d2240c4f 100644 --- a/gui/default/syncthing/core/aboutModalView.html +++ b/gui/default/syncthing/core/aboutModalView.html @@ -30,7 +30,7 @@

    The Syncthing Authors

    -Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ross Smith II, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, bt90, greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Anatoli Babenia, Andreas Sommer, Andrew Dunham, Andrew Meyer, Andrew Rabert, Andrey D, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Kujau, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, DerRockWolf, Devon G. Redekopp, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jauder Ho, Jaya Chithra, Jaya Kumar, Jeffery To, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, K.B.Dharun Krishna, Kalle Laine, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, LSmithx2, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Luke Hamburg, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Martin Polehla, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maxime Thirouin, Maximilian, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Naveen, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ruslan Yevdokymov, Ryan Qian, Sacheendra Talluri, Scott Klupfel, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tim Nordenfur, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, Vladimir Rusinov, WangXi, Will Rouesnel, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, cjc7373, cui fliter, d-volution, dashangcun, derekriemer, desbma, diemade, digital, domain, entity0xfe, georgespatton, ghjklw, guangwu, gudvinr, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, kylosus, luchenhan, luzpaz, marco-m, mathias4833, maxice8, mclang, mv1005, nf, orangekame3, otbutz, overkill, perewa, polyfloyd, red_led, rubenbe, sec65, vapatel2, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心 +Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Michael Jephcote, Michael Rienstra, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, Quentin Hibon, Rahmi Pruitt, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., 佛跳墙, 落心
    diff --git a/script/authors.go b/script/authors.go index 73fc8e5e2..febe22606 100644 --- a/script/authors.go +++ b/script/authors.go @@ -14,14 +14,14 @@ package main import ( "bytes" + "cmp" "fmt" - "io" "log" "math" "os" "os/exec" "regexp" - "sort" + "slices" "strings" ) @@ -64,59 +64,33 @@ type author struct { func main() { // Read authors from the AUTHORS file - authors := getAuthors() - - // Grab the set of thus known email addresses - listed := make(stringSet) - names := make(map[string]int) - for i, a := range authors { - names[a.name] = i - for _, e := range a.emails { - listed.add(e) - } - } + authorSet := getAuthors() // Grab the set of all known authors based on the git log, and add any // missing ones to the authors list. - all := allAuthors() - for email, name := range all { - if listed.has(email) { - continue - } + addAuthors(authorSet) - if _, ok := names[name]; ok && name != "" { - // We found a match on name - authors[names[name]].emails = append(authors[names[name]].emails, email) - listed.add(email) - continue - } + authors := authorSet.filteredAuthors() - authors = append(authors, author{ - name: name, - emails: []string{email}, - }) - names[name] = len(authors) - 1 - listed.add(email) - } + // Write authors to the about dialog - // Write author names in GUI about modal - - getContributions(authors) - sort.Sort(byContributions(authors)) + slices.SortFunc(authors, func(a, b author) int { + return cmp.Or( + -cmp.Compare(a.log10commits, b.log10commits), + cmp.Compare(strings.ToLower(a.name), strings.ToLower(b.name))) + }) var lines []string for _, author := range authors { - if authorBotsRe.MatchString(author.name) { - // Only humans are eligible, pending future legislation to the - // contrary. - continue - } lines = append(lines, author.name) } replacement := strings.Join(lines, ", ") authorsRe := regexp.MustCompile(`(?s)id="contributor-list">.*?`) - bs := readAll(htmlFile) + bs, err := os.ReadFile(htmlFile) + if err != nil { + log.Fatal(err) + } bs = authorsRe.ReplaceAll(bs, []byte("id=\"contributor-list\">\n"+replacement+"\n ")) if err := os.WriteFile(htmlFile, bs, 0o644); err != nil { @@ -125,8 +99,6 @@ func main() { // Write AUTHORS file - sort.Sort(byName(authors)) - out, err := os.Create("AUTHORS") if err != nil { log.Fatal(err) @@ -141,16 +113,22 @@ func main() { for _, email := range author.emails { fmt.Fprintf(out, " <%s>", email) } - fmt.Fprintf(out, "\n") + fmt.Fprint(out, "\n") } out.Close() } -func getAuthors() []author { - bs := readAll("AUTHORS") - lines := strings.Split(string(bs), "\n") - var authors []author +func getAuthors() *authorSet { + bs, err := os.ReadFile("AUTHORS") + if err != nil { + log.Fatal(err) + } + lines := strings.Split(string(bs), "\n") + authors := &authorSet{ + emails: make(map[string]int), + commits: make(map[string]stringSet), + } for _, line := range lines { if len(line) == 0 || line[0] == '#' { continue @@ -159,7 +137,9 @@ func getAuthors() []author { fields := strings.Fields(line) var author author for _, field := range fields { - if m := nicknameRe.FindStringSubmatch(field); len(m) > 1 { + if field == "#" { + break + } else if m := nicknameRe.FindStringSubmatch(field); len(m) > 1 { author.nickname = m[1] } else if m := emailRe.FindStringSubmatch(field); len(m) > 1 { author.emails = append(author.emails, m[1]) @@ -172,53 +152,11 @@ func getAuthors() []author { } } - authors = append(authors, author) + authors.add(author) } return authors } -func readAll(path string) []byte { - fd, err := os.Open(path) - if err != nil { - log.Fatal(err) - } - defer fd.Close() - - bs, err := io.ReadAll(fd) - if err != nil { - log.Fatal(err) - } - - return bs -} - -// Add number of commits per author to the author list. -func getContributions(authors []author) { - buf := new(bytes.Buffer) - cmd := exec.Command("git", "log", "--pretty=format:%ae") - cmd.Stdout = buf - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - -next: - for _, line := range strings.Split(buf.String(), "\n") { - for i := range authors { - for _, email := range authors[i].emails { - if email == line { - authors[i].commits++ - continue next - } - } - } - } - - for i := range authors { - authors[i].log10commits = int(math.Log10(float64(authors[i].commits + 1))) - } -} - // list of commits that we don't include in our author file; because they // are legacy things that don't affect code, are committed with incorrect // address, or for other reasons. @@ -230,102 +168,95 @@ var excludeCommits = stringSetFromStrings([]string{ "9bdcadf6345aba3a939e9e58d85b89dbe9d44bc9", "b933e9666abdfcd22919dd458c930d944e1e1b7f", "b84d960a81c1282a79e2b9477558de4f1af6faae", + "4dfb9d7c83ed172f12ae19408517961f4a49beeb", }) // allAuthors returns the set of authors in the git commit log, except those // in excluded commits. -func allAuthors() map[string]string { - // Format is hash, email, name, newline, body. The body is indented with - // one space, to differentiate from the hash lines. - args := append([]string{"log", "--format=%H %ae %an%n%w(,1,1)%b"}) - cmd := exec.Command("git", args...) - bs, err := cmd.Output() +func addAuthors(authors *authorSet) { + // All existing source-tracked files + bs, err := exec.Command("git", "ls-tree", "-r", "HEAD", "--name-only").CombinedOutput() if err != nil { - log.Fatal("git:", err) + fmt.Println(string(bs)) + log.Fatal("git ls-tree:", err) } + files := strings.Split(string(bs), "\n") + files = slices.DeleteFunc(files, func(s string) bool { + return !(strings.HasPrefix(s, "assets/") || + strings.HasPrefix(s, "cmd/") || + strings.HasPrefix(s, "etc/") || + strings.HasPrefix(s, "gui/") || + strings.HasPrefix(s, "internal/") || + strings.HasPrefix(s, "lib/") || + strings.HasPrefix(s, "proto/") || + strings.HasPrefix(s, "script/") || + strings.HasPrefix(s, "test/") || + strings.HasPrefix(s, "Dockerfile") || + s == "build.go") + }) coAuthoredPrefix := "Co-authored-by: " - names := make(map[string]string) - skipCommit := false - for _, line := range bytes.Split(bs, []byte{'\n'}) { - if len(line) == 0 { - continue + for _, file := range files { + // All commits affecting those files, following any renames to their + // origin. Format is hash, email, name, newline, body. The body is + // indented with one space, to differentiate from the hash lines. + args := []string{"log", "--format=%H %ae %an%n%w(,1,1)%b", "--follow", "--", file} + bs, err = exec.Command("git", args...).CombinedOutput() + if err != nil { + fmt.Println(string(bs)) + log.Fatal("git log:", err) } - switch line[0] { - case ' ': - // Look for Co-authored-by: lines in the commit body. - if skipCommit { + skipCommit := false + var hash, email, name string + for _, line := range bytes.Split(bs, []byte{'\n'}) { + if len(line) == 0 { continue } - line = line[1:] - if bytes.HasPrefix(line, []byte(coAuthoredPrefix)) { - // Co-authored-by: Name Name - line = line[len(coAuthoredPrefix):] - if name, email, ok := strings.Cut(string(line), "<"); ok { - name = strings.TrimSpace(name) - email = strings.Trim(strings.TrimSpace(email), "<>") - if email == "@" { - // GitHub special for users who hide their email. - continue - } - if names[email] == "" { - names[email] = name + switch line[0] { + case ' ': + // Look for Co-authored-by: lines in the commit body. + if skipCommit { + continue + } + + line = line[1:] + if bytes.HasPrefix(line, []byte(coAuthoredPrefix)) { + // Co-authored-by: Name Name + line = line[len(coAuthoredPrefix):] + if name, email, ok := strings.Cut(string(line), "<"); ok { + name = strings.TrimSpace(name) + email = strings.Trim(strings.TrimSpace(email), "<>") + if email == "@" { + // GitHub special for users who hide their email. + continue + } + authors.setName(email, name) + authors.addCommit(email, hash) } } - } - default: // hash email name - fields := strings.SplitN(string(line), " ", 3) - if len(fields) != 3 { - continue - } - hash, email, name := fields[0], fields[1], fields[2] + default: // hash email name + fields := strings.SplitN(string(line), " ", 3) + if len(fields) != 3 { + continue + } + hash, email, name = fields[0], fields[1], fields[2] - if excludeCommits.has(hash) { - skipCommit = true - continue - } - skipCommit = false + if excludeCommits.has(hash) { + skipCommit = true + continue + } + skipCommit = false - if names[email] == "" { - names[email] = name + authors.setName(email, name) + authors.addCommit(email, hash) } } } - - return names } -type byContributions []author - -func (l byContributions) Len() int { return len(l) } - -// Sort first by log10(commits), then by name. This means that we first get -// an alphabetic list of people with >= 1000 commits, then a list of people -// with >= 100 commits, and so on. -func (l byContributions) Less(a, b int) bool { - if l[a].log10commits != l[b].log10commits { - return l[a].log10commits > l[b].log10commits - } - return l[a].name < l[b].name -} - -func (l byContributions) Swap(a, b int) { l[a], l[b] = l[b], l[a] } - -type byName []author - -func (l byName) Len() int { return len(l) } - -func (l byName) Less(a, b int) bool { - aname := strings.ToLower(l[a].name) - bname := strings.ToLower(l[b].name) - return aname < bname -} - -func (l byName) Swap(a, b int) { l[a], l[b] = l[b], l[a] } - // A simple string set type type stringSet map[string]struct{} @@ -347,12 +278,68 @@ func (s stringSet) has(e string) bool { return ok } -func (s stringSet) except(other stringSet) stringSet { - diff := make(stringSet) - for e := range s { - if !other.has(e) { - diff.add(e) +// A set of authors + +type authorSet struct { + authors []author + emails map[string]int // email to author index + commits map[string]stringSet // email to commit hashes +} + +func (a *authorSet) add(author author) { + for _, e := range author.emails { + if idx, ok := a.emails[e]; ok { + emails := append(author.emails, a.authors[idx].emails...) + slices.Sort(emails) + emails = slices.Compact(emails) + a.authors[idx].name = author.name + a.authors[idx].emails = emails + + for _, e := range emails { + a.emails[e] = idx + } + return } } - return diff + + for _, e := range author.emails { + a.emails[e] = len(a.authors) + } + a.authors = append(a.authors, author) +} + +func (a *authorSet) setName(email, name string) { + idx, ok := a.emails[email] + if !ok { + a.emails[email] = len(a.authors) + a.authors = append(a.authors, author{name: name, emails: []string{email}}) + } else if a.authors[idx].name == "" { + a.authors[idx].name = name + } +} + +func (a *authorSet) addCommit(email, hash string) { + ss, ok := a.commits[email] + if !ok { + ss = make(stringSet) + a.commits[email] = ss + } + ss.add(hash) +} + +func (a *authorSet) filteredAuthors() []author { + authors := make([]author, len(a.authors)) + copy(authors, a.authors) + for i, author := range authors { + for _, e := range author.emails { + authors[i].commits += len(a.commits[e]) + } + } + authors = slices.DeleteFunc(authors, func(a author) bool { + return a.commits == 0 || authorBotsRe.MatchString(a.name) + }) + for i := range authors { + authors[i].log10commits = int(math.Log10(float64(authors[i].commits))) + } + return authors } From 72849690c96e7672ff67aa43a9ff3a057ad16196 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 20 May 2025 14:26:14 +0200 Subject: [PATCH 36/62] fix(model): index handler error handling --- lib/model/indexhandler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/model/indexhandler.go b/lib/model/indexhandler.go index bb61e686c..4155bf9a2 100644 --- a/lib/model/indexhandler.go +++ b/lib/model/indexhandler.go @@ -194,7 +194,8 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) { // currently in the database, wait for the local index to update. The // local index may update for other folders than the one we are // sending for. - seq, err := s.sdb.GetDeviceSequence(s.folder, protocol.LocalDeviceID) + var seq int64 + seq, err = s.sdb.GetDeviceSequence(s.folder, protocol.LocalDeviceID) if err != nil { return err } From 085455d72e54075a05036f51385d883e56699c67 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 20 May 2025 14:27:08 +0200 Subject: [PATCH 37/62] feat: add `syncthing debug database-statistics` command (#10117) This adds a command that shows database statistics. Currently it requires a fork of the sqlite package to add the dbstats virtual table; the modernc variant already has it. This also provides the canonical mapping between folder ID and database file, for tinkerers... ``` % ./bin/syncthing debug database-statistics DATABASE FOLDER ID TABLE SIZE FILL ======== ====== == ===== ==== ==== main.db - folders 4 KiB 8.4 % main.db - folders_database_name 4 KiB 6.0 % main.db - kv 4 KiB 41.1 % main.db - schemamigrations 4 KiB 3.9 % main.db - sqlite_autoindex_folders_1 4 KiB 3.7 % ... folder.0007-txpxsvyd.db w3ejt-fn4dm indexids 4 KiB 1.5 % folder.0007-txpxsvyd.db w3ejt-fn4dm kv 4 KiB 0.8 % folder.0007-txpxsvyd.db w3ejt-fn4dm mtimes 608 KiB 81.5 % folder.0007-txpxsvyd.db w3ejt-fn4dm schemamigrations 4 KiB 3.9 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_blocklists_1 4108 KiB 89.5 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_blocks_1 700020 KiB 88.1 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_devices_1 4 KiB 3.6 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_kv_1 4 KiB 0.6 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_schema 12 KiB 45.9 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_sequence 4 KiB 1.0 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_stat1 4 KiB 12.2 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_stat4 4 KiB 0.2 % folder.0007-txpxsvyd.db w3ejt-fn4dm (total) 1906020 KiB 92.8 % main.db + children - (total) 2205888 KiB 92.0 % ``` --- .github/workflows/build-syncthing.yaml | 2 +- cmd/syncthing/main.go | 64 ++++++++++++++++-------- go.mod | 5 +- go.sum | 4 +- internal/db/sqlite/db_stats.go | 69 ++++++++++++++++++++++++++ lib/build/build.go | 1 + 6 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 internal/db/sqlite/db_stats.go diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index f71b3d81e..74b9f764f 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -22,7 +22,7 @@ env: BUILD_USER: builder BUILD_HOST: github.syncthing.net - TAGS: "netgo osusergo sqlite_omit_load_extension" + TAGS: "netgo osusergo sqlite_omit_load_extension sqlite_dbstat" # A note on actions and third party code... The actions under actions/ (like # `uses: actions/checkout`) are maintained by GitHub, and we need to trust diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index b9b97d697..195b302de 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -8,6 +8,7 @@ package main import ( "bytes" + "cmp" "context" "crypto/tls" "errors" @@ -26,6 +27,7 @@ import ( "sort" "strconv" "syscall" + "text/tabwriter" "time" "github.com/alecthomas/kong" @@ -37,6 +39,7 @@ import ( "github.com/syncthing/syncthing/cmd/syncthing/decrypt" "github.com/syncthing/syncthing/cmd/syncthing/generate" "github.com/syncthing/syncthing/internal/db" + "github.com/syncthing/syncthing/internal/db/sqlite" _ "github.com/syncthing/syncthing/lib/automaxprocs" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" @@ -825,24 +828,6 @@ func exitCodeForUpgrade(err error) int { return svcutil.ExitError.AsInt() } -// convertLegacyArgs returns the slice of arguments with single dash long -// flags converted to double dash long flags. -func convertLegacyArgs(args []string) []string { - // Legacy args begin with a single dash, followed by two or more characters. - legacyExp := regexp.MustCompile(`^-\w{2,}`) - - res := make([]string, len(args)) - for i, arg := range args { - if legacyExp.MatchString(arg) { - res[i] = "-" + arg - } else { - res[i] = arg - } - } - - return res -} - type versionCmd struct{} func (versionCmd) Run() error { @@ -900,7 +885,8 @@ func (u upgradeCmd) Run() error { release, err := checkUpgrade() if err == nil { lf := flock.New(locations.Get(locations.LockFile)) - locked, err := lf.TryLock() + var locked bool + locked, err = lf.TryLock() if err != nil { l.Warnln("Upgrade:", err) os.Exit(1) @@ -930,7 +916,8 @@ func (browserCmd) Run() error { } type debugCmd struct { - ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"` + ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"` + DatabaseStatistics databaseStatsCmd `cmd:"" help:"Display database size statistics"` } type resetDatabaseCmd struct{} @@ -945,6 +932,43 @@ func (resetDatabaseCmd) Run() error { return nil } +type databaseStatsCmd struct{} + +func (c databaseStatsCmd) Run() error { + db, err := sqlite.Open(locations.Get(locations.Database)) + if err != nil { + return err + } + ds, err := db.Statistics() + if err != nil { + return err + } + + tw := tabwriter.NewWriter(os.Stdout, 2, 2, 2, ' ', 0) + hdr := fmt.Sprintf("%s\t%s\t%s\t%12s\t%7s\n", "DATABASE", "FOLDER ID", "TABLE", "SIZE", "FILL") + fmt.Fprint(tw, hdr) + fmt.Fprint(tw, regexp.MustCompile(`[A-Z]`).ReplaceAllString(hdr, "=")) + c.printStat(tw, ds) + return tw.Flush() +} + +func (c databaseStatsCmd) printStat(w io.Writer, s *sqlite.DatabaseStatistics) { + for _, table := range s.Tables { + fmt.Fprintf(w, "%s\t%s\t%s\t%8d KiB\t%5.01f %%\n", s.Name, cmp.Or(s.FolderID, "-"), table.Name, table.Size/1024, float64(table.Size-table.Unused)*100/float64(table.Size)) + } + for _, next := range s.Children { + c.printStat(w, &next) + s.Total.Size += next.Total.Size + s.Total.Unused += next.Total.Unused + } + + totalName := s.Name + if len(s.Children) > 0 { + totalName += " + children" + } + fmt.Fprintf(w, "%s\t%s\t%s\t%8d KiB\t%5.01f %%\n", totalName, cmp.Or(s.FolderID, "-"), "(total)", s.Total.Size/1024, float64(s.Total.Size-s.Total.Unused)*100/float64(s.Total.Size)) +} + func setConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error { homeSet := homeDir != "" confSet := confDir != "" diff --git a/go.mod b/go.mod index 57dc7ff11..5ed5d7860 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/maruel/panicparse/v2 v2.5.0 - github.com/mattn/go-sqlite3 v1.14.27 + github.com/mattn/go-sqlite3 v1.14.28 github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 github.com/maxmind/geoipupdate/v6 v6.1.0 github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 @@ -108,3 +108,6 @@ require ( // https://github.com/gobwas/glob/pull/55 replace github.com/gobwas/glob v0.2.3 => github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b + +// https://github.com/mattn/go-sqlite3/pull/1338 +replace github.com/mattn/go-sqlite3 v1.14.28 => github.com/calmh/go-sqlite3 v1.14.29-0.20250520105817-2e94cda3f7f8 diff --git a/go.sum b/go.sum index df93d4f5a..3836dc4f7 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b h1:Fjm4GuJ+TGMgqfGHN42IQArJb77CfD/mAwLbDUoJe6g= github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b/go.mod h1:91K7jfEsgJSyfSrX+gmrRfZMtntx6JsHolWubGXDopg= +github.com/calmh/go-sqlite3 v1.14.29-0.20250520105817-2e94cda3f7f8 h1:oNVrBJGXkD334ToEmxJz8G6LhzD1/sMA4twMHsMLzQo= +github.com/calmh/go-sqlite3 v1.14.29-0.20250520105817-2e94cda3f7f8/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/calmh/incontainer v1.0.0 h1:g2cTUtZuFGmMGX8GoykPkN1Judj2uw8/3/aEtq4Z/rg= github.com/calmh/incontainer v1.0.0/go.mod h1:eOhqnw15c9X+4RNBe0W3HlUZFfX16O0EDsCOInTndHY= github.com/calmh/xdr v1.2.0 h1:GaGSNH4ZDw9kNdYqle6+RcAENiaQ8/611Ok+jQbBEeU= @@ -164,8 +166,6 @@ github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPb github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= -github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 h1:yVCLo4+ACVroOEr4iFU1iH46Ldlzz2rTuu18Ra7M8sU= github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ= github.com/maxmind/geoipupdate/v6 v6.1.0 h1:sdtTHzzQNJlXF5+fd/EoPTucRHyMonYt/Cok8xzzfqA= diff --git a/internal/db/sqlite/db_stats.go b/internal/db/sqlite/db_stats.go new file mode 100644 index 000000000..06925c029 --- /dev/null +++ b/internal/db/sqlite/db_stats.go @@ -0,0 +1,69 @@ +// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/. + +package sqlite + +type DatabaseStatistics struct { + Name string `json:"name"` + FolderID string `json:"folderID,omitempty"` + Tables []TableStatistics `json:"tables"` + Total TableStatistics `json:"total"` + Children []DatabaseStatistics `json:"children,omitempty"` +} + +type TableStatistics struct { + Name string `json:"name,omitempty"` + Size int64 `json:"size"` + Unused int64 `json:"unused"` +} + +func (s *DB) Statistics() (*DatabaseStatistics, error) { + ts, total, err := s.tableStats() + if err != nil { + return nil, wrap(err) + } + ds := DatabaseStatistics{ + Name: s.baseName, + Tables: ts, + Total: total, + } + + err = s.forEachFolder(func(fdb *folderDB) error { + tables, total, err := fdb.tableStats() + if err != nil { + return wrap(err) + } + ds.Children = append(ds.Children, DatabaseStatistics{ + Name: fdb.baseName, + FolderID: fdb.folderID, + Tables: tables, + Total: total, + }) + return nil + }) + if err != nil { + return nil, wrap(err) + } + + return &ds, nil +} + +func (s *baseDB) tableStats() ([]TableStatistics, TableStatistics, error) { + var stats []TableStatistics + if err := s.stmt(` + SELECT name, pgsize AS size, unused FROM dbstat + WHERE aggregate=true + ORDER BY name + `).Select(&stats); err != nil { + return nil, TableStatistics{}, wrap(err) + } + var total TableStatistics + for _, s := range stats { + total.Size += s.Size + total.Unused += s.Unused + } + return stats, total, nil +} diff --git a/lib/build/build.go b/lib/build/build.go index b13cb93b7..499cd7242 100644 --- a/lib/build/build.go +++ b/lib/build/build.go @@ -48,6 +48,7 @@ var ( } replaceTags = map[string]string{ "sqlite_omit_load_extension": "", + "sqlite_dbstat": "", "osusergo": "", "netgo": "", } From 96b03fac04e2b9d7314886e3503ffe81c935ea9a Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 20 May 2025 14:34:20 +0200 Subject: [PATCH 38/62] chore: trivial lint fix --- lib/syncthing/utils.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/syncthing/utils.go b/lib/syncthing/utils.go index 9ab4b2ee3..79d2cd6b3 100644 --- a/lib/syncthing/utils.go +++ b/lib/syncthing/utils.go @@ -16,7 +16,6 @@ import ( "time" "github.com/syncthing/syncthing/internal/db" - newdb "github.com/syncthing/syncthing/internal/db" "github.com/syncthing/syncthing/internal/db/olddb" "github.com/syncthing/syncthing/internal/db/olddb/backend" "github.com/syncthing/syncthing/internal/db/sqlite" @@ -146,13 +145,13 @@ func copyFile(src, dst string) error { } // Opens a database -func OpenDatabase(path string, deleteRetention time.Duration) (newdb.DB, error) { +func OpenDatabase(path string, deleteRetention time.Duration) (db.DB, error) { sql, err := sqlite.Open(path, sqlite.WithDeleteRetention(deleteRetention)) if err != nil { return nil, err } - sdb := newdb.MetricsWrap(sql) + sdb := db.MetricsWrap(sql) return sdb, nil } From 0a58747eb2702ebf5969e0a691de018dee1a063c Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 20 May 2025 15:04:33 +0200 Subject: [PATCH 39/62] chore: further minor lint fixes --- .golangci.yml | 2 ++ lib/model/blockpullreorderer.go | 2 +- lib/model/devicedownloadstate.go | 4 ++-- lib/model/folder_sendrecv.go | 8 ++++---- lib/model/indexhandler.go | 14 ++++++++++---- lib/model/model.go | 6 +++--- lib/model/sentdownloadstate.go | 2 +- lib/model/service_map.go | 4 ++-- 8 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b5b8239ff..6d83857a3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,6 +4,7 @@ linters: disable: - cyclop - depguard + - err113 - exhaustive - exhaustruct - forbidigo @@ -53,6 +54,7 @@ linters: - third_party$ - builtin$ - examples$ + - _test\.go$ formatters: enable: - gofumpt diff --git a/lib/model/blockpullreorderer.go b/lib/model/blockpullreorderer.go index c2292c279..a98a4ce7a 100644 --- a/lib/model/blockpullreorderer.go +++ b/lib/model/blockpullreorderer.go @@ -92,7 +92,7 @@ func (p *standardBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []prot // The rest of the chunks we fetch in a random order in whole chunks. // Generate chunk index slice and shuffle it indexes := make([]int, 0, len(chunks)-1) - for i := 0; i < len(chunks); i++ { + for i := range len(chunks) { if i != p.myIndex { indexes = append(indexes, i) } diff --git a/lib/model/devicedownloadstate.go b/lib/model/devicedownloadstate.go index 13059499a..324cd8513 100644 --- a/lib/model/devicedownloadstate.go +++ b/lib/model/devicedownloadstate.go @@ -60,12 +60,12 @@ func (p *deviceFolderDownloadState) Update(updates []protocol.FileDownloadProgre local = deviceFolderFileDownloadState{ blockIndexes: update.BlockIndexes, version: update.Version, - blockSize: int(update.BlockSize), + blockSize: update.BlockSize, } } else if !local.version.Equal(update.Version) { local.blockIndexes = append(local.blockIndexes[:0], update.BlockIndexes...) local.version = update.Version - local.blockSize = int(update.BlockSize) + local.blockSize = update.BlockSize } else { local.blockIndexes = append(local.blockIndexes, update.BlockIndexes...) } diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 8ab78400f..70cd37838 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -179,7 +179,7 @@ func (f *sendReceiveFolder) pull() (bool, error) { f.errorsMut.Unlock() var err error - for tries := 0; tries < maxPullerIterations; tries++ { + for tries := range maxPullerIterations { select { case <-f.ctx.Done(): return false, f.ctx.Err() @@ -259,7 +259,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error) updateWg.Done() }() - for i := 0; i < f.Copiers; i++ { + for range f.Copiers { copyWg.Add(1) go func() { // copierRoutine finishes when copyChan is closed @@ -1358,7 +1358,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch // Returns true when the block was successfully copied. func (f *sendReceiveFolder) copyBlock(block protocol.BlockInfo, state copyBlocksState, otherFolderFilesystems map[string]fs.Filesystem) bool { - buf := protocol.BufferPool.Get(int(block.Size)) + buf := protocol.BufferPool.Get(block.Size) defer protocol.BufferPool.Put(buf) // Hope that it's usually in the same folder, so start with that @@ -1561,7 +1561,7 @@ loop: activity.using(selected) var buf []byte blockNo := int(state.block.Offset / int64(state.file.BlockSize())) - buf, lastError = f.model.RequestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, int(state.block.Size), state.block.Hash, selected.FromTemporary) + buf, lastError = f.model.RequestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, state.block.Size, state.block.Hash, selected.FromTemporary) activity.done(selected) if lastError != nil { l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, selected.ID.Short(), "returned error:", lastError) diff --git a/lib/model/indexhandler.go b/lib/model/indexhandler.go index 4155bf9a2..d8c45da24 100644 --- a/lib/model/indexhandler.go +++ b/lib/model/indexhandler.go @@ -106,7 +106,9 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f // information we have from them before accepting their // index, which will presumably be a full index. l.Debugf("Device %v folder %s does not announce an index ID", conn.DeviceID().Short(), folder.Description()) - sdb.DropAllFiles(folder.ID, conn.DeviceID()) + if err := sdb.DropAllFiles(folder.ID, conn.DeviceID()); err != nil { + return nil, err + } } else if startInfo.remote.IndexID != theirIndexID { // The index ID we have on file is not what they're // announcing. They must have reset their database and @@ -114,8 +116,12 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f // information we have and remember this new index ID // instead. l.Infof("Device %v folder %s has a new index ID (%v)", conn.DeviceID().Short(), folder.Description(), startInfo.remote.IndexID) - sdb.DropAllFiles(folder.ID, conn.DeviceID()) - sdb.SetIndexID(folder.ID, conn.DeviceID(), startInfo.remote.IndexID) + if err := sdb.DropAllFiles(folder.ID, conn.DeviceID()); err != nil { + return nil, err + } + if err := sdb.SetIndexID(folder.ID, conn.DeviceID(), startInfo.remote.IndexID); err != nil { + return nil, err + } } return &indexHandler{ @@ -133,7 +139,7 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f }, nil } -// waitWhilePaused waits for the handler to resume +// waitWhilePaused waits for the handler to resume. func (s *indexHandler) waitWhilePaused(ctx context.Context) error { s.cond.L.Lock() defer s.cond.L.Unlock() diff --git a/lib/model/model.go b/lib/model/model.go index 9d46d925d..e9fa0b1d3 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -372,7 +372,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio for _, available := range devs { if _, ok := expected[available]; !ok { l.Debugln("dropping", folder, "state for", available) - m.sdb.DropAllFiles(folder, available) + _ = m.sdb.DropAllFiles(folder, available) } } @@ -493,7 +493,7 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) { m.mut.Unlock() // Remove it from the database - m.sdb.DropFolder(cfg.ID) + _ = m.sdb.DropFolder(cfg.ID) } // Need to hold lock on m.mut when calling this. @@ -1559,7 +1559,7 @@ func (m *model) ccCheckEncryption(fcfg config.FolderConfiguration, folderDevice if isEncryptedRemote { passwordToken := protocol.PasswordToken(m.keyGen, fcfg.ID, folderDevice.EncryptionPassword) - match := false + var match bool if hasTokenLocal { match = bytes.Equal(passwordToken, ccDeviceInfos.local.EncryptionPasswordToken) } else { diff --git a/lib/model/sentdownloadstate.go b/lib/model/sentdownloadstate.go index 2c205a7ad..e5723dd4d 100644 --- a/lib/model/sentdownloadstate.go +++ b/lib/model/sentdownloadstate.go @@ -97,7 +97,7 @@ func (s *sentFolderDownloadState) update(pullers []*sharedPullerState) []protoco localFile.updated = pullerBlockIndexesUpdated localFile.version = pullerVersion localFile.created = pullerCreated - localFile.blockSize = int(pullerBlockSize) + localFile.blockSize = pullerBlockSize continue } diff --git a/lib/model/service_map.go b/lib/model/service_map.go index 7eaf94506..cc681e9d1 100644 --- a/lib/model/service_map.go +++ b/lib/model/service_map.go @@ -8,6 +8,7 @@ package model import ( "context" + "errors" "fmt" "time" @@ -16,7 +17,7 @@ import ( "github.com/thejerf/suture/v4" ) -var errSvcNotFound = fmt.Errorf("service not found") +var errSvcNotFound = errors.New("service not found") // A serviceMap is a utility map of arbitrary keys to a suture.Service of // some kind, where adding and removing services ensures they are properly @@ -65,7 +66,6 @@ func (s *serviceMap[K, S]) Stop(k K) { if tok, ok := s.tokens[k]; ok { s.supervisor.Remove(tok) } - return } // StopAndWaitChan removes the service at the given key from the supervisor, From 78bfe643a88ac9e8be4ea7a5f402c629d4181c39 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 20 May 2025 15:37:19 +0200 Subject: [PATCH 40/62] chore: bump config version, prevent accidental downgrade --- lib/config/config.go | 2 +- lib/config/migrations.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/config/config.go b/lib/config/config.go index 73c48ecd0..2f6db9a35 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -31,7 +31,7 @@ import ( const ( OldestHandledVersion = 10 - CurrentVersion = 37 + CurrentVersion = 50 MaxRescanIntervalS = 365 * 24 * 60 * 60 ) diff --git a/lib/config/migrations.go b/lib/config/migrations.go index 014d31404..a2e3a24b8 100644 --- a/lib/config/migrations.go +++ b/lib/config/migrations.go @@ -27,6 +27,7 @@ import ( // put the newest on top for readability. var ( migrations = migrationSet{ + {50, migrateToConfigV50}, {37, migrateToConfigV37}, {36, migrateToConfigV36}, {35, migrateToConfigV35}, @@ -96,6 +97,10 @@ func (m migration) apply(cfg *Configuration) { cfg.Version = m.targetVersion } +func migrateToConfigV50(cfg *Configuration) { + // v50 is Syncthing 2.0 +} + func migrateToConfigV37(cfg *Configuration) { // "scan ownership" changed name to "send ownership" for i := range cfg.Folders { From 3423de24ead2a0441371c744de29e572451de55d Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 22 May 2025 12:03:41 +0200 Subject: [PATCH 41/62] Revert "build: use Go 1.24.2 specifically" This reverts commit 4bc17bc58802905353f52e5182335fe3922de49a. --- .github/workflows/build-syncthing.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index 2ea0bd4eb..c6a4e1784 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -10,7 +10,7 @@ env: # The go version to use for builds. We set check-latest to true when # installing, so we get the latest patch version that matches the # expression. - GO_VERSION: "1.24.2" + GO_VERSION: "~1.24.0" # Optimize compatibility on the slow archictures. GOMIPS: softfloat From 1b8a8032f0ae9f5350c89c8ac3eea7bff887ac46 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 22 May 2025 12:27:35 +0200 Subject: [PATCH 42/62] build: target ARMv6 for linux-arm builds --- .github/workflows/build-syncthing.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index c6a4e1784..0ea335f07 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -14,6 +14,7 @@ env: # Optimize compatibility on the slow archictures. GOMIPS: softfloat + GOARM: "6" # Avoid hilarious amounts of obscuring log output when running tests. LOGGER_DISCARD: "1" @@ -251,7 +252,7 @@ jobs: for tgt in syncthing stdiscosrv strelaysrv ; do go run build.go -tags "${{env.TAGS}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" tar "$tgt" go run build.go -tags "${{env.TAGS}}" -goos linux -goarch 386 -cc "zig cc -target x86-linux-musl" tar "$tgt" - go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi" tar "$tgt" + go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" tar "$tgt" go run build.go -tags "${{env.TAGS}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" tar "$tgt" go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mips -cc "zig cc -target mips-linux-musleabi" tar "$tgt" go run build.go -tags "${{env.TAGS}}" -goos linux -goarch mipsle -cc "zig cc -target mipsel-linux-musleabi" tar "$tgt" @@ -659,7 +660,7 @@ jobs: run: | for tgt in syncthing stdiscosrv strelaysrv ; do go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" deb "$tgt" - go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi" deb "$tgt" + go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" deb "$tgt" go run build.go -no-upgrade -installsuffix=no-upgrade -tags "${{env.TAGS}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" deb "$tgt" done env: @@ -937,7 +938,7 @@ jobs: mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm64 # arm - go run build.go -goos linux -goarch arm -tags "${{env.TAGS}}" -cc "zig cc -target arm-linux-musleabi" -no-upgrade build ${{ matrix.pkg }} + go run build.go -goos linux -goarch arm -tags "${{env.TAGS}}" -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" -no-upgrade build ${{ matrix.pkg }} mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm env: CGO_ENABLED: "1" From 99b707c141071afeff9dd8e68016fc6131e63880 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sun, 25 May 2025 22:03:19 +0200 Subject: [PATCH 43/62] fix(versioner): correct fs creation in test --- lib/versioner/simple_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/versioner/simple_test.go b/lib/versioner/simple_test.go index 00527a3bb..d2d008706 100644 --- a/lib/versioner/simple_test.go +++ b/lib/versioner/simple_test.go @@ -177,7 +177,7 @@ func TestArchiveFoldersCreationPermission(t *testing.T) { }, }, } - vfs := cfg.Filesystem(nil) + vfs := cfg.Filesystem() v := newSimple(cfg) // Create two folders and set their permissions From 46d2c59b24951f3e945a83bc7b725d7848da2202 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 26 May 2025 14:53:47 +0200 Subject: [PATCH 44/62] build: upgrade setup-zig action (#10134) --- .github/workflows/build-syncthing.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index a84019f59..fa12b1572 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -130,7 +130,7 @@ jobs: cache: false check-latest: true - - uses: mlugg/setup-zig@v1 + - uses: mlugg/setup-zig@v2 - uses: actions/cache@v4 with: @@ -237,7 +237,7 @@ jobs: go version echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV - - uses: mlugg/setup-zig@v1 + - uses: mlugg/setup-zig@v2 - uses: actions/cache@v4 with: @@ -647,7 +647,7 @@ jobs: run: | gem install fpm - - uses: mlugg/setup-zig@v1 + - uses: mlugg/setup-zig@v2 - uses: actions/cache@v4 with: @@ -942,7 +942,7 @@ jobs: go version echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV - - uses: mlugg/setup-zig@v1 + - uses: mlugg/setup-zig@v2 - uses: actions/cache@v4 with: From e39dcc5c587d3b7ec8353b0f9335eee06b2a3bb7 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 26 May 2025 14:45:39 +0200 Subject: [PATCH 45/62] docs: add relnotes for v2.0.0 --- relnotes/v2.0.0.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 relnotes/v2.0.0.md diff --git a/relnotes/v2.0.0.md b/relnotes/v2.0.0.md new file mode 100644 index 000000000..c08e0388b --- /dev/null +++ b/relnotes/v2.0.0.md @@ -0,0 +1,22 @@ +## Major changes in 2.0.0 + +- Database backend switched from LevelDB to SQLite. There is a migration on + first launch which can be lengthy for larger setups. The new database is + easier to understand and maintain and, hopefully, less buggy. + +- Deleted items are no longer kept forever in the database, instead they are + forgotten after six months. If your use case require deletes to take + effect after more than a six month delay, set the + `--db-delete-retention-interval` command line option or corresponding + environment variable to zero, or a longer time interval of your choosing. + +- Modernised command line options parsing. Old single-dash long options are + no longer supported, e.g. `-home` must be given as `--home`. All serve + options are now also accepted as environment variables. See `syncthing + serve --help` for details. + +- Rolling hash detection of shifted data is no longer supported as this + effectively never helped. Instead, scanning and syncing is faster and more + efficient without it. + +- A "default folder" is no longer created on first startup. From 5945a8c5bd58ca0f13cd95310765a964d0b80805 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Mon, 26 May 2025 15:27:53 +0200 Subject: [PATCH 46/62] docs: adjust release notes for v2.0.0 --- relnotes/v2.0.0.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/relnotes/v2.0.0.md b/relnotes/v2.0.0.md index c08e0388b..8e0bf9c6d 100644 --- a/relnotes/v2.0.0.md +++ b/relnotes/v2.0.0.md @@ -11,9 +11,9 @@ environment variable to zero, or a longer time interval of your choosing. - Modernised command line options parsing. Old single-dash long options are - no longer supported, e.g. `-home` must be given as `--home`. All serve - options are now also accepted as environment variables. See `syncthing - serve --help` for details. + no longer supported, e.g. `-home` must be given as `--home`. Some options + have been renamed. All serve options are now also accepted as environment + variables. See `syncthing serve --help` for details. - Rolling hash detection of shifted data is no longer supported as this effectively never helped. Instead, scanning and syncing is faster and more From 43d33dbeb543fac6e399b9c67ba26f8869ded959 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 27 May 2025 07:51:18 +0200 Subject: [PATCH 47/62] docs: mention subcommands in release notes, use for all 2.0 releases --- relnotes/{v2.0.0.md => v2.0.md} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename relnotes/{v2.0.0.md => v2.0.md} (81%) diff --git a/relnotes/v2.0.0.md b/relnotes/v2.0.md similarity index 81% rename from relnotes/v2.0.0.md rename to relnotes/v2.0.md index 8e0bf9c6d..d285f6603 100644 --- a/relnotes/v2.0.0.md +++ b/relnotes/v2.0.md @@ -1,4 +1,4 @@ -## Major changes in 2.0.0 +## Major changes in 2.0 - Database backend switched from LevelDB to SQLite. There is a migration on first launch which can be lengthy for larger setups. The new database is @@ -12,8 +12,9 @@ - Modernised command line options parsing. Old single-dash long options are no longer supported, e.g. `-home` must be given as `--home`. Some options - have been renamed. All serve options are now also accepted as environment - variables. See `syncthing serve --help` for details. + have been renamed, others have become subcommands. All serve options are + now also accepted as environment variables. See `syncthing --help` and + `syncthing serve --help` for details. - Rolling hash detection of shifted data is no longer supported as this effectively never helped. Instead, scanning and syncing is faster and more From 8e934a8c690431d58e68db6b40ac5b7e544673c3 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 30 May 2025 22:04:55 +0200 Subject: [PATCH 48/62] feat(config): enable multiple connections by default (#10151) This changes the default number of connections from one to three (one metadata + two data connections). This should give some advantages of multiple connections, while also not being an overwhelming change for larger installations. (Though those may need to tweak their settings anyway, as always.) --- lib/config/deviceconfiguration.go | 2 +- relnotes/v2.0.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/config/deviceconfiguration.go b/lib/config/deviceconfiguration.go index b12514175..28a51fe9b 100644 --- a/lib/config/deviceconfiguration.go +++ b/lib/config/deviceconfiguration.go @@ -13,7 +13,7 @@ import ( "github.com/syncthing/syncthing/lib/protocol" ) -const defaultNumConnections = 1 // number of connections to use by default; may change in the future. +const defaultNumConnections = 3 // number of connections to use by default type DeviceConfiguration struct { DeviceID protocol.DeviceID `json:"deviceID" xml:"id,attr" nodefault:"true"` diff --git a/relnotes/v2.0.md b/relnotes/v2.0.md index d285f6603..3b2a736ec 100644 --- a/relnotes/v2.0.md +++ b/relnotes/v2.0.md @@ -21,3 +21,7 @@ efficient without it. - A "default folder" is no longer created on first startup. + +- Multiple connections are now used by default between v2 devices. The new + default value is to use three connections: one for index metadata and two + for data exchange. From 15f693d93c65fc57c8b4d9bc7a36567a51416853 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 31 May 2025 08:36:35 +0200 Subject: [PATCH 49/62] refactor(sqlite): move deleted flag into logical order in schema --- internal/db/sqlite/sql/schema/folder/40-counts.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/db/sqlite/sql/schema/folder/40-counts.sql b/internal/db/sqlite/sql/schema/folder/40-counts.sql index 9b123c725..afa50710d 100644 --- a/internal/db/sqlite/sql/schema/folder/40-counts.sql +++ b/internal/db/sqlite/sql/schema/folder/40-counts.sql @@ -12,9 +12,9 @@ CREATE TABLE IF NOT EXISTS counts ( device_idx INTEGER NOT NULL, type INTEGER NOT NULL, local_flags INTEGER NOT NULL, + deleted INTEGER NOT NULL, -- boolean count INTEGER NOT NULL, size INTEGER NOT NULL, - deleted INTEGER NOT NULL, -- boolean PRIMARY KEY(device_idx, type, local_flags, deleted), FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE ) STRICT, WITHOUT ROWID @@ -24,8 +24,8 @@ CREATE TABLE IF NOT EXISTS counts ( CREATE TRIGGER IF NOT EXISTS counts_insert AFTER INSERT ON files BEGIN - INSERT INTO counts (device_idx, type, local_flags, count, size, deleted) - VALUES (NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) + INSERT INTO counts (device_idx, type, local_flags, deleted, count, size) + VALUES (NEW.device_idx, NEW.type, NEW.local_flags, NEW.deleted, 1, NEW.size) ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size; END ; @@ -38,8 +38,8 @@ END CREATE TRIGGER IF NOT EXISTS counts_update AFTER UPDATE OF local_flags ON files WHEN NEW.local_flags != OLD.local_flags BEGIN - INSERT INTO counts (device_idx, type, local_flags, count, size, deleted) - VALUES (NEW.device_idx, NEW.type, NEW.local_flags, 1, NEW.size, NEW.deleted) + INSERT INTO counts (device_idx, type, local_flags, deleted, count, size) + VALUES (NEW.device_idx, NEW.type, NEW.local_flags, NEW.deleted, 1, NEW.size) ON CONFLICT DO UPDATE SET count = count + 1, size = size + NEW.size; UPDATE counts SET count = count - 1, size = size - OLD.size WHERE device_idx = OLD.device_idx AND type = OLD.type AND local_flags = OLD.local_flags AND deleted = OLD.deleted; From 50480f6ccb1835dfe1101a379128491e5bf2ba7c Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 31 May 2025 08:50:42 +0200 Subject: [PATCH 50/62] build: use same GitHub token for releases as for translation etc pushes --- .github/workflows/release-syncthing.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-syncthing.yaml b/.github/workflows/release-syncthing.yaml index 2dfd30164..53619e73b 100644 --- a/.github/workflows/release-syncthing.yaml +++ b/.github/workflows/release-syncthing.yaml @@ -19,7 +19,7 @@ jobs: with: fetch-depth: 0 ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - token: ${{ secrets.STRELEASE_GITHUB_TOKEN }} + token: ${{ secrets.ACTIONS_GITHUB_TOKEN }} - uses: actions/setup-go@v5 with: @@ -47,7 +47,7 @@ jobs: run: | go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md env: - GITHUB_TOKEN: ${{ secrets.STRELEASE_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }} - name: Create and push tag run: | From d49df1e44cb9364cc8e9254ee69e4070c0fad924 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 31 May 2025 09:07:17 +0200 Subject: [PATCH 51/62] build: refactor builds for forks/PRs Make sure as much as possible runs for forks and PRs as well, while keeping the release specific stuff out of the way. --- .github/regsync.yml | 52 +++++++++++++ .github/workflows/build-syncthing.yaml | 102 +++++++++++++++++++------ .github/workflows/pr-linters.yaml | 49 ------------ 3 files changed, 130 insertions(+), 73 deletions(-) create mode 100644 .github/regsync.yml delete mode 100644 .github/workflows/pr-linters.yaml diff --git a/.github/regsync.yml b/.github/regsync.yml new file mode 100644 index 000000000..56d5f176e --- /dev/null +++ b/.github/regsync.yml @@ -0,0 +1,52 @@ + version: 1 + creds: + - registry: docker.io + user: "{{env \"DOCKERHUB_USERNAME\"}}" + pass: "{{env \"DOCKERHUB_TOKEN\"}}" + + defaults: + ratelimit: + min: 100 + retry: 1m + parallel: 4 + + sync: + + - source: ghcr.io/syncthing/syncthing + target: docker.io/syncthing/syncthing + type: repository + tags: + allow: + - latest + - rc + - edge + - \d+ + - \d+\.\d+ + - \d+\.\d+\.\d+ + - \d+\.\d+\.\d+-rc\.\d+ + + - source: ghcr.io/syncthing/relaysrv + target: docker.io/syncthing/relaysrv + type: repository + tags: + allow: + - latest + - rc + - edge + - \d+ + - \d+\.\d+ + - \d+\.\d+\.\d+ + - \d+\.\d+\.\d+-rc\.\d+ + + - source: ghcr.io/syncthing/discosrv + target: docker.io/syncthing/discosrv + type: repository + tags: + allow: + - latest + - rc + - edge + - \d+ + - \d+\.\d+ + - \d+\.\d+\.\d+ + - \d+\.\d+\.\d+-rc\.\d+ diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index 3a4a21640..0970d0064 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -111,6 +111,8 @@ jobs: - package-debian - package-windows - govulncheck + - golangci + - meta steps: - uses: actions/checkout@v4 @@ -163,6 +165,12 @@ jobs: name: unsigned-packages-windows path: "*.zip" + # + # Codesign binaries for Windows. This job runs only when called in the + # Syncthing repo for release branches and tags, as it requires our + # specific code signing keys etc. + # + codesign-windows: name: Codesign for Windows if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v')) @@ -280,12 +288,14 @@ jobs: compat.json # - # macOS + # macOS. The entire build runs in the release environment because code + # signing is part of the build process, so it is limited to release + # branches on the Syncthing repo. # package-macos: name: Package for macOS - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v')) + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v')) environment: release env: CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }} @@ -899,16 +909,12 @@ jobs: args: sync -v dists objstore:apt/dists # - # Build and push to Docker Hub + # Build and push (except for PRs) to GHCR. # - docker-syncthing: - name: Build and push Docker images + docker-ghcr: + name: Build and push Docker images (GHCR) runs-on: ubuntu-latest - if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release-nightly' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/tags/v')) - environment: docker - env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} permissions: contents: read packages: write @@ -972,14 +978,6 @@ jobs: BUILD_USER: docker EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static" - - name: Login to Docker Hub - uses: docker/login-action@v3 - if: env.DOCKERHUB_USERNAME != '' - with: - registry: docker.io - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Login to GHCR uses: docker/login-action@v3 with: @@ -1011,12 +1009,6 @@ jobs: tags=$repo:$ref fi - # If we have a Docker Hub secret, also push to there. - if [[ $DOCKERHUB_USERNAME != "" ]] ; then - dockerhubtags="${tags//ghcr.io\/syncthing/docker.io\/syncthing}" - tags="$tags,$dockerhubtags" - fi - echo Pushing to $tags echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV @@ -1029,11 +1021,35 @@ jobs: file: ${{ matrix.dockerfile }} platforms: linux/amd64,linux/arm64,linux/arm/7 tags: ${{ env.DOCKER_TAGS }} - push: true + push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} labels: | org.opencontainers.image.version=${{ env.VERSION }} org.opencontainers.image.revision=${{ github.sha }} + # + # Sync images to Docker hub. This takes the images already pushed to GHCR + # and copies them to Docker hub. Runs for releases only. + # + + docker-hub: + name: Sync images to Docker hub + if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release-nightly' || github.ref == 'refs/heads/infrastructure' || startsWith(github.ref, 'refs/tags/v')) + runs-on: ubuntu-latest + needs: + - docker-ghcr + environment: docker + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + - name: Sync images + uses: docker://docker.io/regclient/regsync:latest + with: + args: + -c ./.github/regsync.yml + once + # # Check for known vulnerabilities in Go dependencies # @@ -1055,3 +1071,41 @@ jobs: go run build.go assets go install golang.org/x/vuln/cmd/govulncheck@latest govulncheck ./... + + # + # golangci-lint runs a suite of static analysis checks on the code + # + + golangci: + runs-on: ubuntu-latest + name: Run golangci-lint + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: ensure asset generation + run: go run build.go assets + + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + only-new-issues: true + + # + # Meta checks for formatting, copyright, etc + # + + meta: + name: Run meta checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - run: | + go run build.go assets + go test -v ./meta diff --git a/.github/workflows/pr-linters.yaml b/.github/workflows/pr-linters.yaml deleted file mode 100644 index 6eda16c17..000000000 --- a/.github/workflows/pr-linters.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: Run PR linters - -on: - pull_request: - workflow_dispatch: - -permissions: - contents: read - pull-requests: read - -jobs: - - # - # golangci-lint runs a suite of static analysis checks on the code - # - - golangci: - runs-on: ubuntu-latest - name: Golangci-lint - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: 'stable' - - - name: ensure asset generation - run: go run build.go assets - - - name: golangci-lint - uses: golangci/golangci-lint-action@v8 - with: - only-new-issues: true - - # - # Meta checks for formatting, copyright, etc - # - - meta: - name: Meta checks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: 'stable' - - - run: | - go run build.go assets - go test -v ./meta From e61b8a1ae84fbc2982baef3f2ae1f877345697da Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 5 Jun 2025 19:21:01 +0200 Subject: [PATCH 52/62] docs: link to Docker image, APT, in release notes --- relnotes/v2.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 relnotes/v2.md diff --git a/relnotes/v2.md b/relnotes/v2.md new file mode 100644 index 000000000..ed9126031 --- /dev/null +++ b/relnotes/v2.md @@ -0,0 +1,6 @@ +This release is also available as: + +* APT repository: https://apt.syncthing.net/ + +* Docker image: `docker.io/syncthing/syncthing:{{.version}}` or `ghcr.io/syncthing/syncthing:{{.version}}` + (`{docker,ghcr}.io/syncthing/syncthing:2` to follow just the major version) From 3c92999406a34f9c1b2803511ef22054d7de6e82 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 6 Jun 2025 10:32:58 +0200 Subject: [PATCH 53/62] build: allow v2 into APT candidate channel --- .github/workflows/build-syncthing.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index be7b9e1e2..7b7a87100 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -875,9 +875,7 @@ jobs: - name: Prepare packages run: | kind=stable - if [[ $VERSION == v2* ]] ; then - kind=v2 - elif [[ $VERSION == *-rc.[0-9] ]] ; then + if [[ $VERSION == *-rc.[0-9] || $VERSION == *-rc.[0-9][0-9] ]] ; then kind=candidate elif [[ $VERSION == *-* ]] ; then kind=nightly From ef6d561c66d90a3ab0454fe18fc531ded168d3e4 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 6 Jun 2025 11:27:53 +0200 Subject: [PATCH 54/62] chore(sqlite): linter complaints --- .golangci.yml | 2 ++ go.mod | 1 - go.sum | 2 -- internal/db/sqlite/db_folderdb.go | 2 +- internal/db/sqlite/db_open.go | 16 ++++++++++++---- internal/db/sqlite/db_service.go | 8 +++++--- internal/db/sqlite/folderdb_open.go | 6 +++++- internal/db/sqlite/folderdb_update.go | 9 +++++---- 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6d83857a3..3e5c436bc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,12 +8,14 @@ linters: - exhaustive - exhaustruct - forbidigo + - funcorder - funlen - gochecknoglobals - gochecknoinits - gocognit - goconst - gocyclo + - godot - godox - gomoddirectives - inamedparam diff --git a/go.mod b/go.mod index edd012200..5af31ba21 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,6 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/coreos/go-semver v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect diff --git a/go.sum b/go.sum index 5f505877f..a10124f68 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= diff --git a/internal/db/sqlite/db_folderdb.go b/internal/db/sqlite/db_folderdb.go index 5b45713f0..077b73a6a 100644 --- a/internal/db/sqlite/db_folderdb.go +++ b/internal/db/sqlite/db_folderdb.go @@ -255,7 +255,7 @@ func (s *DB) ListDevicesForFolder(folder string) ([]protocol.DeviceID, error) { func (s *DB) RemoteSequences(folder string) (map[protocol.DeviceID]int64, error) { fdb, err := s.getFolderDB(folder, false) if errors.Is(err, errNoSuchFolder) { - return nil, nil + return nil, nil //nolint:nilnil } if err != nil { return nil, err diff --git a/internal/db/sqlite/db_open.go b/internal/db/sqlite/db_open.go index ce8fe7851..072755f3c 100644 --- a/internal/db/sqlite/db_open.go +++ b/internal/db/sqlite/db_open.go @@ -50,10 +50,14 @@ func Open(path string, opts ...Option) (*DB, error) { "sql/schema/common/*", "sql/schema/main/*", } + migrations := []string{ + "sql/migrations/common/*", + "sql/migrations/main/*", + } - os.MkdirAll(path, 0o700) + _ = os.MkdirAll(path, 0o700) mainPath := filepath.Join(path, "main.db") - mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, nil) + mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, migrations) if err != nil { return nil, err } @@ -88,10 +92,14 @@ func OpenForMigration(path string) (*DB, error) { "sql/schema/common/*", "sql/schema/main/*", } + migrations := []string{ + "sql/migrations/common/*", + "sql/migrations/main/*", + } - os.MkdirAll(path, 0o700) + _ = os.MkdirAll(path, 0o700) mainPath := filepath.Join(path, "main.db") - mainBase, err := openBase(mainPath, 1, pragmas, schemas, nil) + mainBase, err := openBase(mainPath, 1, pragmas, schemas, migrations) if err != nil { return nil, err } diff --git a/internal/db/sqlite/db_service.go b/internal/db/sqlite/db_service.go index 635542ad5..38695785e 100644 --- a/internal/db/sqlite/db_service.go +++ b/internal/db/sqlite/db_service.go @@ -84,8 +84,11 @@ func (s *Service) periodic(ctx context.Context) error { defer func() { l.Debugln("Periodic done in", time.Since(t1), "+", t1.Sub(t0)) }() s.sdb.updateLock.Lock() - tidy(ctx, s.sdb.sql) + err := tidy(ctx, s.sdb.sql) s.sdb.updateLock.Unlock() + if err != nil { + return err + } return wrap(s.sdb.forEachFolder(func(fdb *folderDB) error { fdb.updateLock.Lock() @@ -97,8 +100,7 @@ func (s *Service) periodic(ctx context.Context) error { if err := garbageCollectBlocklistsAndBlocksLocked(ctx, fdb); err != nil { return wrap(err) } - tidy(ctx, fdb.sql) - return nil + return tidy(ctx, fdb.sql) })) } diff --git a/internal/db/sqlite/folderdb_open.go b/internal/db/sqlite/folderdb_open.go index f0b96bd28..5c38b112d 100644 --- a/internal/db/sqlite/folderdb_open.go +++ b/internal/db/sqlite/folderdb_open.go @@ -32,8 +32,12 @@ func openFolderDB(folder, path string, deleteRetention time.Duration) (*folderDB "sql/schema/common/*", "sql/schema/folder/*", } + migrations := []string{ + "sql/migrations/common/*", + "sql/migrations/folder/*", + } - base, err := openBase(path, maxDBConns, pragmas, schemas, nil) + base, err := openBase(path, maxDBConns, pragmas, schemas, migrations) if err != nil { return nil, err } diff --git a/internal/db/sqlite/folderdb_update.go b/internal/db/sqlite/folderdb_update.go index 3e5befb9b..f787ab8c3 100644 --- a/internal/db/sqlite/folderdb_update.go +++ b/internal/db/sqlite/folderdb_update.go @@ -433,7 +433,7 @@ type fileRow struct { func (e fileRow) Compare(other fileRow) int { // From FileInfo.WinsConflict - vc := e.Version.Vector.Compare(other.Version.Vector) + vc := e.Version.Compare(other.Version.Vector) switch vc { case protocol.Equal: if e.Invalid != other.Invalid { @@ -519,11 +519,12 @@ func (s *folderDB) periodicCheckpointLocked(fs []protocol.FileInfo) { // failed, we'll keep trying it until we succeed. Increase it faster // when we fail to checkpoint, as it's more likely the WAL is // growing and will need truncation when we get out of this state. - if res == 1 { + switch { + case res == 1: s.checkpointsCount += 10 - } else if res == 0 && checkpointType == "TRUNCATE" { + case res == 0 && checkpointType == "TRUNCATE": s.checkpointsCount = 0 - } else { + default: s.checkpointsCount++ } s.updatePoints = 0 From e25de22705eb96bab878714a8c15136d4a32ce59 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 6 Jun 2025 11:30:10 +0200 Subject: [PATCH 55/62] chore(internal): linter complaints --- .golangci.yml | 1 + internal/db/metrics.go | 1 + internal/db/typed.go | 4 ++-- internal/protoutil/protoutil.go | 6 ++++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 3e5c436bc..153975b35 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,6 +51,7 @@ linters: - std-error-handling paths: - internal/gen + - internal/db/olddb - cmd/dev - repos - third_party$ diff --git a/internal/db/metrics.go b/internal/db/metrics.go index fb41124cf..abb350af3 100644 --- a/internal/db/metrics.go +++ b/internal/db/metrics.go @@ -21,6 +21,7 @@ var ( Namespace: "syncthing", Subsystem: "db", Name: "operations_current", + Help: "Number of database operations currently ongoing, per folder and operation", }, []string{"folder", "operation"}) metricTotalOperationSeconds = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "syncthing", diff --git a/internal/db/typed.go b/internal/db/typed.go index 75ed88921..6180e12b7 100644 --- a/internal/db/typed.go +++ b/internal/db/typed.go @@ -37,7 +37,7 @@ func NewTyped(db KV, prefix string) *Typed { // is overwritten. func (n *Typed) PutInt64(key string, val int64) error { var valBs [8]byte - binary.BigEndian.PutUint64(valBs[:], uint64(val)) + binary.BigEndian.PutUint64(valBs[:], uint64(val)) //nolint:gosec return n.db.PutKV(n.prefixedKey(key), valBs[:]) } @@ -49,7 +49,7 @@ func (n *Typed) Int64(key string) (int64, bool, error) { return 0, false, filterNotFound(err) } val := binary.BigEndian.Uint64(valBs) - return int64(val), true, nil + return int64(val), true, nil //nolint:gosec } // PutTime stores a new time.Time. Any existing value (even if of another diff --git a/internal/protoutil/protoutil.go b/internal/protoutil/protoutil.go index c43057b44..3387b4da5 100644 --- a/internal/protoutil/protoutil.go +++ b/internal/protoutil/protoutil.go @@ -7,14 +7,16 @@ package protoutil import ( - "fmt" + "errors" "google.golang.org/protobuf/proto" ) +var errBufferTooSmall = errors.New("buffer too small") + func MarshalTo(buf []byte, pb proto.Message) (int, error) { if sz := proto.Size(pb); len(buf) < sz { - return 0, fmt.Errorf("buffer too small") + return 0, errBufferTooSmall } else if sz == 0 { return 0, nil } From 700bb750169dc55e41d17ed16860418f43b37127 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 6 Jun 2025 11:54:40 +0200 Subject: [PATCH 56/62] chore(model): the easier linter complaints --- lib/model/blockpullreorderer.go | 4 +-- lib/model/devicedownloadstate.go | 7 ++--- lib/model/folder.go | 8 +++--- lib/model/folder_sendonly.go | 2 +- lib/model/folder_sendrecv.go | 24 ++++++++--------- lib/model/folder_summary.go | 9 ++++--- lib/model/indexhandler.go | 11 +++++--- lib/model/metrics.go | 2 +- lib/model/model.go | 46 +++++++++++++++++--------------- lib/model/sharedpullerstate.go | 2 +- 10 files changed, 62 insertions(+), 53 deletions(-) diff --git a/lib/model/blockpullreorderer.go b/lib/model/blockpullreorderer.go index 15d8fabc6..d566972e2 100644 --- a/lib/model/blockpullreorderer.go +++ b/lib/model/blockpullreorderer.go @@ -51,7 +51,7 @@ type standardBlockPullReorderer struct { } func newStandardBlockPullReorderer(id protocol.DeviceID, otherDevices []protocol.DeviceID) *standardBlockPullReorderer { - allDevices := append(otherDevices, id) + allDevices := append(otherDevices, id) //nolint:gocritic slices.SortFunc(allDevices, func(a, b protocol.DeviceID) int { return a.Compare(b) }) @@ -92,7 +92,7 @@ func (p *standardBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []prot // The rest of the chunks we fetch in a random order in whole chunks. // Generate chunk index slice and shuffle it indexes := make([]int, 0, len(chunks)-1) - for i := range len(chunks) { + for i := range chunks { if i != p.myIndex { indexes = append(indexes, i) } diff --git a/lib/model/devicedownloadstate.go b/lib/model/devicedownloadstate.go index 324cd8513..a76fd0c94 100644 --- a/lib/model/devicedownloadstate.go +++ b/lib/model/devicedownloadstate.go @@ -56,17 +56,18 @@ func (p *deviceFolderDownloadState) Update(updates []protocol.FileDownloadProgre if update.UpdateType == protocol.FileDownloadProgressUpdateTypeForget && ok && local.version.Equal(update.Version) { delete(p.files, update.Name) } else if update.UpdateType == protocol.FileDownloadProgressUpdateTypeAppend { - if !ok { + switch { + case !ok: local = deviceFolderFileDownloadState{ blockIndexes: update.BlockIndexes, version: update.Version, blockSize: update.BlockSize, } - } else if !local.version.Equal(update.Version) { + case !local.version.Equal(update.Version): local.blockIndexes = append(local.blockIndexes[:0], update.BlockIndexes...) local.version = update.Version local.blockSize = update.BlockSize - } else { + default: local.blockIndexes = append(local.blockIndexes, update.BlockIndexes...) } p.files[update.Name] = local diff --git a/lib/model/folder.go b/lib/model/folder.go index d01795df3..0ebe3db3d 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -52,7 +52,7 @@ type folder struct { ignores *ignore.Matcher mtimefs fs.Filesystem modTimeWindow time.Duration - ctx context.Context // used internally, only accessible on serve lifetime + ctx context.Context //nolint:containedctx // used internally, only accessible on serve lifetime done chan struct{} // used externally, accessible regardless of serve scanInterval time.Duration @@ -322,7 +322,7 @@ func (f *folder) Reschedule() { return } // Sleep a random time between 3/4 and 5/4 of the configured interval. - sleepNanos := (f.scanInterval.Nanoseconds()*3 + rand.Int63n(2*f.scanInterval.Nanoseconds())) / 4 + sleepNanos := (f.scanInterval.Nanoseconds()*3 + rand.Int63n(2*f.scanInterval.Nanoseconds())) / 4 //nolint:gosec interval := time.Duration(sleepNanos) * time.Nanosecond l.Debugln(f, "next rescan in", interval) f.scanTimer.Reset(interval) @@ -1111,7 +1111,7 @@ func (f *folder) setWatchError(err error, nextTryIn time.Duration) { prevErr := f.watchErr f.watchErr = err f.watchMut.Unlock() - if err != prevErr { + if err != prevErr { //nolint:errorlint data := map[string]interface{}{ "folder": f.ID, } @@ -1127,7 +1127,7 @@ func (f *folder) setWatchError(err error, nextTryIn time.Duration) { return } msg := fmt.Sprintf("Error while trying to start filesystem watcher for folder %s, trying again in %v: %v", f.Description(), nextTryIn, err) - if prevErr != err { + if prevErr != err { //nolint:errorlint l.Infof(msg) return } diff --git a/lib/model/folder_sendonly.go b/lib/model/folder_sendonly.go index 228452d2e..1273be598 100644 --- a/lib/model/folder_sendonly.go +++ b/lib/model/folder_sendonly.go @@ -28,7 +28,7 @@ func newSendOnlyFolder(model *model, ignores *ignore.Matcher, cfg config.FolderC f := &sendOnlyFolder{ folder: newFolder(model, ignores, cfg, evLogger, ioLimiter, nil), } - f.folder.puller = f + f.puller = f return f } diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index d65b45096..2f659c2f0 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -136,7 +136,7 @@ func newSendReceiveFolder(model *model, ignores *ignore.Matcher, cfg config.Fold blockPullReorderer: newBlockPullReorderer(cfg.BlockPullOrder, model.id, cfg.DeviceIDs()), writeLimiter: semaphore.New(cfg.MaxConcurrentWrites), } - f.folder.puller = f + f.puller = f if f.Copiers == 0 { f.Copiers = defaultCopiers @@ -359,13 +359,14 @@ loop: } case file.IsDeleted(): - if file.IsDirectory() { + switch { + case file.IsDirectory(): // Perform directory deletions at the end, as we may have // files to delete inside them before we get to that point. dirDeletions = append(dirDeletions, file) - } else if file.IsSymlink() { + case file.IsSymlink(): f.deleteFile(file, dbUpdateChan, scanChan) - } else { + default: df, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name) if err != nil { return changed, nil, nil, err @@ -1023,7 +1024,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db tempName := fs.TempName(target.Name) if f.versioner != nil { - err = f.CheckAvailableSpace(uint64(source.Size)) + err = f.CheckAvailableSpace(uint64(source.Size)) //nolint:gosec if err == nil { err = osutil.Copy(f.CopyRangeMethod.ToFS(), f.mtimefs, f.mtimefs, source.Name, tempName) if err == nil { @@ -1295,7 +1296,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } for state := range in { - if err := f.CheckAvailableSpace(uint64(state.file.Size)); err != nil { + if err := f.CheckAvailableSpace(uint64(state.file.Size)); err != nil { //nolint:gosec state.fail(err) // Nothing more to do for this failed file, since it would use to much disk space out <- state.sharedPullerState @@ -1461,7 +1462,7 @@ func (f *sendReceiveFolder) copyBlockFromFile(srcName string, srcOffset int64, s } func (*sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) error { - if len(buf) != int(block.Size) { + if len(buf) != block.Size { return fmt.Errorf("length mismatch %d != %d", len(buf), block.Size) } @@ -1489,8 +1490,7 @@ func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- * // ongoing at any given time, based on the size of the blocks // themselves. - state := state - bytes := int(state.block.Size) + bytes := state.block.Size if err := requestLimiter.TakeWithContext(f.ctx, bytes); err != nil { state.fail(err) @@ -1713,7 +1713,7 @@ func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) { // sync directories for dir := range changedDirs { delete(changedDirs, dir) - if !f.FolderConfiguration.DisableFsync { + if !f.DisableFsync { fd, err := f.mtimefs.Open(dir) if err != nil { l.Debugf("fsync %q failed: %v", dir, err) @@ -1996,7 +1996,7 @@ func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, scanChan c // Lets just assume the file has changed. scanChan <- path hasToBeScanned = true - return nil + return nil //nolint:nilerr } if !cf.IsEquivalentOptional(diskFile, protocol.FileInfoComparison{ ModTimeWindow: f.modTimeWindow, @@ -2055,7 +2055,7 @@ func (f *sendReceiveFolder) deleteDirOnDiskHandleChildren(dir string, scanChan c // not changed. func (f *sendReceiveFolder) scanIfItemChanged(name string, stat fs.FileInfo, item protocol.FileInfo, hasItem bool, fromDelete bool, scanChan chan<- string) (err error) { defer func() { - if err == errModified { + if errors.Is(err, errModified) { scanChan <- name } }() diff --git a/lib/model/folder_summary.go b/lib/model/folder_summary.go index ad71fb158..c240f0f90 100644 --- a/lib/model/folder_summary.go +++ b/lib/model/folder_summary.go @@ -11,6 +11,7 @@ package model import ( "context" + "errors" "fmt" "strings" "time" @@ -125,7 +126,7 @@ func (c *folderSummaryService) Summary(folder string) (*FolderSummary, error) { var local, global, need, ro db.Counts var ourSeq int64 var remoteSeq map[protocol.DeviceID]int64 - errors, err := c.model.FolderErrors(folder) + errs, err := c.model.FolderErrors(folder) if err == nil { global, _ = c.model.GlobalSize(folder) local, _ = c.model.LocalSize(folder, protocol.LocalDeviceID) @@ -137,12 +138,12 @@ func (c *folderSummaryService) Summary(folder string) (*FolderSummary, error) { // For API backwards compatibility (SyncTrayzor needs it) an empty folder // summary is returned for not running folders, an error might actually be // more appropriate - if err != nil && err != ErrFolderPaused && err != ErrFolderNotRunning { + if err != nil && !errors.Is(err, ErrFolderPaused) && !errors.Is(err, ErrFolderNotRunning) { return nil, err } - res.Errors = len(errors) - res.PullErrors = len(errors) // deprecated + res.Errors = len(errs) + res.PullErrors = len(errs) // deprecated res.Invalid = "" // Deprecated, retains external API for now diff --git a/lib/model/indexhandler.go b/lib/model/indexhandler.go index d8c45da24..6ba8e34c7 100644 --- a/lib/model/indexhandler.go +++ b/lib/model/indexhandler.go @@ -66,7 +66,8 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f // about us. Lets check to see if we can start sending index // updates directly or need to send the index from start... - if startInfo.local.IndexID == myIndexID { + switch startInfo.local.IndexID { + case myIndexID: // They say they've seen our index ID before, so we can // send a delta update only. @@ -83,15 +84,17 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f l.Debugf("Device %v folder %s is delta index compatible (mlv=%d)", conn.DeviceID().Short(), folder.Description(), startInfo.local.MaxSequence) startSequence = startInfo.local.MaxSequence } - } else if startInfo.local.IndexID != 0 { + + case 0: + l.Debugf("Device %v folder %s has no index ID for us", conn.DeviceID().Short(), folder.Description()) + + default: // They say they've seen an index ID from us, but it's // not the right one. Either they are confused or we // must have reset our database since last talking to // them. We'll start with a full index transfer. l.Infof("Device %v folder %s has mismatching index ID for us (%v != %v)", conn.DeviceID().Short(), folder.Description(), startInfo.local.IndexID, myIndexID) startSequence = 0 - } else { - l.Debugf("Device %v folder %s has no index ID for us", conn.DeviceID().Short(), folder.Description()) } // This is the other side's description of themselves. We diff --git a/lib/model/metrics.go b/lib/model/metrics.go index 5c0ed370f..24fdff5d9 100644 --- a/lib/model/metrics.go +++ b/lib/model/metrics.go @@ -18,7 +18,7 @@ var ( Name: "folder_state", Help: "Current folder state", }, []string{"folder"}) - metricFolderSummary = promauto.NewGaugeVec(prometheus.GaugeOpts{ + metricFolderSummary = promauto.NewGaugeVec(prometheus.GaugeOpts{ //nolint:promlinter Namespace: "syncthing", Subsystem: "model", Name: "folder_summary", diff --git a/lib/model/model.go b/lib/model/model.go index f504e6aed..623adae13 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -195,7 +195,7 @@ var ( ErrFolderMissing = errors.New("no such folder") errNoVersioner = errors.New("folder has no versioner") // errors about why a connection is closed - errStopped = errors.New("Syncthing is being stopped") + errStopped = errors.New("Syncthing is being stopped") //nolint:staticcheck errEncryptionInvConfigLocal = errors.New("can't encrypt outgoing data because local data is encrypted (folder-type receive-encrypted)") errEncryptionInvConfigRemote = errors.New("remote has encrypted data and encrypts that data for us - this is impossible") errEncryptionNotEncryptedLocal = errors.New("remote expects to exchange encrypted data, but is configured for plain data") @@ -622,24 +622,26 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe for _, line := range lines { // Allow prefixes to be specified in any order, but only once. + loop: for { - if strings.HasPrefix(line, "!") && !seenPrefix[0] { + switch { + case strings.HasPrefix(line, "!") && !seenPrefix[0]: seenPrefix[0] = true line = line[1:] report.IgnoreStats.Inverts++ - } else if strings.HasPrefix(line, "(?i)") && !seenPrefix[1] { + case strings.HasPrefix(line, "(?i)") && !seenPrefix[1]: seenPrefix[1] = true line = line[4:] report.IgnoreStats.Folded++ - } else if strings.HasPrefix(line, "(?d)") && !seenPrefix[2] { + case strings.HasPrefix(line, "(?d)") && !seenPrefix[2]: seenPrefix[2] = true line = line[4:] report.IgnoreStats.Deletable++ - } else { + default: seenPrefix[0] = false seenPrefix[1] = false seenPrefix[2] = false - break + break loop } } @@ -1227,9 +1229,10 @@ func (m *model) ClusterConfig(conn protocol.Connection, cm *protocol.ClusterConf for _, folder := range cm.Folders { info := &clusterConfigDeviceInfo{} for _, dev := range folder.Devices { - if dev.ID == m.id { + switch dev.ID { + case m.id: info.local = dev - } else if dev.ID == deviceID { + case deviceID: info.remote = dev } if info.local.ID != protocol.EmptyDeviceID && info.remote.ID != protocol.EmptyDeviceID { @@ -1451,7 +1454,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi sameError := false m.mut.Lock() if devs, ok := m.folderEncryptionFailures[folder.ID]; ok { - sameError = devs[deviceID] == err + sameError = devs[deviceID] == err //nolint:errorlint } else { m.folderEncryptionFailures[folder.ID] = make(map[protocol.DeviceID]error) } @@ -1461,7 +1464,8 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi if sameError { l.Debugln(msg) } else { - if rerr, ok := err.(*redactedError); ok { + var rerr *redactedError + if errors.As(err, &rerr) { err = rerr.redacted } m.evLogger.Log(events.Failure, err.Error()) @@ -2015,7 +2019,7 @@ func (m *model) Request(conn protocol.Connection, req *protocol.Request) (out pr // The requestResponse releases the bytes to the buffer pool and the // limiters when its Close method is called. - res := newLimitedRequestResponse(int(req.Size), limiter, m.globalRequestLimiter) + res := newLimitedRequestResponse(req.Size, limiter, m.globalRequestLimiter) defer func() { // Close it ourselves if it isn't returned due to an error @@ -2061,16 +2065,17 @@ func (m *model) Request(conn protocol.Connection, req *protocol.Request) (out pr } n, err := readOffsetIntoBuf(folderFs, req.Name, req.Offset, res.data) - if fs.IsNotExist(err) { + switch { + case fs.IsNotExist(err): l.Debugf("%v REQ(in) file doesn't exist: %s: %q / %q o=%d s=%d", m, deviceID.Short(), req.Folder, req.Name, req.Offset, req.Size) return nil, protocol.ErrNoSuchFile - } else if err == io.EOF { + case errors.Is(err, io.EOF): // Read beyond end of file. This might indicate a problem, or it // might be a short block that gets padded when read for encrypted // folders. We ignore the error and let the hash validation in the // next step take care of it, by only hashing the part we actually // managed to read. - } else if err != nil { + case err != nil: l.Debugf("%v REQ(in) failed reading file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), req.Folder, req.Name, req.Offset, req.Size) return nil, protocol.ErrGeneric } @@ -2230,13 +2235,13 @@ func (m *model) SetIgnores(folder string, content []string) error { func (m *model) setIgnores(cfg config.FolderConfiguration, content []string) error { err := cfg.CheckPath() - if err == config.ErrPathMissing { + if errors.Is(err, config.ErrPathMissing) { if err = cfg.CreateRoot(); err != nil { return fmt.Errorf("failed to create folder root: %w", err) } err = cfg.CheckPath() } - if err != nil && err != config.ErrMarkerMissing { + if err != nil && !errors.Is(err, config.ErrMarkerMissing) { return err } @@ -2498,7 +2503,6 @@ func (m *model) ScanFolders() map[string]error { wg := sync.NewWaitGroup() wg.Add(len(folders)) for _, folder := range folders { - folder := folder go func() { err := m.ScanFolder(folder) if err != nil { @@ -2677,7 +2681,7 @@ func (m *model) WatchError(folder string) error { runner, _ := m.folderRunners.Get(folder) m.mut.RUnlock() if err != nil { - return nil // If the folder isn't running, there's no error to report. + return nil //nolint:nilerr // If the folder isn't running, there's no error to report. } return runner.WatchError() } @@ -2744,7 +2748,7 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly prefix = osutil.NativeFilename(prefix) if prefix != "" && !strings.HasSuffix(prefix, sep) { - prefix = prefix + sep + prefix += sep } for f, err := range itererr.Zip(m.sdb.AllGlobalFilesPrefix(folder, prefix)) { @@ -3456,8 +3460,8 @@ type updatedPendingFolder struct { // redactPathError checks if the error is actually a os.PathError, and if yes // returns a redactedError with the path removed. func redactPathError(err error) (error, bool) { - perr, ok := err.(*os.PathError) - if !ok { + var perr *os.PathError + if !errors.As(err, &perr) { return nil, false } return &redactedError{ diff --git a/lib/model/sharedpullerstate.go b/lib/model/sharedpullerstate.go index 623011cbc..f0b66f685 100644 --- a/lib/model/sharedpullerstate.go +++ b/lib/model/sharedpullerstate.go @@ -384,7 +384,7 @@ func writeEncryptionTrailer(file protocol.FileInfo, writer io.WriterAt) (int64, if err != nil { return 0, err } - binary.BigEndian.PutUint32(bs[n:], uint32(n)) + binary.BigEndian.PutUint32(bs[n:], uint32(n)) //nolint:gosec bs = bs[:n+4] if _, err := writer.WriteAt(bs, wireFile.Size); err != nil { From 10d20c480009575cc0313ec18f1d9a3289fd0e16 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 6 Jun 2025 12:03:28 +0200 Subject: [PATCH 57/62] chore(fs): linter complaints --- lib/fs/basicfs.go | 8 ++++---- lib/fs/basicfs_fileinfo_bsdish.go | 2 +- lib/fs/basicfs_unix.go | 2 +- lib/fs/basicfs_xattr_unix.go | 2 +- lib/fs/casefs.go | 10 +++++----- lib/fs/fakefs.go | 22 ++++++++++++---------- lib/fs/filesystem.go | 9 +++++---- lib/fs/filesystem_copy_range_standard.go | 3 ++- lib/fs/mtimefs.go | 2 +- lib/fs/types.go | 6 ++++-- lib/fs/util.go | 2 +- lib/fs/walkfs.go | 6 +++--- lib/model/folder.go | 2 +- lib/model/folder_sendrecv.go | 4 ++-- lib/model/folder_sendrecv_test.go | 2 +- 15 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lib/fs/basicfs.go b/lib/fs/basicfs.go index 098769870..4ed60d10e 100644 --- a/lib/fs/basicfs.go +++ b/lib/fs/basicfs.go @@ -379,12 +379,12 @@ func longFilenameSupport(path string) string { return path } -type ErrWatchEventOutsideRoot struct{ msg string } +type WatchEventOutsideRootError struct{ msg string } -func (e *ErrWatchEventOutsideRoot) Error() string { +func (e *WatchEventOutsideRootError) Error() string { return e.msg } -func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath string, roots []string) *ErrWatchEventOutsideRoot { - return &ErrWatchEventOutsideRoot{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, roots==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, roots, absPath)} +func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath string, roots []string) *WatchEventOutsideRootError { + return &WatchEventOutsideRootError{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, roots==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, roots, absPath)} } diff --git a/lib/fs/basicfs_fileinfo_bsdish.go b/lib/fs/basicfs_fileinfo_bsdish.go index b8e60f329..10061ebfb 100644 --- a/lib/fs/basicfs_fileinfo_bsdish.go +++ b/lib/fs/basicfs_fileinfo_bsdish.go @@ -15,7 +15,7 @@ import ( ) func (fi basicFileInfo) InodeChangeTime() time.Time { - if sys, ok := fi.FileInfo.Sys().(*syscall.Stat_t); ok { + if sys, ok := fi.Sys().(*syscall.Stat_t); ok { return time.Unix(0, sys.Ctimespec.Nano()) } return time.Time{} diff --git a/lib/fs/basicfs_unix.go b/lib/fs/basicfs_unix.go index e1b3967eb..1a54b2652 100644 --- a/lib/fs/basicfs_unix.go +++ b/lib/fs/basicfs_unix.go @@ -86,7 +86,7 @@ func (f *BasicFilesystem) Remove(name string) error { // unrooted) or an error if the given path is not a subpath and handles the // special case when the given path is the folder root without a trailing // pathseparator. -func (f *BasicFilesystem) unrootedChecked(absPath string, roots []string) (string, *ErrWatchEventOutsideRoot) { +func (f *BasicFilesystem) unrootedChecked(absPath string, roots []string) (string, *WatchEventOutsideRootError) { for _, root := range roots { // Make sure the root ends with precisely one path separator, to // ease prefix comparisons. diff --git a/lib/fs/basicfs_xattr_unix.go b/lib/fs/basicfs_xattr_unix.go index f43445c87..f32929708 100644 --- a/lib/fs/basicfs_xattr_unix.go +++ b/lib/fs/basicfs_xattr_unix.go @@ -69,7 +69,7 @@ var xattrBufPool = sync.Pool{ } func getXattr(path, name string) ([]byte, error) { - buf := xattrBufPool.Get().([]byte) + buf := xattrBufPool.Get().([]byte) //nolint:forcetypeassert defer func() { // Put the buffer back in the pool, or not if we're not supposed to // (we returned it to the caller). diff --git a/lib/fs/casefs.go b/lib/fs/casefs.go index d88ccc0a4..bf8ecb63b 100644 --- a/lib/fs/casefs.go +++ b/lib/fs/casefs.go @@ -24,16 +24,16 @@ const ( caseCacheItemLimit = 4 << 10 ) -type ErrCaseConflict struct { +type CaseConflictError struct { Given, Real string } -func (e *ErrCaseConflict) Error() string { +func (e *CaseConflictError) Error() string { return fmt.Sprintf(`remote "%v" uses different upper or lowercase characters than local "%v"; change the casing on either side to match the other`, e.Given, e.Real) } func IsErrCaseConflict(err error) bool { - e := &ErrCaseConflict{} + e := &CaseConflictError{} return errors.As(err, &e) } @@ -239,7 +239,7 @@ func (f *caseFilesystem) Rename(oldpath, newpath string) error { } if err := f.checkCase(newpath); err != nil { // Case-only rename is ok - e := &ErrCaseConflict{} + e := &CaseConflictError{} if !errors.As(err, &e) || e.Real != oldpath { return err } @@ -389,7 +389,7 @@ func (f *caseFilesystem) checkCaseExisting(name string) error { // comparing, as we don't want to treat a normalization difference as a // case conflict. if norm.NFC.String(realName) != norm.NFC.String(name) { - return &ErrCaseConflict{name, realName} + return &CaseConflictError{name, realName} } return nil } diff --git a/lib/fs/fakefs.go b/lib/fs/fakefs.go index af508f18c..3c056eab6 100644 --- a/lib/fs/fakefs.go +++ b/lib/fs/fakefs.go @@ -147,29 +147,29 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS { // *look* like file I/O, but they are not. Do not worry that they // might fail. - rng := rand.New(rand.NewSource(int64(seed))) + rng := rand.New(rand.NewSource(int64(seed))) //nolint:gosec var createdFiles int var writtenData int64 for (files == 0 || createdFiles < files) && (maxsize == 0 || writtenData>>20 < int64(maxsize)) { dir := filepath.Join(fmt.Sprintf("%02x", rng.Intn(255)), fmt.Sprintf("%02x", rng.Intn(255))) file := fmt.Sprintf("%016x", rng.Int63()) - fs.MkdirAll(dir, 0o755) + _ = fs.MkdirAll(dir, 0o755) fd, _ := fs.Create(filepath.Join(dir, file)) createdFiles++ fsize := int64(sizeavg/2 + rng.Intn(sizeavg)) - fd.Truncate(fsize) + _ = fd.Truncate(fsize) writtenData += fsize ftime := time.Unix(1000000000+rng.Int63n(10*365*86400), 0) - fs.Chtimes(filepath.Join(dir, file), ftime, ftime) + _ = fs.Chtimes(filepath.Join(dir, file), ftime, ftime) } } if !nostfolder { // Also create a default folder marker for good measure - fs.Mkdir(".stfolder", 0o700) + _ = fs.Mkdir(".stfolder", 0o700) } // We only set the latency after doing the operations required to create @@ -284,9 +284,10 @@ func (fs *fakeFS) create(name string) (*fakeEntry, error) { time.Sleep(fs.latency) if entry := fs.entryForName(name); entry != nil { - if entry.entryType == fakeEntryTypeDir { + switch entry.entryType { + case fakeEntryTypeDir: return nil, os.ErrExist - } else if entry.entryType == fakeEntryTypeSymlink { + case fakeEntryTypeSymlink: return nil, errors.New("following symlink not supported") } entry.size = 0 @@ -731,6 +732,7 @@ func (fs *fakeFS) resetCounters() { } func (fs *fakeFS) reportMetricsPerOp(b *testing.B) { + b.Helper() fs.reportMetricsPer(b, 1, "op") } @@ -829,7 +831,7 @@ func (f *fakeFile) readShortAt(p []byte, offs int64) (int, error) { if f.seed == 0 { hf := fnv.New64() hf.Write([]byte(f.name)) - f.seed = int64(hf.Sum64()) + f.seed = int64(hf.Sum64()) //nolint:gosec } // Check whether the read is a continuation of an RNG we already have or @@ -839,14 +841,14 @@ func (f *fakeFile) readShortAt(p []byte, offs int64) (int, error) { nextBlockOffs := (seedNo + 1) << randomBlockShift if f.rng == nil || f.offset != offs || seedNo != f.seedOffs { // This is not a straight read continuing from a previous one - f.rng = rand.New(rand.NewSource(f.seed + seedNo)) + f.rng = rand.New(rand.NewSource(f.seed + seedNo)) //nolint:gosec // If the read is not at the start of the block, discard data // accordingly. diff := offs - minOffs if diff > 0 { lr := io.LimitReader(f.rng, diff) - io.Copy(io.Discard, lr) + _, _ = io.Copy(io.Discard, lr) } f.offset = offs diff --git a/lib/fs/filesystem.go b/lib/fs/filesystem.go index fa34fe9fd..368ba1ca5 100644 --- a/lib/fs/filesystem.go +++ b/lib/fs/filesystem.go @@ -180,7 +180,7 @@ const ( // SkipDir is used as a return value from WalkFuncs to indicate that // the directory named in the call is to be skipped. It is not returned // as an error by any function. -var SkipDir = filepath.SkipDir +var SkipDir = filepath.SkipDir //nolint:errname func IsExist(err error) bool { return errors.Is(err, ErrExist) @@ -259,15 +259,16 @@ func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem // attributed to the calling function. layersAboveWalkFilesystem++ } - if l.ShouldDebug("walkfs") { + switch { + case l.ShouldDebug("walkfs"): // A walkFilesystem is not a layer to skip, it embeds the underlying // filesystem, passing calls directly trough. Except for calls made // during walking, however those are truly originating in the walk // filesystem. fs = NewWalkFilesystem(newLogFilesystem(fs, layersAboveWalkFilesystem)) - } else if l.ShouldDebug("fs") { + case l.ShouldDebug("fs"): fs = newLogFilesystem(NewWalkFilesystem(fs), layersAboveWalkFilesystem) - } else { + default: fs = NewWalkFilesystem(fs) } diff --git a/lib/fs/filesystem_copy_range_standard.go b/lib/fs/filesystem_copy_range_standard.go index cd49fafe0..f59e0fd64 100644 --- a/lib/fs/filesystem_copy_range_standard.go +++ b/lib/fs/filesystem_copy_range_standard.go @@ -7,6 +7,7 @@ package fs import ( + "errors" "io" ) @@ -28,7 +29,7 @@ func copyRangeStandard(src, dst File, srcOffset, dstOffset, size int64) error { } n, err := src.ReadAt(buf, srcOffset) if err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { return io.ErrUnexpectedEOF } return err diff --git a/lib/fs/mtimefs.go b/lib/fs/mtimefs.go index 3264e510c..523652d77 100644 --- a/lib/fs/mtimefs.go +++ b/lib/fs/mtimefs.go @@ -68,7 +68,7 @@ func (*optionMtime) String() string { func (f *mtimeFS) Chtimes(name string, atime, mtime time.Time) error { // Do a normal Chtimes call, don't care if it succeeds or not. - f.chtimes(name, atime, mtime) + _ = f.chtimes(name, atime, mtime) // Stat the file to see what happened. Here we *do* return an error, // because it might be "does not exist" or similar. diff --git a/lib/fs/types.go b/lib/fs/types.go index 7dc86c9fb..224e24389 100644 --- a/lib/fs/types.go +++ b/lib/fs/types.go @@ -26,8 +26,10 @@ type Option interface { type FilesystemFactory func(string, ...Option) (Filesystem, error) // For each registered file system type, a function to construct a file system. -var filesystemFactories map[FilesystemType]FilesystemFactory = make(map[FilesystemType]FilesystemFactory) -var filesystemFactoriesMutex sync.Mutex = sync.Mutex{} +var ( + filesystemFactories map[FilesystemType]FilesystemFactory = make(map[FilesystemType]FilesystemFactory) + filesystemFactoriesMutex sync.Mutex = sync.Mutex{} +) // Register a function to be called when a filesystem is to be constructed with // the specified fsType. The function will receive the URI for the file system as well diff --git a/lib/fs/util.go b/lib/fs/util.go index 41ee39300..94a0a5416 100644 --- a/lib/fs/util.go +++ b/lib/fs/util.go @@ -192,7 +192,7 @@ func CommonPrefix(first, second string) string { } common := make([]string, 0, count) - for i := 0; i < count; i++ { + for i := range count { if firstParts[i] != secondParts[i] { break } diff --git a/lib/fs/walkfs.go b/lib/fs/walkfs.go index 36bce97a7..a6c8613bb 100644 --- a/lib/fs/walkfs.go +++ b/lib/fs/walkfs.go @@ -89,7 +89,7 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ances err = walkFn(path, info, nil) if err != nil { - if info.IsDir() && err == SkipDir { + if info.IsDir() && errors.Is(err, SkipDir) { return nil } return err @@ -117,13 +117,13 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ances filename := filepath.Join(path, name) fileInfo, err := f.Lstat(filename) if err != nil { - if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir { + if err := walkFn(filename, fileInfo, err); err != nil && !errors.Is(err, SkipDir) { return err } } else { err = f.walk(filename, fileInfo, walkFn, ancestors) if err != nil { - if !fileInfo.IsDir() || err != SkipDir { + if !fileInfo.IsDir() || !errors.Is(err, SkipDir) { return err } } diff --git a/lib/model/folder.go b/lib/model/folder.go index 0ebe3db3d..8d98e8495 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -1077,7 +1077,7 @@ func (f *folder) monitorWatch(ctx context.Context) { f.setWatchError(err, next) // This error was previously a panic and should never occur, so generate // a warning, but don't do it repetitively. - var errOutside *fs.ErrWatchEventOutsideRoot + var errOutside *fs.WatchEventOutsideRootError if errors.As(err, &errOutside) { if !warnedOutside { l.Warnln(err) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 2f659c2f0..ac84fd832 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -977,7 +977,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db } switch stat, serr := f.mtimefs.Lstat(target.Name); { case serr != nil: - var caseErr *fs.ErrCaseConflict + var caseErr *fs.CaseConflictError switch { case errors.As(serr, &caseErr): if caseErr.Real != source.Name { @@ -1149,7 +1149,7 @@ func (f *sendReceiveFolder) reuseBlocks(blocks []protocol.BlockInfo, reused []in // reuse. tempBlocks, err := scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil) if err != nil { - var caseErr *fs.ErrCaseConflict + var caseErr *fs.CaseConflictError if errors.As(err, &caseErr) { if rerr := f.mtimefs.Rename(caseErr.Real, tempName); rerr == nil { tempBlocks, err = scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil) diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index 822a567a4..5eff7a19c 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -1014,7 +1014,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) { default: } - var caseErr *fs.ErrCaseConflict + var caseErr *fs.CaseConflictError if !errors.As(err, &caseErr) { t.Error("Expected case conflict error, got", err) } From 40ab668a735bd681d6a6b197e676a9160ce876f0 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 7 Jun 2025 08:19:23 +0200 Subject: [PATCH 58/62] chore(etc): add option dash to upstart config --- etc/linux-upstart/user/syncthing.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/linux-upstart/user/syncthing.conf b/etc/linux-upstart/user/syncthing.conf index 0ef9efa7f..fc973073f 100644 --- a/etc/linux-upstart/user/syncthing.conf +++ b/etc/linux-upstart/user/syncthing.conf @@ -18,4 +18,4 @@ env STNORESTART=yes respawn # the syncthing command Upstart is to execute when it is started up -exec $SYNCTHING_EXE -no-browser +exec $SYNCTHING_EXE --no-browser From 9922a3abd96a17f7edc4453919e857da21ab8d09 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 7 Jun 2025 09:34:49 +0200 Subject: [PATCH 59/62] build: more resilient pushes to releases --- .github/workflows/build-syncthing.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index 48a650939..64699f3d0 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -716,7 +716,7 @@ jobs: RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }} RCLONE_CONFIG_OBJSTORE_ACL: public-read with: - args: sync -v packages objstore:nightly + args: sync -v --no-update-modtime packages objstore:nightly # # Push release artifacts to Spaces @@ -772,7 +772,7 @@ jobs: RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }} RCLONE_CONFIG_OBJSTORE_ACL: public-read with: - args: sync -v packages objstore:release/${{ env.VERSION }} + args: sync -v --no-update-modtime packages objstore:release/${{ env.VERSION }} - name: Push to object store (latest) uses: docker://docker.io/rclone/rclone:latest @@ -785,7 +785,7 @@ jobs: RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }} RCLONE_CONFIG_OBJSTORE_ACL: public-read with: - args: sync -v objstore:release/${{ env.VERSION }} objstore:release/latest + args: sync -v --no-update-modtime objstore:release/${{ env.VERSION }} objstore:release/latest - name: Create GitHub releases and push binaries run: | @@ -800,7 +800,7 @@ jobs: --title "$VERSION" \ --notes-from-tag fi - gh release upload "$VERSION" \ + gh release upload --clobber "$VERSION" \ packages/*.asc packages/*.json \ packages/syncthing-*.tar.gz \ packages/syncthing-*.zip \ @@ -816,7 +816,7 @@ jobs: --title "$VERSION" \ --notes "https://github.com/syncthing/syncthing/releases/tag/$VERSION" fi - gh release upload "$VERSION" \ + gh release upload --clobber "$VERSION" \ $PKGS/*.asc \ $PKGS/*${repo}* done @@ -899,7 +899,7 @@ jobs: RCLONE_CONFIG_OBJSTORE_REGION: ${{ secrets.S3_REGION }} RCLONE_CONFIG_OBJSTORE_ACL: public-read with: - args: sync -v dists objstore:apt/dists + args: sync -v --no-update-modtime dists objstore:apt/dists # # Build and push to Docker Hub From 5381178c464fec76f33563ff0eb281b7ee2392fa Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 7 Jun 2025 13:11:52 +0200 Subject: [PATCH 60/62] build: streamline gathering of facts, checkouts (#10158) --- .github/workflows/build-syncthing.yaml | 235 +++++++++++++------------ build.go | 5 +- 2 files changed, 122 insertions(+), 118 deletions(-) diff --git a/.github/workflows/build-syncthing.yaml b/.github/workflows/build-syncthing.yaml index f3c9f9a0b..1d473ccd1 100644 --- a/.github/workflows/build-syncthing.yaml +++ b/.github/workflows/build-syncthing.yaml @@ -37,6 +37,51 @@ env: jobs: + # + # Source + # + + facts: + name: Gather common facts + runs-on: ubuntu-latest + outputs: + version: ${{ steps.get-version.outputs.version }} + release-kind: ${{ steps.get-version.outputs.release-kind }} + go-version: ${{ steps.get-go.outputs.go-version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 + + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: false + check-latest: true + + - name: Get Syncthing version + id: get-version + run: | + version=$(go run build.go version) + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Version: $version" + + kind=stable + if [[ $version == *-rc.[0-9] || $version == *-rc.[0-9][0-9] ]] ; then + kind=candidate + elif [[ $version == *-* ]] ; then + kind=nightly + fi + echo "release-kind=$kind" >> "$GITHUB_OUTPUT" + echo "Release kind: $kind" + + - name: Get Go version + id: get-go + run: | + go version + echo "go-version=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_OUTPUT + # # Tests for all platforms. Runs a matrix build on Windows, Linux and Mac, # with the list of expected supported Go versions (current, previous). @@ -123,17 +168,18 @@ jobs: package-windows: name: Package for Windows runs-on: ubuntu-latest + needs: + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - uses: mlugg/setup-zig@v2 @@ -142,7 +188,7 @@ jobs: path: | ~/.cache/go-build ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-windows-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-windows-${{ hashFiles('**/go.sum') }} - name: Install dependencies run: | @@ -231,22 +277,18 @@ jobs: package-linux: name: Package for Linux runs-on: ubuntu-latest + needs: + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - - - name: Get actual Go version - run: | - go version - echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV - uses: mlugg/setup-zig@v2 @@ -255,7 +297,7 @@ jobs: path: | ~/.cache/go-build ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('**/go.sum') }} - name: Create packages run: | @@ -297,32 +339,27 @@ jobs: name: Package for macOS if: github.repository_owner == 'syncthing' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/release-nightly' || startsWith(github.ref, 'refs/tags/v')) environment: release + runs-on: macos-latest + needs: + - facts env: CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }} - runs-on: macos-latest + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - - - name: Get actual Go version - run: | - go version - echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV - uses: actions/cache@v4 with: path: | ~/.cache/go-build ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('**/go.sum') }} - name: Import signing certificate if: env.CODESIGN_IDENTITY != '' @@ -432,29 +469,25 @@ jobs: package-cross: name: Package cross compiled runs-on: ubuntu-latest + needs: + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - - - name: Get actual Go version - run: | - go version - echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV - uses: actions/cache@v4 with: path: | ~/.cache/go-build ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-cross-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-cross-${{ hashFiles('**/go.sum') }} - name: Create packages run: | @@ -502,33 +535,33 @@ jobs: package-source: name: Package source code runs-on: ubuntu-latest + needs: + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - name: Package source run: | - version=$(go run build.go version) - echo "$version" > RELEASE + echo "$VERSION" > RELEASE go mod vendor go run build.go assets cd .. - tar c -z -f "syncthing-source-$version.tar.gz" \ + tar c -z -f "syncthing-source-$VERSION.tar.gz" \ --exclude .git \ syncthing - mv "syncthing-source-$version.tar.gz" syncthing + mv "syncthing-source-$VERSION.tar.gz" syncthing - name: Archive artifacts uses: actions/upload-artifact@v4 @@ -550,27 +583,26 @@ jobs: - package-macos - package-cross - package-source + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/checkout@v4 with: repository: syncthing/release-tools path: tools - fetch-depth: 0 - name: Download artifacts uses: actions/download-artifact@v4 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - name: Install signing tool run: | @@ -597,9 +629,6 @@ jobs: sha256sum "${files[@]}" > sha256sum.txt popd - version=$(go run build.go version) - echo "VERSION=$version" >> $GITHUB_ENV - - name: Sign shasum files uses: docker://ghcr.io/kastelo/ezapt:latest with: @@ -635,22 +664,18 @@ jobs: package-debian: name: Package for Debian runs-on: ubuntu-latest + needs: + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - - - name: Get actual Go version - run: | - go version - echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV - uses: ruby/setup-ruby@v1 with: @@ -667,7 +692,7 @@ jobs: path: | ~/.cache/go-build ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-debian-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-debian-${{ hashFiles('**/go.sum') }} - name: Package for Debian (CGO) run: | @@ -697,13 +722,13 @@ jobs: environment: release needs: - sign-for-upgrade + - facts runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: repository: syncthing/release-tools path: tools - fetch-depth: 0 - name: Download artifacts uses: actions/download-artifact@v4 @@ -713,9 +738,8 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - name: Create release json run: | @@ -750,12 +774,13 @@ jobs: needs: - sign-for-upgrade - package-debian + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - name: Download signed packages uses: actions/download-artifact@v4 @@ -771,14 +796,8 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - - - name: Set version - run: | - version=$(go run build.go version) - echo "VERSION=$version" >> $GITHUB_ENV - name: Push to object store (${{ env.VERSION }}) uses: docker://docker.io/rclone/rclone:latest @@ -852,12 +871,13 @@ jobs: environment: release needs: - package-debian + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - name: Download packages uses: actions/download-artifact@v4 @@ -865,24 +885,11 @@ jobs: name: debian-packages path: packages - - name: Set version - run: | - version=$(go run build.go version) - echo "Version: $version" - echo "VERSION=$version" >> $GITHUB_ENV - # Decide whether packages should go to stable, candidate or nightly - name: Prepare packages run: | - kind=stable - if [[ $VERSION == *-rc.[0-9] || $VERSION == *-rc.[0-9][0-9] ]] ; then - kind=candidate - elif [[ $VERSION == *-* ]] ; then - kind=nightly - fi - echo "Kind: $kind" - mkdir -p packages/syncthing/$kind - mv packages/*.deb packages/syncthing/$kind + mkdir -p packages/syncthing/$RELEASE_KIND + mv packages/*.deb packages/syncthing/$RELEASE_KIND - name: Pull archive uses: docker://docker.io/rclone/rclone:latest @@ -930,6 +937,11 @@ jobs: permissions: contents: read packages: write + needs: + - facts + env: + VERSION: ${{ needs.facts.outputs.version }} + RELEASE_KIND: ${{ needs.facts.outputs.release-kind }} strategy: matrix: pkg: @@ -948,20 +960,11 @@ jobs: image: discosrv steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - - - name: Get actual Go version - run: | - go version - echo "GO_VERSION=$(go version | sed 's#^.*go##;s# .*##')" >> $GITHUB_ENV - uses: mlugg/setup-zig@v2 @@ -970,7 +973,7 @@ jobs: path: | ~/.cache/go-build ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }} - name: Build binaries (CGO) run: | @@ -1002,8 +1005,7 @@ jobs: - name: Set version tags run: | - version=$(go run build.go version) - version=${version#v} + version=${VERSION#v} repo=ghcr.io/${{ github.repository_owner }}/${{ matrix.image }} ref="${{github.ref_name}}" ref=${ref//\//-} # slashes to dashes @@ -1022,9 +1024,7 @@ jobs: fi echo Pushing to $tags - echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV - echo "VERSION=$version" >> $GITHUB_ENV - name: Build and push Docker image uses: docker/build-push-action@v5 @@ -1069,14 +1069,15 @@ jobs: govulncheck: runs-on: ubuntu-latest name: Run govulncheck + needs: + - facts steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.facts.outputs.go-version }} cache: false - check-latest: true - name: run govulncheck run: | diff --git a/build.go b/build.go index e920845a0..b03fedf1c 100644 --- a/build.go +++ b/build.go @@ -922,6 +922,9 @@ func rmr(paths ...string) { } func getReleaseVersion() (string, error) { + if ver := os.Getenv("VERSION"); ver != "" { + return strings.TrimSpace(ver), nil + } bs, err := os.ReadFile("RELEASE") if err != nil { return "", err @@ -966,7 +969,7 @@ func getGitVersion() (string, error) { } func getVersion() string { - // First try for a RELEASE file, + // First try for a RELEASE file or $VERSION env var, if ver, err := getReleaseVersion(); err == nil { return ver } From 9f95bf3573ee501c51dc45788d75b849edbe60cd Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 7 Jun 2025 13:35:04 +0200 Subject: [PATCH 61/62] docs: add release note mention of platforms no longer built --- relnotes/v2.0.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/relnotes/v2.0.md b/relnotes/v2.0.md index 3b2a736ec..4a0bff505 100644 --- a/relnotes/v2.0.md +++ b/relnotes/v2.0.md @@ -25,3 +25,14 @@ - Multiple connections are now used by default between v2 devices. The new default value is to use three connections: one for index metadata and two for data exchange. + +- The following platforms unfortunately no longer get prebuilt binaries for + download at syncthing.net and on GitHub, due to complexities related to + cross compilation with SQLite: + + - dragonfly/amd64 + - illumos/amd64 and solaris/amd64 + - linux/ppc64 + - netbsd/* + - openbsd/386 and openbsd/arm + - windows/arm From e6ed3acf5f0cf19ff342ab4b4ae3c8e76765dded Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 7 Jun 2025 14:18:12 +0200 Subject: [PATCH 62/62] build: add dependency for next-version script --- go.mod | 1 + go.sum | 2 ++ tools.go | 1 + 3 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index 5af31ba21..8b483301c 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/calmh/incontainer v1.0.0 github.com/calmh/xdr v1.2.0 github.com/ccding/go-stun v0.1.5 + github.com/coreos/go-semver v0.3.1 github.com/d4l3k/messagediff v1.2.1 github.com/getsentry/raven-go v0.2.0 github.com/go-ldap/ldap/v3 v3.4.11 diff --git a/go.sum b/go.sum index a10124f68..5f505877f 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= diff --git a/tools.go b/tools.go index 0ce34255f..111b11fa3 100644 --- a/tools.go +++ b/tools.go @@ -9,6 +9,7 @@ package tools import ( _ "github.com/calmh/xdr" + _ "github.com/coreos/go-semver/semver" _ "github.com/maxbrunsfeld/counterfeiter/v6" _ "golang.org/x/tools/cmd/goimports" )

    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�{(e5jHhcm@?e!XVQ6kg>Y^$=hm;bE#_vdXZpe2adkE-t>0el z6|Ed|A1yBClSOHA1+r$7+7mbgK0^Z{hc_N`@C=@cdDXqgRjOc$r=zLY^duiWO26^=?H zIwe1KebYYg5F{Oz5VEM%OF7f>gEDgQsVmeA3Fjv6>^ogz z?|Ko!jd6XU1D`*+b+gLm%d)Ah2Xjov-k0kLoPHkgQEvLOW%p(1EJ$yxBtSf-6FyYw zo$@;KS{PJ*%fa)eN8@Xf4Va%7waz#qcNae3O}_aP)_-%tdBdNzB)7G1EP3+90zT=O z8ql};I+3wDe<7#!xh!o9@hk-#$tc6FjbSEeh1F| zkooLHLDsIt^N;WuIwWJm_jjkEdqN#oW@5uQIPY{q;u44gO1^0sxH@y|A?dLHzO3yF zGIpMq;O@M%vGhHI4Dc0<6B#;?6lLDB!7CWd=H%`M>zz7hMWu68qq2=UXh+ZVO7$Mh z;VrG#t0KsDVXJ&K-{fkGkF1~X5^1!C)t1d)Z))MA`GN@1ZHrX01cJ558HGLQu1P|r z*_QjoK7FRKhM8s-x)LB5o7>rKScx#B^gVvF@_Zt;BJ)i%W6F^NGS*(~^gz|J3ylPi z(7W+Bt&V zIH({-g8H|!%I5+0bN#wwzq@HaGvwR|??JVCjUZ|YH5j#RXwv^1@n7Z-UY`Vf&+C&I zw?>Gv`hSlhrsSf@2Sv&Z?zJPPHCC%Q}zt;oa+q<$@f@dZ$ zTGDD2XJwb9J*DTUZNO9!sbD#T`NO_3DY_Yf=2Rt5Vr1LjsZF}CNAxltPLh+K*` z+qU@>QgXjmk`ZV8(Sth*^NIw^R7sl|a{7jY%iqwdWLoGdqT)5_v zq9c`~oB{D~H{sKsoxo>qFL-gGjQAP%evep${kButLJ(`T39=j;I;K)-Q5&`2T|soy z9*Ej^iRcqHkE8o7<6c>CxE1I%5hwrVmm!6aXUc_NiUindfkTXT#4pW)&7Z%yCOBO6 z+PC4Zw8K$5Md7(w`1qREM1H0Lktce?#=sVKl4!OsWPaCqyIHwg8lt}X&})@cNy>A8=n%8WY= z`)XJBtv7!@dNV_ZWus{L9dC@HrUZKqqQNbcHIcHgl_a@s;FYluRv#SbDOIgt9aQ_A zlqh<_?csZHIUZ8-A1*QzwRcntbvyymU;&(4zBa?#;Lzu)2o;+!wE3**-})e~IUKqw z1k#<Et-~FEHRh~;tx?1bw7(9-iL<{ZcT0uMMopcJSdR0R9@Je62!E3}9Gf(~oH8tZsI77Z z`zvKZLuHe5+SK&?An#&4bF7|@ep$iF2f z|8r#ev0BJp)EEw=+pbgxZQ-bibWeqeLBi=Eq^J7){Fk#Lwq!j z)LR6zy!6$cNy+0ETFm$GqFdXnUm<085eIlqN{v!KZ!^DUxay95K*t4sGBo%vg~)K z0dRU1=XE^_rghS+-c;{Lj!f$!^yYcm)x8}>KmQ7-<)P2 zCv+~IU+mbaoGa?0?@(yBUq>?F{lM?StF_Cbd-*+N@q41Abjdo*w9iDvq5M0YhSkK^ zhbsk@hg2=wHL&_7d-mF2RJDmQVtRhCf#dOcZ>28JjKA&m> zpUB?+FT%>ONMKxVYtX3{I&`A;o2F(UEhu*8_Lm#Y=L2yTUABkzvV<6)wV<7v|NYFs z`o#voSgqvM*4g7rVpz`MCa1g*3OKoNbTP?t6@Z6cOdnGdJH4-WpRIklNma*|KYJDb z0vSl&CzUa8)4msPw^y0^OuL2H8>XrH_!n{C-BoWkUp}q(N-8?34t8JefdsVlZFFwF zR%c<>Q6By#k}r7HvU2oNUm|_D+p%|-f>E&;+6OrG}TkwqvL8=_hK+2w4=PM zi%uuniR*ATG(3{C-(BC(FLInUe0MLI;FOG2?g?Ex$EbtNM z6-XZ58&YpTHdE|C`yQ$<#g7;8-Czc~TRm3gI^s!LvdfGDg2mAB+>_!4?)jiupi0O# zg8t#WCZ92XY^XA_8~^2kZUBfR_df_LnnFcH`O}0}L{m>*kmSr!-Vr{lo4(ubJm&D5 zvfkmb7YvPe$KEk)z#6-3kpGkKxjCV?_mTjRCKJVj!E7$*LcGQJ|32tlJ}EQpdItiz z9F*UlJ}XUK?&%bSSaepPDB3V>7WE_-xaZ#v)H`2XSU z&BLL7+dkkzQL?2h*%MMBWH)42NGjRaAu;xyVI+jCsVvz-MY0XXz7#UamZdSqzKz}3 z8S8sZ&+6(_AA7#9hj?&Yl<>P z;G#kVLVYJ|KmMW16O>i*ka~cY#H&i*89R`ZZrFONmnec!k9{CI8exC8=S6~q- z^tax&aXaX4dbAfE^p}I2Zg2Ty@pr#ul7)|{Z0yVxU-L~76`sqF9FJfWY;M=n@0v`& zpADQGNdZbdanIn=&!C9MuM_ihnEe`u=8FD|wQQWvo|QK&m9}G8hT%4)kvQ>*EhD;$ z(4f`&E+2{=8rH(*V{qJ;_ur1`SLH(Af(Jo>$bPA$^DWQR71B!`>#4vwe0i7981Xd( z>bb{t1$V|s>rKV9$1U%YQ_->=cg}@McMVP{cSLfzb#^xp zSy584#9!hD#`SiVx{kO&Jl=8?kG3;aejj-@%=f;vQ62Xt0oH6q?=arPX|a@2CUUhU zT$5&e3F>kW0|3rm#jN$%yZ%&4_>r-i zyy(?KS-V7=F3P%D_|JcY{62fV;J#cm zl=5>apXGM6IzQ-wE_^1XAFdyiWo#87Uf1BWsAb>s3c0=BQjNsDUaS7CaKd8=iN1SfwX$TcEhdFSNVMN37{!OR8i(%jT{_68aoiu?jy`9JbGY*RbFj#f4TEH)I&=p(S+=`MT=VZHXBNIfrHu{qf``m$2NQ?6HEplB@T6YU5&M zBuza>5@%o1Ws4wodGXK~i|ef|8P!DOw~Sr9K{(Ae-en6DoJkziFq{gfZ#KpAG_Tl6?8Rk%3PuJM!_2X9kDLqW>U zEafjHODmYua$O$GbkcP~>)gWqS!_m*qk&boZ}hI%7K zkbv@8i^BmC)z{Tdl%v^E=Xgl6=Y&pbc7mVK?M=u+UQP`O^(LJv_0)Rm9O;(1+agdy zqN5-~-o59C%=d8v+nUZocWB@dny><-`XVkhE-Wt)^zWFvbxWrl`GMByQ%g=O{l4eM z=q8EJ^78#ORAko71&C?=1NA5|ebr^IgK^5YRd>v5I9*EH_&LnF7=C1cOJbS*ZH#L| zdI!D07+ky`ug8;2m%yjfZ~jPNC0Bk%a0C!SW?JFVbk*+sq_ZAJsjG>V|M2YggJIC9 zU^|YonSJT0JNLDuLp^T{#ykJ^{FEWsd!7^6AO>jN*PbeeKmGBRD*f$z$?#9F0W*U$ zRrDGAr`DwijY;)Ow&YwmEC5M0?ZUMMV@_ExR|s%E_)9YKIq0^|J|6Nq*zD6KKn{AY zm!_;5zbBV!njK$5bB!(CbCm5NCSG*vQza8--BIkZX=B=2oK^+lEDB;nBcgwi@dAV zkFQN~CPrP)O>@6XIC+cHb^l;ivGKkE=Y=e4DdioP$RTR4pHKeU+<%*%g%|v+;}}YK zq;mBhO)x3-Hx!vxCQ-)0 zyMtu6Xm=Kr&z&tz_IApO=6n|D#99xS>@XFw3pra>1Ea&G1#2t}j*$c%$rnT<&uQ3( zxRh`MCMM%xMclYn^kx1+L3hBYP2ljrXmX1ZOW_C>d%GV~p$!NBq0vWe$*Vy}f%ml5 zy~L+ndH!H-uWrbWCpZm0_4v+I<|ef32+qxbTJVf@pT+Usz@?nbXT#^G<}g4NlcVd9 zxTKf5(zSPcn_boW);JhuJl8)m&P@I^Xx#g~QqoNO1s@;4(<>Ys0Dg8WlDMZi4mb=Y zk~LxfDLBMVHFx{WF8KlN$oxz>{ZDR3l@DRjG;Q=*>Jg@%vf=D>eqOhXdR3RhAxl=M zjk%{=VPM>1mHiNMv2?AK%|~AI{q4MpT7vJYV{I?+VCfcc%mN~gFtF-5glN+;x6Y?+ z;vE@wso%onPodB-*H4;S{h#`zI2tuxCx&)3Uk^A9=-Y&69Y;X3I4~b#bFQ*Fl*a;% zgBd$y70yuuE2{pqfHAU5-y3fsJ5A2i@_wpOOfP%eCcwFe=72Pw@u9BRz#P%r#nm8g zS`glc(8}h~NZD`jZSAF%lWTMT3Z~@5?jK2$j6y2>Ax%Or^Ewx|Ua&dm6-@>1e&BQH z^U4%HjJi%==68a6{+Rl7h;@08y|q=4C^X*1^Dow8B}+F3Dm}Q{s~Q(ApG!^ zbtsxW9m!fvKb10Oxa-EMTTk5d(Yc{oyRG(<{abElxHHk+>5*(jr!51kGlgPc{ zpHJseg)TJ~nRv4VD4dH&T_v9Liu_fQt?GB-7RID_K+nu%J0Ll_Xjr~Q@^K0k;J_6L z&`?5rao{p6=To@nQG|wk_S6K&^mA%q!vabrb(-Xue>ztDlhc0lEU8G7bp$?w^ynLO!0--8>WvaAhMc+>kkLRyRYfhYJW-h-%g&AI_8p_-OrYybTfeu zxSIF8riO1!*&#fg3Q7y$3lMT%*=}pI>Dw+bc%@nMl*gjEv)rFhsH3@6yD?w9NdC2g zHPLXd&M}TD-30AN5ffGsw!sTMqAqmbd3bo>yJCgealChDM;~E2+6Kv#0#xNNSl6s( zI*#h0Wfd$cuWO zU=yir%fb5K-`sKF18TY~TzPW{NQy#l^TbsZJF~VIjC`$NglPPd&LikiJ(IY|{fZ`m z#IlX>gtPY!y>rao&OF-8jqR#F8+uhXJ626$uP4qMP~47QMuthFhdX19a{EhV@GODX zc{;!B6!!-?T1J6Kq1L-P%<(MiKFOHU=?93vZ7Orm$Dh6sqs%w6S0DsG-QrJ2ZK}XT z=dH=fp^8}Sp7GD-cpoFqh(?}A{V`7f579%&Pm+kX0;tZ-`dGCA_>aoE#8++n4jVGJ zrRDttN>^26N!%swVx^*4p8Kp-@!;gVlxMT z-+?1Y;-ufA$rw(FlS_vdn4?zM#&*?56ec5hWo4iLTPbg(<=pj~*RnAFU5D}GQ0c%U z5*242ah)^v9<*h4O`TVDib^jU$R#FDYbLqN*7F{miUgC}xcST~Gj#W0dY_(k)9gEe z8*$nZw4o6}FoqOMM?KIE%?sT6ZQygTMP)K+WnN=B_a%@6*3gB5nr92bvHh|)BT`zUu1X-I~j07fcBl7%|v6%45`3~Tn zSEy*+rsTLWZ#w9m%O}Ed&m=`t!)X#gC{B)%T!tTULZWronAbvZJi$Eib=>d`0YzgX zaMp|lg4x7bf5e6nMF&`wSm%bb=>am#Ey5HJ1ZH7j!br~>Tsr*9{rq^6cl_=p3*bX> z(C9^&o}O{CV59%pxRFjcC-z152P_`@2L}ePbOCZ@3RJsAZo_rguL<$s^cOcC?XBF= z^?xArc89VU_1M>CeJ`9MPaBwOs$2r5*yK3-?{mxVy)!_QbI@~&G#;$y`-2QFnG(jz z?dbR`Jl~5@7-RCt*1~0*4(9MYodG{{1N#0~Bwb?!;JBZnx#4fl$*|5h3V6@`FGKNvK<@zqgqiCF#{?WnljYWU4Vcac#0Jm&FastCj%g$-sW+ z-iO!s7Ai)(O+HbB{e&5j5pu5GuVJ_n8Bpt$&fuIU=*>tUW2p*`Uq?fLePjhtns;EBbdN5Xt)u*d-lT?p zz(;utGsDuT^isQtg%!B`DIc*=kF^nbOs5?8UsU;p9LL)Um>Y z&%=)+&cwH0xhk!k@jNK%2P9orIkgxn9CwihCS7QXWoy*_5-Z_<3htiFr5GyVjkoLi z-as^hY9PYuAL}Yu&kbQh=#G{cldAm+xts}RGYN2$PZEl29XX1yjj6b}t(45Isxa=? ze51F{s!3CP|5EAeF2<}$M|Cse84BQbM&E7I*Ku0EuqqLDv>37{h{H}wQJf+6X`BcW z3H`uC5#&AxJzSa?+I!zjXQIB{5l0JO=fx#A#;DN-Fc> z6Yl+dDcJIq%=v8ZM$$2jttT;?B050=mYP3JoU3i^!adKC6t0y>-2m(E+#u3u3`m#Z z#YcB3Eou-u)YIZ^(@Q!R!TZ`ZpPM;aNODV~wt%;^uJMhaMcmuG!#~<~vLA<%aIV~v zI-Ap@MG~y218;h03b^~EZ0%?3GX5H5Il$CkwU)I#s2fCeS%=7!mc)A;w=n8@!pAiaJxA~xOr`TAKh4TFI>hR2lQ*x|0{gk@ z>VsC+R;ej`HRlgW;2mkLWCjSx!MN@OU=)*4%k>G{VIi1=uFTc78Yf8Zkv0QrrxUc2 zw?n(f%)AFbHgM_zru@rPJsRc_e?aUGDxzaq_17a+rB>1o%E|}<;X{KaK-0pI7f1UJ z6k2vpi_Lk|pt|L${-U0&O#zyS!1K{nq9g~Z_}cF=BVp-}3Gov@F0VhF_;`8s{zRQS z(ynh>tWfsg7gR(lQ3l~|xF+hH^4P@PgSGp>)g)5o+;*qSjiu>^?B-Z3=0}Ett?|yL zQk*3WvC}cI2Ms9+_r2#>5Cg#!()e}EWXu{^XtxS8-SX}Z@}pwnRaR;}#nRHOS7#~e z?_r#ZVctIqxin_DM19GP-rDsQIozq!4pMN+^_L8`GDB?njI8w}>Ei%3v)0L`F&`hx2`D?vy(0Pql8EV@>R5Uw?vWqh zHM~gX8s<5LGfGrs@nWnkRf)c;rkc9CfeR07N&7me<{Pq_D9>N5Ou>KDPE55T|J{Cm z+7Bb(s20C4eUf6kV8nCN1rUoPz=4Hc0d9~$4ax_y6TDvkWkf0r%#}GPodJf?1I?mY>pem@_Ug?2~yzo1Jqv&42LIm=J{(#do~|%gZfg@WPR*6bVB%ZYdc99JoH44TIFlES zow7R;EA*b*w7S*EZnFHtMc!*y7Ts;wqQt%rwTdbkYrSoE4nK9CJht7hC6a|{k_UTU zllED|G550@^v^zWAk*Nyrm5i))}~v}TQtKRJS?MPyrc0~m^^~*fc|zFPKAir@A_Tl zJ(^IuzueLG)ZF6M!YU8wvE7`o){E^Z#C%?0!j+uT*mHs;igiAQ&2A7>;t>Ojz^%Kx z!LI#hRRto33bjv*(v}L^T#nRm3c!0B9f3*kaa(4w@AvBqk|QQ@ibuPrfF(oJ=Hrp@ zYigFb(q~k}p9T%3B%`Z3ayQAJUXM+g?@B?1$yTF$jdPSxMV^-m)kfWGo&JV}{4X^e zKEueQMA{kpZQ}_AR%Z<04oz>4zq4fJoR8{;X?h*V65a@ zRVUeSE|B$3{pJKXXUm>1RhN}y{4^*2pq>hD=8a3Uhhmz(LA`~;W!e|@*vQ0e#w&(y z?3@#`*@vGn`!xS-Sl4r@INf<1+?8&0n`UrJ$i=UntftKr7(2qghy7}sJ$GeR=`w@& zFZ0IOaBJ=`+67_6!slE}QA19kb2*Y*rBc=DCS@@Rsuvo$bg|A&cc(1Zjp&l#j}Rhg ze2Y@BJdk7aENjYrx=cvUA?i6Mqw6?fxFie2af?Y*|>2EG`83Kq`{Od=w!1$ zJCP$a{MxSb_I~_+1MsiIdfDdLGM^rRp8WXq6gT^$$M1N0CpO->kBC0tQ)Q~oUv~t%1+KbA;=&SQ1{R5L z;q4h@vyX!|T_5w@pK9i8noEOBUfxX!k?`1n5^x96RFYjW%bJL(cZ^)KD=5(z4TYsV z2DccYJxPK6g_xKk#h_aBQF2Qt)O|s`LbIPUB0HIvJwwL(S@T>*!@>x(Q_19hM94j^ z0kz-cV^zCWQS{X3Z#A*AE%GpBXH=h94CzW+n$QYZhe6ZvH%#i~@dBqk9IFiBpGER( z23j#M*r%J5K%nM3vj~0dtG!%RBqe$Z7LNvUrSQ`QrmfZf)GR z%1lpUFkQfVk~XDp76*{9Gmv60v3%mIQSyb|agn~=8uH=v=Q|UEH#EO!u)C=)Yu*{q ziqQB0{EP#KU{AHbJQhHGU7sW3>;N%aR#jviTbV57V$LVknJ!le9125w-nBSCe=w?l zF>~Ru*lrg=tVOK=bA=OSidOJBn(DV6eV_M%!QP+GWR3bt!{c~PQ+`Bw=ONX*kZBSr zp|LZe)D-wU`YzfajFNTkT^F3Sfb(cI+#uWWc#xk_ca&NF*Dr6m3eM3=&zB#&dR9Uz zqDgxXi{K{If}Wlj)`Xo$BpIQOGjpeIR=;;S1jsCU?c*CS2KcVn%P!NNuQA#<%372d zanWF`(b@3(IjhkK4EP@eAfG3j>~5sbnPf3a?;HctYR>og`hgc2r?xM)nG>^i`xIRr zH+>~=M>2OVNE>CESguz9>}G!Du{pE+``bQ&HOV5ekbinBwsbx_(Z*yPvFWFH|uq#mSn-=?rnLk z$wOzv3LD+CRfHbK7*=@KAHUCvLDCjC)tW#a zbeOC{i$+5hzkd47%El#3kxuNqa{~;QkhzHPx$rKJYP)-m7p&)?rrXJ{xwtmUo;b%~ zz+zETgO~nyUxeYRZ@?DM3paV~W2M1LUp!ezY;%o(W~)_bT6Dc%iG;;57B_{y#BeoO zl5@ZPO}lJFc$f5o5o3-bmn;Q@c^l7{^gJC|@ZGdMc4;EFOF9-`d3wq6*oh_ETWLap zn?CFUa3&I??iHDyKPvB(9;Ui2mfg4ZG0EKc%zMv{K%i1%3jy4JOHcFJ^FA~7v$O<- zH6l`ld&>!Ikvx3$1Zk}tTC+PDRl8VJTa>`X6CQGAAp3`Id;-r0H_jg^^cCDe>OWns z0ayA6kkc?`Zr?Ayy{|p%AsZu7d&5HIp~!%#hmOzQeC|Nk2>nZw^CSIsnZBnLlIEZ2 z80c%h@Wd?(OzO(FPpXYuQT)KsXMVN<4)CI_+niictM7mGXmE`Ll1#hK@q9d&?!5*5 z1o#3`q_t!@g&p6qqOva8kxQ@w(NF!uufcjqjY67RGmTU~;m*xx)Ae9MDQ3RDd;j^p z>XwPJknXRfQO*Rc6)+ny(p`2{+>IUe?9-`LqD#1OA z?>Kmod88>m>7u@e`a?kaI7l8NMcd?J$S6v&*?;aI#{saBfF%!Z2LSx3%ImtuO98vnoB8?`0LJn^|9LdqH@RBs;g34!ie>!bUd z^}LTi{d*O~7Dogn&gQjF4QVwkD&W4I|24Li8+}9sm-B}QfqzvgO&%l>GG&Br8v_mU z&qifWdi3_`X7)6yx!Ic3oHX#~6DG37&@bA(QXXGM9T;wxKpVFPiu2${9XwWxQIO|_ zpd&*O+taOy_Aw_FnMC7=G0i_Xhm8)~%Uf?L!aj_J8)C1xRdir?b|*MB znfK4>dU5V#&F~7KEKu-6z_bk>&S@yVyPq_q#-XUXtj6;ukiKgD=k;2WV+-|Ab`csY z^9hbA%lLjv>?Fmf{X>P|2UC~+Y9W38^)hMD*)~bQ{zn!7!-)}e#lEb=n1Z{!W?$h~ zKQ)$_f+jiJWgt)p=gU^*bxnL0^LaCNm(wd&4Rt~!Sz35sU zNyW)}lzI!_Nj1zEsA_>W1XN3&)2n^&$`wzKB9KdoYW!IoKF2@?tdF`50?Sb%4OJ!p zDc8z);5|)%iu)!5yr+U;K37AM8qf8mleQjJIM?G>0AeNx*UV)DyJ+kp0PKzZ8%quQ zDtT#gk!kmprhX1@3ZM%q(8x|(J{p&*!M zf>|{u2s7`m+K)irj9u#nw!`SXH})uW?s3zn7#G@2yxlD(zY-;u-{O~BG)%=S&G8Wu zH_@>Nb0v(_>tAb~h-llv_V7YC)*)F_XfcP!M{sk2Hm{PvwU+nvU$>sX8HaW`Z*x#y zyru3!`}GVb^!l_?iS6hf>WbaQBCu?At?l6HHG-_6c8*I0Bh^GB6R9g6Q#VTmCX9n7 zjmh_?2jX5ytVOp)%yqK|ZGwe+0x3G7nveLW9y}%RY;4y6|5^>WKfcy{1K+tQITo9N1j7{rq?wbY)splE#~;MV2zaiZfjS z94MQM4S**0X&31(i+mNyU2cc7Ec=Z}dCPdeY7B5gB_ z7ztP9(kE1pzXoU8MrfdXo+?(o3S_=veM*7qWlz3X0cNQ9iB?RXp=|3M{97#n^GbVK=6mRNc$kaKGQysws< zdmmCmTDI+baKx8n+RGcNI4jdV9}7J9_HC(G#F>MYRF^M4Hq06dIzFs5s9(_MvDfV_ zG&xV$xpNI-^-o~XKbyV4Ut@U&#xL^Q)pTFfBXIDyyyhmtvcPi{rrkkR{D%6dz+=GO z!KkvuN%~6>@Bvy!&1pG$ns&8&2BQM7k|G|e7DQA+o{8A4qt@0830_fLnSCF~ASfOm zIJ-aBG1jVK$Fci#C?hxBgt}ttJel^RXeA`oseDFL4Ev+N6pYN`_6zb8-lp-dV%mOi z_O>dCU>A_Q!bwD=oB1dGf`^dpxrjRjiQHz1)btfd{SFtQX~F=KpFamBW6=FBO4<}J z(sym#%KSgbfCpOZg^5H_Ppa~rlI2gG@*Jq}74J{u|0xn!iqcn+_+wJ(AmtgD-We>j z>t2xr`=-udFUjm2A^9&)1>he&f&hoa{urU57}jEnQz?2nn3UtArQIo4^F~ z9g5QrpdR$$)D1$s{=y4NzXazlfe3+X&sOj~X> zax||=D8dV3>~3sDe|ZB-2b}%PhoB?I${#m*L3sm?<^I1s#{c7qOXdU~iww>*ySs_| zfO{rodmLpy=-!&i&XKIsm(E)^;E8u{OA_k4Rv4pd*Y?TvK9A9LZ0YO|1qCXPZT7a1 za#;^l-GF2wY9fRt4VyR}BVQmmz!LCZDL2{}g*DHCcN8j^3PND_d8X};& zhyG(VPeNXM`AU`wHkZjIuR5Zc-Re;{PBwDB(|L-q?;wqi&2yKhMNDxYZx<)KYL;cn z#+1E1Opl^IuObY1CnAkD@vj%Ne>hYmr%eXN4_iVy0-nX3<}qnJ+>pl-$P?Fb~|F)sE!kN=L*|=@!?YKX@}K18;u)xSV}b5GIKPU=1Xf(yMtv)>Z+yzLG08;Hk|nRq z*Nco_nlm}jt=V@zDq;oXooEUZsRP8;B`!~H8EAph-I_L029 zeIXm#ZDJ&|#)EFu%-+7H*}CyWlKY*@Z!H!SY@W#EM<-OYmutKP7u;{7zH@Q(62MOO zZZY0Yb=BUlGNF7>Jl3ycF(Px)uiRrq89Z3;)z8qsRbTYK*xMueAyXGF0S z43)jHg^2J@Hi-7T3V#*yFSMqWyWK=8&goLDpy?w|Di6;Bq+RTL4VK?!cG^49A)|hBmr@Vhtz(?d z$sl)A#?r9)KHVs{0xd{RHeE$W+YGCrmgN8?M-mZ|1gec18`E;Y(TX(UyiNAx#EnE1 zx6e`v`*VB>^1eR2V~G0l5m*#4EB4#K zd!@QG5LY>FUZf2E&$jlz^!9&xa=xl4L1&Q*@Upb5APbNHf~BMXBE&t;rr(BAQF=nFt@1 zPN;^6NWO%(U;M5DdQ~FhF;RDzx0$ps(HyYz0RC6U_kVmami{E<}I#vj_%ALZlP(F;)IytI0kZiXjw50eAg9|=(?f+Z}~N5{L&#{ zG7CKm8Pkfts-AV9u8>x4Gr9|MHC*go%Hy6^2vpX3I);JUvyjW5f}`o346}#Dqv`kN zi`Vk>g~UFDUrKlbxwh-0n2 zxUR-BPhFOv%-^m#r${yD)cdaScwP>a#iP4*`KI(J2C7z9Egpkr3AORQCh-~N@tn)6 zdVE))A(kPrkfn&MdJ0=itMwy`PhPagzYkhO75Ax*=0VK);v%-hef}YuX=@nVe6YpJ#=!x6 z)vC*k|8JC_oQR^7`+(Z{#K5Sy zUC<5jTw=(SsfdnH*kcW^84UTMgRr(z(O9M-y=c|P2Lcn9{|{d@L8Ead1z?Na$DTvy$6$>SY75(j9!lmD|s#r6abi7Kc45d5@N+2A*3a){9%l zaY%M`5@aH*gz@-W4K<~|YqL5SwhK~y_rxwo&moOIoBqSgW;1FIazu1}z>38HY_!gA zgoLg`1+?4#+hFrA{oC0?dIsH}wB>eJXZ+3iE4e4=}FAGpL#; zT+Mlaehn+oG0fAzg$@@}jj5kV zz6@)f=yA&eBPF&8GK{7ih>wpi8a zX_I>E8DnnMr2xm33K%e@Z{uPsBo&VeGl?_1rMWl5*|ZQ8CKUA^8~Gl~*w(&Ej+q&E zerRd;@In8Js$$M`KMsf@9LmHPJ&1H0zkLDvitJ<$HbhhV37uTc{dVC`r$J882s$IR zM7w&`e#eY?i)4TyJpuyE^g&Dl1&XzoQa0g>+T%EhBS3jOKsxR#O1&d59UZgU(Sg+u2FoP-$ z7lEydPA(cdyXtLK6)(Ac^N|2a&4PWGa%R3IfMJG*a)0;7u|(sV5uDa3Mu~m>VXb$I zL$@7b$yjmGCKJ;AV>rN8)?0Lf6lrL6E;!ru!|yUY{X&U7U0gAo^X^yUA&mO?OW4Jw zRkdOfwvM%_%L2W)Y;>!CoeWk+K-ijnqvUZ!FoaiG9{Mg7MU2jhfmz?@njAi8wW(LG zmaGE7tN4SB%y(1gS=C8UqqR2i-QR~AQ6#7lwUf|Q(f>fp3nZnL=4_8?K@wi@#5Nhc zz5SC4ec`#NhwmpLJ(HDd&_&p}Es&g1XL04>)Br&XvE4a#9u*^K_c#c8U!+1|W*JoWOb<;44+-TL92!SHKwOk9qvJ(U}!rk0%w zq6}4Ex+3|;ezV}i`(7F{dN{HCer=)PC?`P6Y<=$_0tOyacV4|)M|ehcFR;zSI|gAp zDXAfTsf{<#0u=A^?nhAaC4Q$?K5md?Kb>`2cxml>hRptR<$#F(6I}}1c$>lo?Jq8H z;qAgZK&lgn^WTUclLZp$WeLA4p3tZm;%}4M9Ac-6{8W-XL4K~Yc$5WiXQ8~~ZrjiU z>_E4er+&DS*bUdQ#z%i2X3&z#G3^_>d$A;Y3{q_bq5BXn3!^W-=9*B#U! zmlO--NL6ZoIdS>6s4k>&tkEu!6$XpviAqT~llb7GM z*DU{)vNefpGq)qjAKhtN=WPosHy6*l!^=%Y1W`sMl_NBSSHZlbL zQ%x8u>oS$N><~wDVB*wys*)ML*}@x#>_KAGxwaWLas;H3B-i6&!px7{QXX4uRM=hS zdJbt=VHlF&os6dZLf*ur^l3X=2?WjMm zT#}ki6$x=x5iIim{<)$goh!?Xt0A3KKUq?ypU&T(69q|RqTmUy)8XtusW!V?R{^$9wR%NE zZDjJ@Z6}g1FaO~5-sR~S92oPR8!x)+`J}K&`%6ipPy17ZWLtB?+V3oZBH~O>!km8kvyzUBdwb?650Oy(bwbCVPPes?D+9epR(&)w}A(Ay{Qer$6JOFC9qKbGH5%peYg%oSGiN<-c~Qv_b$^H-;;?VuA$L;9o_!J%JFUIT`q-PW`92U z+eBU_h57>}1&iueoXj@%j6A%k4bAs-iKj)J5_VF&@dzKn;&j8Y9G!&4iawxzt|+sK zL~O4*1^$`{7vhD3_YOj2Z~~gjWy*#h?lbQK+l4ASh_i|Uo0sywtwY-`gQk}7U2*Vl zKcHsP18R0n;xBIfl(VhRw*VNBhu6-q#@@KBc+vbC8Qn!|L1wt5j&NW8>)q&jK!fhK zS$#)W*&53fPvgZhh!Yj(!&#P6#|(v%m^r*#D%5?EK=mByqXcR_aYWo=F^;B*Q<)Qw zHB4Q^ypYE?B|^H38}j}?uz17Xidhf<+4>IlEV`LA)iuKj5B&c2^jt%lo@?SWSpD9` z;s%mlI&*!_=eyRdrgnv+Eb(Fcu!N+$!^B4imm51JeU#}H(FF_v6U}1Q)WyGjZH;GL ztr@nrJZ$LT2OSole$%EaMB|g7UpR;{xwICuyR1>t5zwF;jttvFEOXJ{0!*#Hy2->z zGc+s9Q<#voo&2@KMG=3BtfZVb(ZeMawo#%U{lz?PD!5DtNdu(MwoL_VzIc+am=sb!t`zoF)+%A@5{ANvVvHy;t$1kP}lkfY`UV2+! zrGydK%Pr3-Vr~K?5fPSWC;WBqy|@5;_k897&M;S|{jo?KG4dNbn=rrVT>R(4qC^Jw(;f4X;GXHI({{fR0k)W)wMAbBi7W((7R%Mi?L!iZ zFg)fdq#%WN$;3cEdN%x29NGXx^P`(LxgOoVnq2+Wl{*r>D)1|xhmsM>4ablFCQj$#1dbgG`c?ijshG)t;8i8z=OC=zS;{}fc z^Mw`i^T72m&X1@1o5LYRu%J4rL!I`05ynOGQvC#8Dv+z+B$oETwKdn=WbQP<78LapG74vTm39ups3@!SvG(ZLwBVLP_?2te{mb|Vm+(~ zbtn|m_J3NbzN1?0RP)*n3ILtUqnJI)0BJa1+DVvGs195kZ%3Gj%dV^RoBxN}b^HAP zsoGWg?+`RQXPfKiR-F4cp0FrfYzN)vfSS+^C+c`*4X~HnW zDNJVZ=+=X!wiuzlOXP?f)|?w`;jbW7WqV2XG4=?IW+NT4x7^Rk7 ziaucZ0NY`f5dd84<>;ArRqVG{&8Xk=ZtwY8ep?+c7=(q@Gu_-^3qcWo`;m9##x%rE zZ+ySqC7C2SS8jEWcIvJD#cdfyI293P+rr6_19H*WpgHKra?ry2o@IUi)5G(RYSw=x zbpAI{LtB(I|A~GTzY$=eu{@;lGvwCD*))Sty{>C@4>2om?Ps#{L!uVgGp>1P5DTaE zW(^-vuRb?|#<9d<=L$|*sB+{;;tNm5(Cn|rA(7@wn>@S}ttYN+zu-fvG9uMZ z!Oz^Z{DqC7iOK81X1Ag*35q|vbVw(Dmz8CLNbuT8vv^xx>a6eW-4E?=-`nB)t>ZneDt(@ zl>O_OxQdJG+}K{1L)_&*HmBC@|6+48tqt4|_a3>B!WNz=LtV5bPz1BgIN0kEanipyX`?bLvPuP*1PfGAkO}r@2z_f?w^0%~r2nCv4|-lEi@6O~c1Rf$NF+1%|hC=sY2n*FCu z>DRy6lmdTU?f^C=Bi!wr(^kPZnmT)D-NiRMRfd2~$w)h&*|Y`L9d_^e_U6UEIr78p92T2!Pk&~Z?)ckfluko$6_`^-;UZFTn|zY8!uua_Bi_#WDid0Q=^Bh3Bi|ca|GWR>2FQ?a49uA|p7v;ZfHUmS{E6Qc?R96`b^3SRtp} zq@~a($z&wT&tC{DE6xyQcB|^JsL=-|BOJ$1 zl#h(wenG{LN@IL?3&T@_75ts#VOk!$Vd!|y{sH=kDI`2rFvc28cC?nqJDo%RZx>zMLib?fYy zkK+Va3Gx1t({0?!ic5}4#_zPa27vYmonqC3EZ>{KL4W7#+nM=rIKR5X4?fzR2DlOY zU#>f#RCUmiN)O#^Sc2`JYn8wrW=^}mupsH^RNke@-}-VP-rY#Vq(bDo~uoXYpOj;!3wYWAWo9q1dFqq!iCXB>Q zelU}=rj8wc7J2BE@i_BHVJa((Vv1f$Sp22y5+qwZz(;{qUvUB^nRBX3q{%+* zc(+7yNZ@{r)YaWC>z_^+NnSy1E~dm974Vd`J$G525Vzgy~bkTmrsSq1ULn?hmH24<`(WygG zoD)mh!CZ@Zv`n2Jccxt69k*MQXmWY*fY8Rnj`OYU* zz8;ZrLKf_&5Vj^R(fldTVuQ@ha$5YGCWiG>ln%OTyFBApqVn*HcrLdoMz2`CT#2X` zlw)Jo9j=o6mw4zUZc)nQTD}LS$ri0f4m;vjcUV)>US9MTS{iZF$uxJz^;}Dz^LNB~ z4Q{;Hi;t1D>Bmnc-es5v{L=A?{RAb57_0eVF7CzW$u5u$`?Iy24Z5t2W%lB;6-QH( zUX@)9jm<{cg(n@l`Z)02cT%5?OZ#*>0Z_ zf^jy>FDlyp+x4knYr%BH&!9dzeyl=zxwXw*D#cekff%JRzfSzOpgv1kDi zyU8*NR56Tt&A@gOS&PCfM1}YK@K+Kj@1asVNiA2moOnx%C7&=s^ltsSwvPuPjuV*UNiNkdfB*Ai;1$=%stO2XAXEubuF z0?Hz?t*}!>`u^dKMJ4+jg~(kb!YNT-7S>QGSXtKJ9IdJ_%c)a62Sl!(w-F;@+P4-U zku1C;qq+?g_b8X^zXW>X*&cv}_xVPQn@yGKs>)inw33-LBS(joU11e=rCm{MEUEmO zzn6}MV;+;V?AN95DkOQ_7J3n_Pfayx8kl{gHezBKzcP=rj^T$A z>Il@661$NWb@&^)1Gp_Q(M5JOPS^RF?c2o8(E4^_h0Yib;JEMbFr1x+g&w7jlAieU zJIHS()LCtbiBy4@$g>hejjIMnm4-aZ>Wqw5V%XYy@)BLaIc9Qzf(-Tb>^MTDk5GSwWIH-W(<3ntw!w&fr-sq#ULWq61jV zr|g|*O?jH0?7mK=v7M2EP`s*hY;AC5(XO!;d5{}tb=f#V!o~N`IS*^Cn{sRv2ip4GH zSm$#z{Jy{Wh4$f{TL0;v8=UN53v#}d`Th(%YVjxrdUcbm%2Z2J1NiGV7LQd-CJ|V9 z{BH6a1c8yQuEQEzT_>Ak!|uQWhl;ffP&U}sbi52{ZMkrxWvVt-V}7ETUN_b;pw4%# zPZ2rYvty%>-4|K3uF}(lkk8S?ar?KmQ)T8Vbz>flK_39A3T7il7?Cgz@6D z#nTImC+Bvou*7K7Zci&e6LNp#cAj3#->GKUB}bxMHmNIBuGVM zG2E%^0Qa}6Y$~3TsqLqL^hy-j&cy=RxZ7_@ug|$$(`kPG#Zg~bawm9fP#eY})K51Q zm42pFV|V^OBThHLi}xY!08kNSKq?{z+|p&{0f%tiqAh9wQw-l|8Yb7s*7l3$6FG%e zXK>bzcZ^+^NI|35FA-?4!Ki8Zg?*+=U8i#Ff6$R)uXg7=T94zYNX-#^_yc78j z`G7WWapl3LxJKo*@hQUq)<_zS z{AbYv6_difMe_`2#MHf>mvV8L=m63aB}g#j+^6ZL9k%C*r;{*+JIwX@!?%DRGK*~z z_Ds-n_K-uJJU>D5nJ(Sa`-&skv)|P?&x=*KatBI9k(c0v3v3YJeTRDEYt)9QXfsw$ z=c0UY1ast!h31%*JaXX(qco&Zh5IJ5@lBkzWw|G9DZsW`yOb#lwoW@gkPOACZ3k*k z!_JIkUcBU7!0q~9I$;9P2@|*~;MsKaaVaKf|Hhr z!VGQRqeXuYlwq;c)bhiPU|C;G+}kLafbw$^zq&#$G5%2^W=M=6$At_Mq;!ha$!C-v zjBgdBDoN~~(gs7R*zjUmvc`w&SQUEN)Stwor=CK;@u?Ra^-GQW%2CkWu8d+Z_K3g8 z9z*bUJ%^ZTyH_{c6o3C!^dXbI;S(nIRDMT9`K4agA#HUxYR8;FSoZwX(os!14b6d% zxYL}oLAX)5e2T!Il9yFW5Nx`m-AlL&r%uiZ+AM+JNTePT>U~~+<1eD{tRC5`Co~nOYbjY20{4|+KBkblM2h;I zELI6Oa$s=Z1^P_ThEh{XXR-SS?ni(XJ03)}&ouHAd3A9k{=d?)r$bOM=MLB*iaJ@3 z(sy^WEIr0~91XE)^=t?WhI92kvXH!?-ec;u)w+-UKq{1(allodfls++7%*kqtKnM0 z?KY%qNtZ_lSje`hqHlOGbmCT+vFmd~g&KKz%@f`2j#E^M`~bSeQY@la0T$Fh0=2_}s6! zf5i#dTS7UYmr|JZqh%GAU9nK`cA zm3pM{Y?KZ1So+D--8d_gAyRH+q4=qysIdQA2X_0pGwo#>)6{sxlhdN`hw%Bz1`w&B zP@HSwOCWP zs0~m^6>G)=L>YLZ$hlbZo71SW^vf{@B-%&nub^l42RMk+Fgt0W_bi(iA0@?6m2YXP z4^ObTHfbUiQp9{^ah1^(r()Px;Tet-8gbJHSwCu?UATMC8)KQhC3a<0r1m^yGLZy#)}m>*9wv5Zl+ zjG=4e|A+(Q*kZ++O+6sDZr(-U7kd`hKNG7k3~x`wjLmeZj|D+5c|ae2rR z*0|g;MIPyqjtIhe6c^I$U#K++*@*ZKy%|ukSjfuMp?Z{MdbX@NtILAHdtq*Nx3Sxl zZ}XfPY|Sc4t__bNXmf14eL^~1X(qMZ=aJ<*$d$fajAzpheyBi!CY})S|L|;W(x<@RSPLu4d}>$t&5{@ViOTp!-7epbT*) z;|OACq}=H(qfqw`00v>l^Cg2<=CuWp_5m!*rubwz&k@sD)Z&Qvld2itAD@$bhB_4U zrZS6`-rO_yLzMlr3I)(IDr!nzbNG4C;b9;Z;?SY&3^Sb@YPe|g%!T`8$S0}>kP0O! z6Gw)BKjg+f-}Kq+28}%6&v#ykLuh9HsDfu<&aThN!mJWN+5VIY!QK9+RH$f_8VHf{ z)?R%%8~eL%thlZJ4i4I4qh`-9wWq&B?dk8c9m^z2LKepMsqeMYEGayD+#&Ni*44=? zJd;LJ(xW$9v|nB>o|N_{^i$1P%jUJAQD9(6s(2CK=uOKkdaVSe6>9`Xlux^@t`qr! zRgOkFV(h0C3c@*$g)sh`!=^KTx2XeL9H22BJt zb|?--CKI&g@Fo*;cxcMC`+{mIRO(BYWKR52MOzVrO1)U+$%Q{99c`{$cVFpxPt~$> zH0BA5Jcuy4JRqk)>bes1z7m&EYXEbncAPr@BV9C_A2DeWVOoYa)R(TQ2qr3?{9SD2}=T!`ZcDjR1le=gnRi2jcOH!fq908|>mfr5E&9ymFQy@i#6#bNUUl zdH%jM`;9xg(yRUKM4D~5plRhZ2jvv1Fy&^N2v+&2j?Is7NF>AwL_+;lz654(>U4mr zTgMx6;NPBl!pMInT!2KqpjQIEa}D}NH0+m{urm-7_M6YPbhDrMcnJCRMRd$mXm+}5 zDjxry)YBn0o~>@DceyfKfmC+|iK=!-!ND{p+dTdL8#8c=Fyq8!9wsXbc1rH{ofavV zAxA`Dki9PkN@&vWtpmoWu>^6LMh=f_IAp#?pX^mzU+Go$fATvo=V+Jxw3u=E5q=$* zEuDee!HIQaz4wZg6LL=BW=qtF413S^(|avU%;7QEWWi=3zO3yewnSynz$}lD2_A0> z^cELllfgwicyl_vDr?eG2qARu8J7(-Sl%5&cf zjz)2a)^7=fni=S;hoxhyZcv^D5u04)?1e2tU$)1w!RU*{;2mc;4={4}ZgN{FnmR11 zGMr^UP2G|kbmTTyj9kf=k4DLn_h-_6m;}R-2zvSMX+ZD%*YyUjcIE;x(D*!GPhZL- zpI^wtB=lr(71qQ~5AFy`x!@7b8j3gnx#9rE{Mb!VKmTS!jXHc>GQUmT+SK`}jWW*j zq=uV$n!amYaWHizsV9}(;xSmua!5qeQ);96Czg=m4#h5i*4Xfde=keoFrxe>AlKXc z%C>t?1^mYCTzq;|qM{)r4|3q!k_Vxy@gDFd4&b!phaT(o2J)ax9kZ7!2ji19Ag3j` zuRr{YLN%5?!h~^EH5H@I>jvz_FIfZP;v*_=*Tw%5x14gz{I8DFpsVVwrD;sqp(~EY zz0p;>8oE+DfMb&D1WC(r3K>@$xk9q3PMuxp%w$K{O!GL+fJTBYZ}03hK@#jDiqYh% zT^-`mJg);tBG=q1II(nuUtzgmcil3hT|O$7=tH38>^2G9JwaX3XYM(Pa0Ql0dX@@@ z{fXNP>#bpm()$!!VoR^bq8`Kz2!=Q3wFuqNaAx3-CT|LU>7jM(PE=qt*j994Yt&Zl zIgeBq6;jgD7LWHiBApl%NBf+aFqchm&m42w{X%P|`Mbnzj$fiGh|sM~yf0w3T*Zr* zO|}r#>BJh*L)$D0F7-(v$1W9_-W@$d;Z`KKt5OhZdK?J~h!NYf4( zhOCY(dm*c%Q$^_K3b=21u1|FX%kRyA&Rfe?{u!Smdbgq+;(wvF|9AhW3{BjXcUOF> zF+AW1vx>%7Sn@fVXkKbQp`|W$x;Q-B&k7+!%g*$OB(bhfIW2nWeK4H3?&r^vb)qiQ z&VXhc`J7GY@pW~$mVccbhI|?3x}Sj)*HoFlbyjrJrI$5)YI?MmR8lyjA`D!KFpw)z zR`gqlH|GBCTTuDb*k66!b9VRPSfEL ziKt?903kPC%x$>sl>KM?MxDq0rk6IF7KA!^33#d|X06>Vm1zxTRXsh<&q3uZ@T;eM$Zk3 zRthP-)81LML7F=iEUL-c%dAtVo|4QfBFH}}a^AGGt?#{p4(_Hfw61IR0%oM3)yl~N-LQLbU{spYwza8`T_333%e`ooJ{h6`7E z)ffkmQ^BGl$QePmBeZmU6ns_JLURF)$Xy2m;?nSj|6U(;Eo;5xW9os?A&9MzlE_HU zeHF$sUya0O+kNAAF)5fVasMD&6jD`po@iI?P8LFN_0rO##!(ooPM5RXmL-rJT#j0K zrxQHMg)D*STb4j$DnHtb`V4!$;FQQecE(}_AJvOV8ztU7SYQnJn$_@LA| zj57w{r@d-Tm&*YyDg$WIZvK_)jcajA|Hg~Xdfxv0OP7r#blFIn3AncbUeqiLV`rXq zIwL2}BtKhH2k0uK1w3-b$5l(jJuWlD8IhuIq(L#_?dA7DhL!BTH-WVFk2N(MpjxmjDP_9}b=@#^B)wb{%*k6iCp!o1 z)e4{85FVn19JRbzZsx9)YWVq7&H3(5v4_7&tP4eRsNPhT6eMr?{d`|vnI99=tor7# z7x?|~8^^dLY6qWewWvq|djG zVz^hePtLRP)$B_>d*6X6Qe^JoFBExEV=U`h6$e7 z@TbUt^&f*;hJI=HW&pgIv?E(L{x1Gf58S9P{g*y6e`#@GP}>b_;W*j|Cc{ziDRbl8 z6PYACs}qaqmi*5xY$ny}n@Cw57KK)2m8lME^GIvNTKkdpfbkzG8{KP#>>AE}SRHE9 zL82NBUvKEDNiA~Cm*av9FezfWVK`v*gIu#z44}4|=C#PVX>**teZuK$bB7q;?*%70 z!n)(4lra@je3IfWU-oxw*HyilTFVJz5l&U3PF3Rs#c7|C1nZX0p@8in6{YJ$_d8Xj zVfTJ-QErs-bi^3&`iT@A85?mTJerQMyBt3A)#KCG*C$$!Ip3;*1O@+vHY)ab7Y$gx zK>}P~6B!`Cy__BrS9YB+o_$s)#|?-CqkUMUo}tXh=veA0vTV}?G5~cu&Lrox4w-OE zG;l$jAXe;)X_Kncj@53}*+-bM{+d4N?IQ>Ixr#-{>GiQ9;kBoS6!rXY+j59^xu8hxw1VCHCe22}NW&&`=3B5(f zT&f(q-^=Hgb1r@P1EPli7^uuq|0QZT7@~%Q=~`cbDCY$+X3_F$?uV~BR?=YV`?h23 zlGAnSuhHdNP>H&zYko(LPd;RUVysNdFtdp?|94aotTm{>hAP z+u$Tpm5`kYL8|XKRmolt5nds;U5m_0(QP{dnuYcy*a2m;yr~m?{jB!#7)ayn|5PWe zP=V&`Ry6+wy>G2Ks?yaG5y%Pn&H_Nbc_Ky-?$7u9(biwf{L`5QMl;*d4!@>ecZ2vE zx8FRr1t+K&1F3%xFG@EqnNB>;ZG6Vbg&3KZ1!$i^F4S8E=}7OQFtTdj^(Pe^mMNe zVHxo!HcExyEJL7;opY{R?DVp>nv&gN!Tc&LP?XeSRpv1O?0u%>6;yy|=`Emy!BG4! zJ;hH-SfK5NCP)Hxw@>o9?e*nIush^dUoQ0R{$|Aazoa@h=tqB)53J|euXEab@LZ`JV5=11DA}h zdJKf)?75aZ#l|Cgm{Ygp(ak^YgLDDorYrEU>3YcL?R{BcX?6vLSNAftkG)?EEjZ{W z-{&o~OFY^1&eyi^Lm!fxN46{~2S+Lo zbrPfb+jTXE^Id4iMkF+G`Gel|xlI1TARIZe_NV~Hnet=yy7z5{G)7;U zlx?uM_ZEAC1Wgy<_cQ0>Ro?5tCVclyzj(69q&K8A{d5kR*`52AfrGyoGY5$YLP6BV z#`>pJzF^}&fgsQWxE?tpPWb5`q;FFZ2q>ifuI)E@4ciLuJ3bZKM(Qr@Ad-|A^K$1W zuAhx+NvL~=_GhufJ=v%1s;odsx8gvqEM!mJ`{^3*%&7 z6MD}$OW#?_;~hI3>3BU`2Ynk#0qONdM=0c|d%udubpJ{lELT5P4Ee3WfMBAp+#4kaBnIPq&?y1-8Vx~ z%D=%sEVk=x3Qi~`>SVBpq!z-e=}@?F8ThU)H439OnTcMOU6m#Uy=#o0a<52*Wuans zl+%zn<#6u$lk`>dNcwZKT^r!u>zV=ScZ=jQ(_BmQ!0ZWd68*MfVS)yJJW-sk zTCSJ%1R-JM1m`~hlz5`>0K)b+UIJdB^5VJp#Lq8|UXGNCFU#K|B^?0^&D{Gm`}An4 z=OUPh1_QR%*U|6}4~sHUE9jb}?C5=vH#pkDf=Jpea4l~+PFaSe8!&krCvP~BYpN8{ zf4WqZ<3pTV6ws0lZ?KTGkk2|mOqS>q`aYLSl-(1XJax;OK#hxW!xB_Xa&wPr_l zGYi@0!|%8doHeGyKMQN^BL!0wt2f5~Lp53Zo`ic}r4}E{i&ae9$xcP8Gbzs>rUTJc zk?XwhghZXxzp5G7x(eFhBwCv(dX#C+c)s@MSq>489G9u~?>ie0CD)e+E(ZrX=o6@h zKnvn3VFn$`eL4WZX%D{f9@Yut_Xrb^Wu61C$(|g$kum$K#(44?{BU|Y;nhk}p0ly3 zYfc{4*MH)J&DdgQHNS~`UPRT1eUe~TWl<27P=oc0P$t_*;^; zAhw+9m4n6M4w~;%xOac(!af=PI-P<29cbI)_=y2&qz6IHQ)ocwN|T>rL82n3Cyh-F z^Z2JCEgA2cND(U@$t5 zuBFOeJrDg0r^j&UjCVH${u*K;6drKMl)Me_Zpq6M@&5jxFH>VNdv>?t4>)m<^3JGy z>inp3ADv>G+^H{MU!q!iGKYwZaji2G{PSbzzgP{*$30EDDNKJ&FJUj+`TRVQ+jLn+ zi6Cm-CMaUxD!=cN&B}%zWX%E`>9T$xvu7l!#p|0|gZ#jzL`E-y0g;dSZ4fh%GLSN# zn+NIT&RLt}ZfH4&rCij5ACJ+hluAvC-mc7Ww!+eNtfsC#eQNZlJxqwe;&~M2Gw_`N z`lXa>1Eh{34Dvvy^_H`i6PRv`4##q-W750J*=XbP%N&0Ey6f+MyJP-IXZuN5YG#7Kk7%h6Ugp-!PB*-`Rorg&o+hif;iA&5yOW8tj=W5UZ(>6O2cm-pwNt7 zuK^6X_fJjrMS@}#ZqoZO1|%|5=Q~Y=csE2qzG>b>ZYk}J<0=#_`lfT?rpL32k)yKN znqH?JTvYn%1#G@mZm!SGfkKz((HJ9}2m8GsO0|DJ5<>nZ4c=f5VZOM@B3JjXgTKrM zto9C&7DexV+LlO82^OV7twEg2p7x*5io)YQS*kJT6GlKR?ioz@$k>6Ez>R>_8B^Hi ztY>k#hpvc6m#>ZGp)n*Zs&&nB_;4x62mYo$a)j&PpQo`hQJBuuhI4=S2%m(1|Gpj! zwf*!cUe$ze*hk7VAspvqa4eSC2qyXA-IMN06+r zVCWM623YmVRj?}USTkJe3AQs`TvH%qD#itX6>RH(CkT9WrJpc678i6V!d!++&0ND8{&W_ctl`l`*YxaH~4+LRHQ+~=@QDmU2@{02NHms5~ zfWB14>dy1DPjEf&H5ep7BJWPfld1(6$ZjY)Qb&_-PW5b#CAZ&p!ayK82XJo zi&_vpj=m}*YwDM(;E}a=*R3wx6~v`v>eWeoo+%e{bd^W;c>bVP6m0&-S>1h8mO%aL z9D30(R$dc>ym4RMC($%`zmJvahvLJWnsoW7lLW3KPfD9QbK{j6U87%9-2LPd6qBaE zO}$-P<~q8iO^d)?;FtJAU`oOv4mC3);1NXDGS&_IK0xi9Yem}@`mUMr7Ug1RaLkSATo}biwc*ti5Dzk{3A}jE(*L8bg!9X%B1Iv<*&%IF4q$}|lLRoX?=g-w| zmkCV|+@L2f^&h@_z~9IhM8LRlr=~c2yaC zM25P?PJQ&-v0Cc@$CaqS5xG0srK51PIR_c-8isIEGXHaPC3&-Fy?J0wpBTSW=;i9S zF^$FMn24TRGFt`rhW}2&3WfMSV5QN<(=d?LiF(lH{$WIaK(S(QKmI#gysHLtzFsQ# z?5XKiB^nORT|W*1+pRyeH=o8dmP%)rhokcDEdNN5eFn}Z>^m0ZYD?I-8JcG4AK9rx z6tHq{KW_qHk#*(vd;5H_VCQdEv3`uYhLxc2y^MDcT=EJuU0e@5woti3SL1k2z(R$ai5KmL0(utoM=##r={58p;>9ofSv=DFclQWsrR@dqt_|=d zI_Hn-Jr;A;;2rf*3_idAg?Nd+GnBj6p64H$0bBI2y;l2 zRkjLq#dSwg_#8WL2HzMHZAX@ylrp6xCldZ8O3YW+kJo^CkDQyC@9Qcui>R3b5zvo> zc8)zdU)9$<@m8(PCs1kI7nAV2LywOe+`qELB|uTv zwMo;}v_?LTSSv70+8W3$j5o zjmm>!wjLP+I}qqNPzdp(0aC#)1(ds^w0I3XM+6Rf3!iJ7kHyt;`d8oW%N7~-o>rwd z$&>xWlRH`d35PO_RHHD9MI(m)lr=Tp?{&AU?qfhN;;C5H9#Q7js$QHn?t`pxJ7#dk z#5?ci=K4}zYjH@e-!=_4p{rTy<6+$i*3GLt3D&yv3YjQz&%fPD!s5e8GM{9;jG^UNul2eU%g{hUw=RQvY*9-}T{8;5s?5dnxQnR$uKa zgTH_H8FsyW4_pNt>1Cf<&-t`83rP9s*SgsS$$nf2BDsDou_ias3I<)L?PIlO0MV4T z*jP3)N+0PoP<5_6D*c%Jbl6+}V}Hh}EkOu899+G{IYJsh@GNIX7Knz#Qref)0rkLl z4iOa^dFcAomCMEUy3TMW&tok6KaY0w>w0&V78I*XZ2>9?(CzhM)gBdaJkl44k3%3fvj(TEFg7ZR^|3 zb#3I&C(LE>8DWh{oP8fnwohe@g+sak(-9kk0Q2kAFOPZixnrFHLUuvlM&#Vi2wWou z=IkP^jX3$qF6W1Hr7mYE!i?tKYWxcX2j3mh@UBZ@(+-eXq9CJA--0J){OI9oU%q3i zvGd*A{ezF%Aak0PR)cXe zcqd_zLIgfzBa%yd6^>00o{Nm{o4#GyO^>Y~X5Y-yRq|O?ZuK#kY3)YB(8pHovf}t1 z$8dT5FV_B{s!BxJjk+$mo~ZHLW9akMM4@bN|BmnHkfTXU754Cl{UAd7n#VunX5=ad zzVQfGA2_K4k^Cu#LyA2j=VyH>j(BGt$ftjRmVQ}$O)u%^#Lr?ANg>bnHV3MZWnk0H)Fx5+O5qOyjtC>vTn({N=_EGRuZi4zSAna zKJyiC>hn7o`f`ZEyGLrTYq;wyU(DB4pENdbZi@t$ zR=}#zZvP8kcKDyjR|3I#??X*$S4#V@Q%x?$$XHouj!Z|jQ!{@`gHHxRo8GJ6XUxOU zx8vnxjns{;0B!5%MXHW7MSY&VEctQ1fWvO{6va!nmqb@95O@B;;YYm^MD<+1?hn@Z&v};4IYz6yWWCSMonC{wslbEbyg7&- zvlIoJ5@z8n@ObD-Mf*!-IcScfhi3q+6tOWgm8tkq@Mo>e5qNDs7!HP@AvI#U!at(rx>_h6I{! zeU@!A`t(gU;oHoE?fDsBQa8!XjDw>qCo}m!a`r!Z%r-Y=q|`6VXi38U=R@gRJs%Rr75)(e` z(M-^wJCl`7%1QPa>$ZFGD|SNG+G6W$58zN0@YO$M;)&a$$(tdXQ;}G%vYzD4mB;4@ zGm(aL*7uA<^z@Izb*xG6IHF=A>MR=9$&8T+Cy+;5rL&4qYOsw`9?g{iyIymAcVCb2}paVlh!^IO3I ze*FZ`_VJ3dG97X&Z9WB*zut57SF&*<>x2njkv?PjQ^~qSH6}ZA#7!@tPOXsdUVO55 zh+kXsX<${A>+ZJ_hsIdtO(8{X;UPtZBV#ZzjqqOKaV6u``;g4~Ujxpf3-Lfr``1=C zdL@v_E~&TTJi{#f!@zWi?+6Ee<+o6-RBm@ZIr_}(*&0I@;B^eKCVbiR1^B(Ns|)*G zX64c6f4uy-?i#T8!NwyYdJ(kCxFn+pO-&uu#C?dYX2Yj|ZSZ<8>Ca1NbQ()K||7>Z{IWTc61daE#|lYO2GKF!d55n-B;{nBvB)}MOMe?(*vi;5yyZx zFKp7zERTk!MXJ1Kjt$}RFuZ{#Q#W~hrau&m1^&}PfboIE7`nCV=uGGIMGED(D!bhYYj0py-<$Rno$7nn%3U$)k;}`+7C}(s)mh< zq~b@MtQQv|OMPE!kx~412Xg1f@&`b`mZ%%z9sEIg-;B$myE8%Fr}EuE{>-sF6k)OF zM?Pw%7?xqQ3dVo8iLwWeK!dU+P+f6Mfv;0_u9Xtm@n4f6nvJ3bkz!lP&b^jBtgB|Ss-J9^f3%uF`p6WVQfLTR*6(t5!ORiad4t zP09WC3i8rTW9bkMTV84F$}mYu%-OQDpAK(Ftx}MZ-ZbHO6Ne_qvlqE=69sgwy0(R2 z&Uvq;lDl*LB+(2;wz+yu@TGQTKw%xyxrv8OWQAu)&V?LSxss4}g?=A+AS5;fOFeIp zI5Yo2tGe<^7Cdgg&>43qsD93uT<-foxtj&zV08d^?e*|)^ak^>_ijlWcT$*MRrJJ2 zu8wD5?Pz@hRX>8LFvi&X54rDQTBnGQBs3SP8#!~XL=jVt%DBw8@ySn}gNBNr|L9tM z19jt-E2@(j4DY(Ft}E(uOLnKEuIurx!-Mw0>N-;pZ8X|No!`F${A;L?=4@0MB3%P5J`2wDI^zBN-(pF&!ha``GJ`Y#m+t`>8m{Myd4c|`d%Ayuj>dS+4TY5V>; zw$R=E@RJg_a{OP}yz23rt7Mx^tkqCE=4euVQtjlYeXrB_9HZz3g9l~Y-u3|9!P@aw zlca@LhsFT^XGNUSzLf$PA~NblS@uuR2Y>Yw$PWNb$Jw5hH`#qAJN4mG9$D;|Q0{DV zo$1aig+Yn)B#o2b)p@+RL71gbtwFPo zq|CCx7UW&l4~oxF$C0%OD~q!4MK#@&iHF*=iJlIwXSG!oIDfvR3Zl`>D)JrRS7rJ{ z?~?>pHzE!Vm^E=cS7Mhd`SPH$h~}m~~6#svNvQ4$-!O(GkKe z6if%0O(A!wKa3o9W6BKlTRsu{`EWXLDGz>q`086-wIQC$5q`(cBrqsf#{{M@gEbf_ z)@M({6sjQNwTE-EP;*CPaAtyNy8jHyW-95&W67=~nTBpRi-NvYP-u@zfhic-q!8B! z(65IGg%jL#<2&#V9<09dpL&C~DZV#fFF@u}T)+6uGg8rN*#~Y=p31H9`%h2~@X0)d z=-A7vuhcg$yD=o2(YI3)?KX6{W3xWXRg<|N5&MEzbq9@*_~#qbLVmLusv20ICC_)Z zRM5swFE+b)7UZdx}nDHn(GsWiu|6BltW7g7NU!t-exXu>_>ZR&o!Id*zY}nnlgx>k(A$Mqe7g1=CqUJ1({Qf}`uG2<4)>n3Z{Z4bX z!H+ums+BnL3}62eZhD^3`qN!24G&~HsBy{4j5-&$@kg3Pk6FTnahikn9S9?K0AVs{oIbX^=}AAVWd+rCROm8 z2Ma%O1uob2aajL&sM1{}mp7vXTYUo8Yd|J@9dZ7fXZYmL^L9LX2=RFA!n!0kmjzpO z{`vO&xz+gu`?8_giM@E`U95uSb*KG-3k{NFlUHn`ZyI^=5P9pRE9`nszkE1S)$>u+ zx0yG$L8G|WkOS{yGt_9a@llGbYP|ZvX5FzE1Vocc!EHSBgx65bHK#Qg+NfTw-UN9^ znb{MiD=SNyfT@Y|C+AH4^(~q?G9=ot~_o1i8`oG3+><@x2-?xbyES0N~#s0Wl z`{*5}zg0lk$2z1ihzhOFl)&2Vl{8efV5w8j$5|P#(nj};f{rMlaGz~`qTBZg+{p=- zHX^+$(lUllV##1ags#-9+R22zBLfUU=6Af%O=AX+f+0*59Rl7h z+BXR!6)p+8v%9uU496-T5sZ;5V#j4A=>8VfzgUQ4*jdBqNL`Jg8@kEL!;ba_bnqOm z0lkkv`y&Y@_N4Su@vH!XK2hM@K4W7o2&eB53KxHQ5wyj0uK|K_4uQ=VqK~cq3~Gk0 zQivISzina`Ugr6?J@Kc<4}AMxE8Fkn4Q6Q;V-TV4zUD8xDWUX$0ph)LSP-?}2eIAM z0ZOthsD>++9JRoYsrW&Cw@_m-t`tHOB0&>;byfi@qkwHtO8^2MW&Ld3KN6vD8H?Bs zh(cr#1W1Ujh2qc*Wl|{HRi;rqqn0}T8LG)93~5tF{=wdNft0XJqg?C zZPgmnMas46ovGg3z~A+acUBs(9o8B|t?JCdHkXN=8(Q69qfY)`ti5+slj+tzYy}HV zacBV|GJ=%|Na%@x!Z=b!v7jhXT7*!B-V+f4krEIU5FrW*Dm6wxqyz*4YC=FtLhqs1 zKp>=jPv)F6XMTUY?|06ucP*CdAqXt)-1ok(>)O}edy|{+&?c<6Bedz`e4IasGyVIc zeBL3BL)d&*7Qk7z%j*7WV#etQYC-or&X-0r+D7&~?)H!$Ebi73B`UZ03viV+a0#WxQQbIRI6pZpTpMtYF?BgWK;JWRfg_>`d7ZAgZ zutwv_W;2tydMgD9;jezzaDGD58RL5dcSZoZ#{g{7+vbS(8jUu4mNiuD^rGavxeJK+ z$J(A1T6r3)pqtP00Vm|=vwE#(n1}w=3xIg@n$5YK>;td5{cqjr#m#ui+akXy2Hxy! zyb<;uTlT*9`r@M+or1S2yl4ky!w%2^wl<~zC=b=Sf?fF2}kp5GPuzZ&LIn|g0Ticn2h*vXK7NdtI>3DrnH z(J4MjU*o|uApKptb5Cvh0`$xp6c zpHp);z794>7#6N4t_qdEwMj>|)V`6%L$KlJaRBQBd`yDRXtM!&b$i|PrLhBpr(Yh# z;+ktl_n;$kXBRL6&U$T=aqG>vpZ>rSr7A)kyl)Liz6P!@V=>ig!OQW8eq(@a4xH+F z!g!r(J)_sNZ{+(gHt3bqeAjNBvG9L_pC;&)wfnj_Jb?WGCvjo z1)6|?713!L5Cf!YJ)6@$i|#%L1`@i2P}`25+_B&M6r+2`!eL}{{eh$}yB?(OVPAPJ z1YZL}dHH?j^wIkO2v+us-*vC z1B+~}wQNZMUR`KX<;|y8MTzab8$YbiUGd?D{0op2F$E z$N2Ana(BSe*8Ur)r8Dai;#b}=Q%&tqcT3McQ+JPj5sILawFa;;emc|4_~bc1l4JHw zph^YUFvU=?)9CSD>1!B9sjbBj@Agplw0rEJ!7Y2nr{3@cpikWpai`^(GQH=zEHZd0RcfTp9>tsU!N^J&&FPzRXy zG7*Vx?)}|wT9t&*-Av8aM42q*4;2r*07|@`34(8efSF%FFRugci^5tN0{5T@(CkFm zgMm9x;d7v1d8i*qLN^z4*RRR)KhtW8bI*h(|KWV)Rm;NLn_@kpUh~*Tt>s4h`m67n z>lw${K5;$N0BE<&x4B6(wZMG!?jek8(GMdLe=~4&}H1m%<8wBo~V@@ z+(X>P0`jdD>n=tN06b>~7!jXadxiD&1p-$z_%ncgYR7(FFrkXt5AjX6JxV$Ht5>w% z0l$w#(poRAwtS(eEK{5cNWEAN`xIP-O08llOMOXG>CydC@_u>NT|4Dswxplwh%`VJ z6tWR^95NEtF#vc$v(JBiFdLNyct8e( zyex7rTxjGKW~Eze{^ic<#hrh}0$TZ8m2BDMEn*N6&}YcoEBl@_)D~yN10cdKkS5rC z1PNIHJaMzpqsa#($SY+(FLnWT27u29Q#B`CYaLQoq1k^Ve>`G@J~;Wo&O{Hl_MCGA zqfPOEefGZZEufa?#=Daf?wrp2OHS@Z$DBM04d2+LXs^B7yc1>Yq|vC)A**im>SFpv zEj0G*euz@^r>ih%k|0L|QcNv=Dl;{`(LA3{6SenPYF5^SMXbsge1xr5_Le=n^VGy( z(zSE{?ebAztV8-Ik7SBLv)w(Qk&>#^olia_a@m)gdkZP`ieHR?!7C`3AE-}moM3mu z-s7V#47zJ9P#I_&_WM{LtJxpiDov&LIy#|y0Sy`h^nW@4-43h|cc9Vdr-J4C&v184 zH>L*(R9IE{ymjq*&0;{aqMxO)5{M^^H4D~3pKVb;W|i@K71Z3wOJ_a(hwpIA~#e*j55=_9RBD|lcR&JbkwMKs)5oc?BjroCo zX+Rr_89Ehq`j4#f#C|y%kVx4!v|hVDf27e|PbahBC*|h05MR}3b)hW;__f$sy!VqY zPmEY=US5rm2UMWLc>z_J0&DB#5p<19s=#(gy% z6@56W*F4+Nka#9<=|$UQ@__`~C67qm{F6egyci+xfJ?Q3RT={Nx?&*UTTU{a_kn|5 z{pS4IO5R!pA7=0?68u9KbrB!x~{R|T_w zsQ?ON)?~OsgdYZ+%|XuS;gUN-aPCe3+CpbzvjGE!Qi*pmkJVRFTR|>aCcjBo`Rv+{ z6UTlaTK(kC)YlMw{d-RNm&wlnweeK!rWVM40TjJzwQ0{|f1tfT6}iMWV`iG5lLj{&`<{8tcL1%d&f)DdzmMt$(35fa z+SRxsaTr5bBreIinUpv=_)!P$G=_3=V32Ky6fC91w5{jYs*8^LUSs8;tqfAmqK=_RoWB`;Ou<t_t1=M~^K0}~PhS#JC6 zH%Z8%RSK2=m1-cxpqSzI*2i8`=j8ncQcQ&a*oqjYSL~>>r#*PzV%(V*uvOvi$=(wH zINgK7?atL}gI!hr#B!j4BVbG4`xlSTSG3ym{CNOt3ZCe@Y}_Htw5^FJW#>#UQCKD6 z6UVOr`M18#ZA_uWu(m@Tc9~%%W8KK0JT0mi{Jj3EP-Ww#7kgzwNl}jy@14Vpp;!S4 zz0ErXMjKa+w9?drs`CCRz|HPTn8jo79uW1J$@v0WdSP9&J{T5My_-y}z|7hYz3(_M zv_$s-G%mMV6T_tD+VWEq$z~GQ7r&cVEqq4tl5a;fo=a)2HMyMgFE757oNOfPtbVN` z53q|kO|?}&%iDETA4uX=3Dq57YKm_B)6-Qm#@O%D(A(e4J=G-TcIw@@)-k6l*suheI=#1x!Y8$Zw zEp}#viU>+LsPD@5JiLB}tjP zot1Cp`_n0Z1~1@2Qf3=B$`IZERWOo?8D5VVT94=(0{bR$qu?7+^BHwS$OL%%3Vi1U z!bOfWd|A2zDa)@g=Z_r0yB%U-x<|MiZNNbm#A`q9G>8p>A^K>qJ$y>9}Kl zzqM+Z*js|ALi>v=lue%-h`?8jO;?sJuCYd^wXa~q_xo~@+o7RzQ&+MYH-pogH^#N# z5hddl>d%ZAu@znuWX8|R)q&sA?jp7u1&(*hWGjlTQ2AqJ{d0+KFjt>+ZC3A>3**n% zju0m^UeXT7i{6BM*+JXzs(az_TU&aK-WK&>a%%sH@}&-{8Vb2eH(@wK)k0^ZX_8=% zLQi}chArRl+3iF2+UI0LR>Y_Pb70QxrBHHTWL|vuc#M&JId)PBq8#aORC)*(FQpq85mD%0==VskvF*LN%86p zr^iOnd%Stw-tz)STXqKpcnjzY!tF@aDX}ufjF2$bNV3PMXKiS2d6u#@KJb6%bkSqFNlksvUFnPL z?8P1h1P^EJnhQ1Ojt(pf9_d6aOMYj^GESctjDYzgd86RIfq9Pl^CKp$68aF10a z_70y>&eTofZLH0MdF|i<1!@nZQ82zD++-jOe5o3d*t^a#B8w0is9Ht>LMBaP6Nl=& zbsDi|WLc;SsE&P*f)4)`%SJf8+CG=1&CU^~rup*$fspc^zmg0iyr;1I{kVPulEUIsrg1arILP9#OFyaK_=Ak zE6OO~!pD>~JL%Bb?rwvGck@|N9OIYj%IMy7nmpM5$1?xvP>0d>fz*bb<_BEcz3#1z zQyNYj>6Lq27TA(-e)~rw&1JaP`|Qm(gLC-kw(EPBZ{>ElGO~;+TO{iV*O*m-g2tza zxmpzvn$d0zTFI~-)G5~{S`D-~l#-fDL{f1GKmXBPO9zkFQ!nP0xAfyO1P1X$#Rqq@ z`Hvy&#}T*3r`!Vhk>HKU8g3YIJ*-0TEKT4#D>PfK`^qUlByy^VVVsityfqK;jlJjj zE00~hywcZAxk05u`%QJmN?Iycw=wM%jmTd^5}AfXzgOR}+!pMMh-F#Ka`EM`k|4A{MLZg?z^q!}c(H;da5>^yPEY85%&V3(2$6*%2C}e%4Xkb1Bw{s3HtQl- zprcK!R{BSEWxv?Ab z6}PI7W9s*X%n@L-gmQsGzsQa3N-T)rRycgEbEC^3PHj(Z$wqGpuh*(ku%ES;xU?4} z5XM&|0kEVTO>v-_5;m>n({c2GoRr~p`DjEf)=W-W43?}DVASmX5DDev! z63)a9sGwbJ#1=)fhfN!}Ci9g<>@O*1479ObE`c#UQZvqmYa@cr~Azy-8&iVNTekH z&IEq3@(lIM=rO9!6Ca|$YeUPPX))mHy_L^Zb>OEtk@6#Y^F0NrKbnDU0r#8|9no*! zR0OvJW$v~yD8n=E+q$!XG9dwq?m2~usv>L$xX=^qSNc_1)3#;@`j`5Thw1!m}`WrKOh^( zgjsu|Jtm5F_8s51wfF+*<&lTmp5oG_tC7h<{oz!(X~K_JVH=LAxbuh8}~gfl(W^MyI>>tMbf>8tEYy=lRx>G8k&D>PL?!zLU1*JF~q;7d}qA-dafP311!hlG)5{`Pu(*H9MKg?JnZIajpOSq$33nD*p|so2YNp z=(=+oJQ%VIEvEa3ulG#LV@W!^yH_J{j1rC2fAKXQ%a*N5L4~_ovi1E`CZiADTdNOy zwX6yGP-zqFydO26l0mg=j7-LCD1bQtX(TRB^ZP;)az(+B3T0p}>T>j7aoqp@ydG2M z(aFHLv0s5VUxoRRkNtVW%kO)8kt46Nc~5IN))a8zrPZQ6XW%8v-YEKMOTh`_H-*#2 zE}qn*HHE7UTElC1#P0-!iTg|WI!>A095taILaIqLq%%4sD>W0F6?iX}34$Ozr}jp; z979W4%l%Lidn6?zRtLRV3Y9VP?I!zSzNH0sXafU&?yeYqJuLXSo_7!!lcj6rvG=Un zFH2B$j_DnZVq0EN6?dpF% z>Znc`SnBoP8vltV>LM&&n$L42rnf6Cg;2wk1P1wX{NhCU3a8=#FbBB8;Y<~9Ie=(d-RQN5o_x-CTZXES;GDdDO2a)nUcv z{>?NG_rQim=C)I+v1$*u#ka;gc63`nFwbWu5%b&uw)(TOYJ*FouqNgV)f)+F%p!sL8qL~2)YIY>Z zeNfVPwXLT?63)F+umJ3vY@IoKm*Y#&+Z|57Qjcr3)ZW~*eR?#=9BDI?5MOdSWKMk~ zYLMUUW(aCot{2gY2~a`WsaE?Wg?K*u3L7MQdvM7ml3F6r)~P3uK-GW~YQzqQ5#g5k z^oxwO(H`Ux#GLue-Ctt1H18!Bf(>?Ouv!ud1~*e0s?^E{7U;*CTS-WFC|tiy9*NJC zL@_~T;O-TAJ<7}j^U6L$n>~V8fmYW_Tg;c~I}n4}HJi5p-xZajHgq`NS}HO`d}Q+F zJ^^dKukTa9YC_Y#dM3qK#8`8h7RKC55@2@Wa;!#$3!rYsHAX@| zw=u?*pXI!szjlmp)wShDX;e^=(hA4aYxQ&e4nLV)2e&;Ov^rH4>2z;Liv0d9!H%FI zxzomB7u)JP4kiLa>vgmF-zZqcA4xrm3!d6`7;;VWckUF$#>ib&lC^mvaI9S#tbcmm zu8-_l)AF->!=hRD#)D=shKt07?_ZyKBD`~+lsNpod3=EP z$q;EP-TlZ+<4)|0>x06{SSJEiJs;*;OQowoA&W*lXzrS*R?Fv)uiAU#Q4*dM9aI!N z>5hYcvJWHl8yf<+*H(!&m9i}0Kxx(LQ~ zp#~3u`OO*eIuDeyH@Yk7`JGW@g<*)5TJ^`V-%B@kKLI&Cv+#zwkUh3++4`MwP;|%E zj~StH&_gnz_eimK(BX;Rt@5*WB}8_9kTH5LMrM`nLwn>E1h&*sHr`W&4v+-~u1) zx!|y}9{^Lgq7a>Vx=*t&J%rf?b1biSAl;o-#G~6aK0vKM5zNQ8Qorflo z+bF#%cCwLY9LBwpbTkh_ciSjBNRdyU>*INcM`XmEgSoEV-}!Rt;OMuoSNK?R9L}dd zidb8esK2C5MAJ{XqC+M&Qovfz@QKaOxnxE@FqWd}Ods~Q*|`)8W&L`ktAA@U)`PkLVHo?o zEBl#1=;9!x)vmrlRKEX*WO?!p_JE_S74QC_G0?t>=`e3p%JV*mPd|0Sy>3O-<5$TlYXE{W+gR&9Z1skyh(R_x4dNL zg?+mhMCD^CcIhBLH7sa2fDN7O zP$aWsau0=Z)uUy?(sQYI)G)*|EqKvMiQz{Q@%2=k9dMImx5p=PaAkFwxQyy+YSK6L z>wI6|e)Qp&$d~Q(L|}kO>6q-Rnk}8T6Pmn2=R)=wVZ`Tlc;fBFX!L!OT5xU6_7PNV z(*hGDYv_qooXHKGd)pIE|J>~z2mLY>S{fTfY>X_uQ+-<;v87j|JsSXtnyxpQ)px%l z2ml6}bWL4^nUc?|oGKaN>UeGdFTK`qX%dzP{kI==gc#tYQWZW*dJLxldEyd8Q;nxk zg!r>an5h^VQa@^t$N3#U?( zy@v;%5X|$*@sW~^g-PPQw@+drr#+J*;^K^bW7r@4ti+g*KpSmn=)>TSZi8g7bV%@4 z#mR;w`o@i@viD9GIWRo6j0p88pM9)5<{pZhb}|6eRl zU7`@>B!1zcZCGt)l3B(;TQ7bN3v+!ZTeTi4SyfK!0)|sw8kx^CuzzqiF7ZpZR-HqR&$7ixj5|mQ#n|j;D5P=)qAu znW_{`3RWt+TEaQGyhWXOlm=$##!|XT<@@@K1fax2om4_EPrrKuPs_d(9OfZgwGXw+ zJ|?0JkPoA8HguHyYI%31@e7)ChM|zXh|afRop*5yMxg zeOaoT|8iJV-o6ye94knyjMHRFTVyPMg+tyNEx(I5N3GY3Y4)XCDL(9p$Uzn@*ui{G z5yb}}H_>1LkpHdmp|r=T0&j2}qVC&YyD|X=zW?k4bOvAwpe^jJzb*r2ko7rL*r@8p zxp|8}E~#r}*8=dM_Aqx$QB4^+*;gTZ{>oGJBqQGYjf1M$yaLi4arvP#7Nb7Rh-7L# zbUb8^c`io0c5|O3qJ_?ybX3n6(lMAzagu~HRn>_hGF3y{YqgcxiLpw{ZHRc+3 z7d;XV9i5Xq;hzlM7MWGOxqFWm%9(`eZMZ;^xjRADhQmn7&7xnDxX|cKX{Bm>V*U%g z84q%H0Cj`WD4}fXt-;hj?&K;Ln}O&=PY6LDEBL4JJ6v(U4!lUO-)1VtHD#HN#K(VmDS|Rz=Gpkw=c&(E!=vj z^4kGDn#{L3kv4lh0t5)TXA^qnjDU{fD$55~NxIKwDYvJJs5jD=Rd5f)6MMhy zbBbP71cwFn8u%Q*0$TgM7?${Z$k=rFv+#PlwZrDSpcQ}x=qo%Ls{gaCaYX)VN-Lrv zulYb_#5(}Yb*O(&P|n~LuI3(c@;tFdo|?a|lkc;NFZn4<1m!wK59)o1Lt%XSjEDE$ zG1GAM)qXNfIBTx~jB7aFKr_x!*U&L7Mux8zQWg_Gl7 zfqu7FOIG_eksWu3Unv{x54ky}g9Um&foO_ZdcYpQZR{-kTdkx&Z3XB;?bc-Q%L75c zop$`{`9-@ZbI(nCPb1fI(Jtj{&(l)cQy*ocZ9!$SRcp1!^1)XI?KTcIHxFL%e0WZ| zn*{J^R%a`>J8;gsh(wUsmVsgDmFvnvrF{AcyynKUF%pK|y-Lk~xbM!Yvv;uc&)6*1 zW6a~#&(0&H4`(JCW09Za_f)9JXS~J|e4+1*4WL-drQt*;iMfLTEh)PdmeuH}N_CHA zZIM09T&FV|VJhi(3nZYHoDOJoSt{u1w7Izp!IZ~*bFEndfA|OD3;7fA>0zuuwTMnw z{oWg2{|!|B5@`Q_UR*jMB%6dSGustctaG7&tTm;ucsjKM1pL%vO) z%d(qoLW-$-p#+6sk ziNa?Oi)t}vf$hUTY61KK*K)=VmF$0z6Q><-rWPzS{amrdjt~OfO+B$?YU|_H&@rka zsTm9iH+zq1YJ3A`lB^h%Mz`fMqg#5pD;sj)ERLBqx_b(5!9e4$xbmh{#-+3q`^TGO z*2s|_oOAQQ-9+jcuNrUPedtulokW0UZvA_Q$!m*{FKihXJ<^nAJ}>z6Yv`oH;;ksO z?WW80@i&m4vgLjAnpY!GZ9#jc3HD0d6eqKiV-Mh{G1UfFs7(Pd;tC)6qP<5@b+j8y zx}6AyGStDRX;WWaBseI12QK3kPP?XRZfLNtX(22j(amVRA%Xa5CO$#&n%cG@NY0Tx zj%V(TH<>aR>G*T`;#&72>B#|=`nnoRjSXNhaM6J=-8bz#l0i;0rS;&fKmgI%vhItQ z(}#5NQ6jCY1fE=pttskSNAdZT8e#x8w3-g?uWA@?P)n z&~PsW;rb{nuY1=5Mg~OL``|u>)9xL%ze=Chj(C$CQB*u_7L6n&Io#O1s-j?heJoep z$X5hL@4ZV=(1MoFJh_9-q7nF4u)e??R6{etO6@E|h%!6ch1>!;bL`{(zj-@f=?cN^s zUqmtjf_+-*f2*LMln9;Glr{JT;pnsP`h^zP0R|2>p0nKhsn>ES14r-v${j&fj{>zs zVbYJLJ__;O#}Y)>-@04!@KPfwk+dz(;EjhNm za(fo57wDP(7+F-L%`4cTW8;fHGFR@f?uWPzEcK45 zhU|O0y;5_$$O_>)+jjLOQjAwr&mBLP|3b}U4A4Fdf>)$vm5CW<%KD88A2hll4r5Pe zWy^;8A@18LZnnVIh~T~dTN4Jowh6b!wh2-7W;01+f7ViiBs0DjTMS5^Q^(iRUdvW3 zc3d&X9jhrQoOc7-CT3rDbpdFw-SK>jr^?ryGz;wK9Af(&Zn{2%U3092B}IxmpPach zh7y$E$yf^cyy9HGl2PCs*w<6pwZ%@GWlfmv2|&;_i!IfveeX@&O=^O z4Y;c=X2tFJ^6MeM7Lk?@Y+QA;+(IUHPOi*~F40S49`4+I#K@rg=3PhoG`5{8@-Bf+ zNuuvJYOvd=gp3!K$*Q_AZm4m%jE>3Z3+uZJ0I^udeS63M)%s)ss?0@BN%zLO;nQ}T zJ?$uWGcB#q->!Fv#!JXY4PFVeNvVoK{zCttCgDsy$(`3pE-;I!VPDVHF0V{cC17Q< z6`OS~;n+o8b{EPfEQ*tEHrA7#w`HIfzK36^VJD0BT_3xdxgEYCk2(plkO|T+VRkp3 zO3Kt9JQBJ$Mph2Go9^{o{FQc+wwC{f<8hS^*$kE+shR6=cU4N881AXmtCGJvtaXZv zw4}9ucV3*V%>xIvO!B`+^2^tYS&IbZ+iQSbNC!wr&$_jfl;_YhIkI7$g$IftrQ%wj7LLC}ami zkKzW z*YBD|FpjFaa3i)`F_c6mHip@+-%ki`EDa1-iXUJ$eqjZ}TMmYohSURbkT@f3G?$qj ze805)sE`#LMbxAd3kqNSyAMu}}Dy!){Kv{rsl6)8Wee zoMBUvxjV<{`dE~4xtQmOibt}h$FRAGri~#l&gNz}T@HiK>_^+JigousAF6I;(1I#G z9^iJ^XpHsP#V6<`Lzi0QW+-n?LZvFPE}5eaf`fGdUjaw-^G_ai@ryC^w@|Xg8kxT3 zS*BmjS&#}+QTH^^ZY$tZ9fP^;x3qw|+}IcG@}LZMm(yNKd{WZLxhNdF+qa~H)K5+( zgqfKuc29jo%yqxz?dIh`e|rJzdKb0IP{;?ppK9z)EJ;&B(HLF5uN-B!>;zU z3y7me&4k=V`!5cUBt=K8c$P;LiGCRNk!GW9;8)uE!aTS z>#N*62E_Q>OKWp<-Qqy<#&OvrG_u$jB+xz+VF)U`-r%Q+f29vX&rmEZnfharz+2Eg zqwzl4ND5kea0w|GzlJS=D0n1BQ)3I4X8_QWr;a63Y+>4y{D`JVgtB-q6|7tTmZ;0Cbb!6V~)?7FCo0t`R^RfzXEr;D5!JBzW zY`9s3)99n9q2uiNxGT+Kn$jKe@;)D(icVJRGZNs-YaO!s5fubM@X2*{sS}x7m>8w_ z!>gi~7ni7G$u?&0r~xkJOi>TysB$wd^X2c*$@nkv4bM#q$M(*+J|)D+I=ha&&D6O) zt55@x-~oCv`7yfi>sCtm>FZRzDST#_TDA_EyHx#4LM*QARi@@J{>??sQ(=`qe;vSb z&VOJz6|SNU&}pP+zOSOfXSTbD093<`?+CK{TcB5J8||DhEq5sR2LI2r+)o8F7PL=g zA`yA3X+NG=!rw=fK;1f=9syG+Lw}q6Eoo>Kx4hIK0dIj_4O#7N0EvP(-r0ZmW4B9F zxf^}j-+^3w#j~(Oum{w0{Ko7uJ2-4fjF}k~!cf<6)(G|}w$yIB67}1`bGx8MV>y&$ zVE7O|B~MWv<@n=?7_|=VaKN8*xwt6cWNJ+!pAk^5?3tnB_~V|j(+z3aC~jkVXz)Ys z79fnVRf2iRHMbLKMu7d?+u?5|bpIWVhef0S=p%9leeY7gTPGh|@Y;eF zOwIZ+<@9E#bMQ(xtN2^T*ss*1sNlJgX`8Z?P<-Z4C#r$mCTUL_*CP5du5T}A#HyF* zXcC|?sJVxc)()A6dub(!>=*b*yG+Djx*xQB?auc)bKv#Q3j2mXJBvNKf8@7}VSLQB zT6)bu^{`ix6;;I3kr2~nrImLkW9S9c%s#%hHwBTIF(l$SN3pS7NtafFNkNw+JQF=5 zT9oLQ(g2pf@4Lu6z?Cw8EQhr#l-vN2*IvTcAY?BtKL)U+GHD-209s&M^}OrPn#lj0 z97%1XN&rU+Zukk-auEUa{^)tj8@!@!oVTBtV+B8EoKIfO)tb_6YBixAg|hO$LE6nX z&NL=Z6_wF`a?p}RVVXc0ywdTPvfYZsX?F_}K zD4bc)93%TY)Q>DXL?&8NZ%LsJN}Og^L5XEvF=LW``}W%JlGhv{2INGAN6JYfc3}bv z+l5w}b--#P_>yXq^T=9W7;e}zSHk{QxDgYE8?jc&U!VOc&JZ2Dn5{Z|Pw~tcUW%i_ zFY>JK8iBL6F6P$Y_pst=lQ&dtxmt#OM>Cxo{?L2PNe z4F4~AxIdgUFVmhVcdZwh487yMS$)-;`>Xf+h<8h?1@8&XSLL|mU|vNsk}-$q37pk+iJcwLGJ!YuhE9VmVj?G-$g^-M@@KOr-j5ry~1{F zD%Y>(@}$N#R=56qawSxzmQE-~M|{PK83(8aNr|%KX~A8>S_?$*=x)m#TOn4ntqtu` z{-f6Ozd2BV9HAQ}lzyX_QI978nF_;xKiRG`SS3>PW;Nx9?YB9*xjZv3+jkL!jkbbc zJ3cro@|Fp>D;UpPK*Zg&%Sd?#ID0i5kH+f4wIASgPD$Hn6{ATeSj({abnL|FZ86u( z?y|#4ua!`vCf{x@zE0z&9;6+vdu1oO9KQ8b)xN#v-tALMqGvNxyF%L2+)J7z^dBe< zu-eF;mafdqz8=Ni4{GooB?IJ%_;0e?YZ=YhveTfN6iVpAas zOT)g}%|>jrPZTA#@=G@oJ-$RhrYVYX2ja^#+OO(rJ4?(JJ+&*0+uIYhchLcf_85Ry zZx3^P93`(mUTuGCg}R$`_!c#TYW3>2)vGp5oBCdDvn_Rb{Rg#zxB8~GBs3?yc-0>~ z^UR$gWrn&PjdHZ??uY`Kd-zqc7gk7G;H;6nhFT}S8 z&3tT*v1@i#4!e_hCyKS&9wXQ7GT*YA^?ABP**h7-V_Tra-#m@hFnI(3cs`18Sd^ z%y8j9iX8W6DQwFJT=i4A*lbFujri=0_Vq)(iWZWAD zDIxS#coQclAQ(30Mi2Pb7@$&}_*MEN#MIS!_4N;efv5qE-Zu6vLCn$hqT8+)ZK z!e5$lMY~+6t~i%x?8^jQz&KGN+K}<#`}wTiUd5zA#i_WH8JOax?HTOlsB8IPz)Pd{ z+3S?9!QNHVlMImAsH{( zg1NajvS&jKE=o+Vg?aoN|J|r8p1mn_4w3)bWa~e4lMG)Lsy&xW`rW#Dx)`P|hChwt zPkY;S5&eV*@KX8YlRcaCum9==;OYu`KCSac_bLC<>XHRj1~ys^;-00u@F zIhr=J?Q#ja&*T^tYBW~MsCHO*duG~=LUGJ`6eR%(n^U1}m;zmEsPzht^A1R^d{ih| z662yZElgYLnnvyieGCRnB@#bGU)FqT*&znfB^S1)7pg$SeA-QVL-9{F?bD#A4O53m zcOhbcMNWd{kS_1USRb{vA3f7T)*nk5lJoQK&9u!5bL4%|fvueM4*6bD4YX?KEh2Ll zA6uvcfCx=E58|7kI$@f3kL&&&-4AwKYD%^Yep_S0*mE4HaH`-h(rOk01btfkDxJHX zF>{7BHNl(bRh*L}J{L}(i=IooLJD1By&a8zp#voW8?L^C`YSpNnQt6eSR_8Ee1kD& z)SF?E>GHY#Np3DFeiMt2BA@I3T7~o|+zn&AB(bf9LMEO`kLwPTAot zr2!;de3RI8l-QuFS3t`Sg&s228e@`8p*M%l+)l^Lr0jFrESXSKg(r3=uT`l-9s?Q) zedfB^#&XV`n3e*`@|6xSpUAFVrbuk*R7Lc?BamGe)3&wi66yxSz$ zoRcGeSSE1XuQ=|Aut~Onc5HN6XUZjd>S**`#pnuZr}AYNI@#50jn!z430~Xi8+vqd zS-q$98~IEL9KAsoptLLST^-tCbHuau4ouXA@Yy?8Z7dz>g6|47I&LVQEf#Pej;Qc5 zCUl#w2oAOc#K|2v(k9bx_;8882e-s$0;680K$+Okc1%js5uVhZesaWx_7 zDH*ox{hq$Azkt_BK9b|H@Y>DKJ0v-w%Y8Q*6+4YA=cn!05&6`K^=90j?fg#=i z+~G|%Qatq1^t&=-S`VI{STXrT$GAd@m&aAmHGWSwV|BV?`jZnCztCQ+e*R)ckbRau zl^%z(Il&%@+v<(>EYlSY^_OC=>8PW|H%1yx$amD<>fX@o!-z?`?X?kGzu)h35j_Xk zx@rJhmy}%nxrv<*ECA=Nbjxth&)S(^o<&-+9jQH5Qp;P%`ovD=|eZG6{!IzD{#o5355nnq3(dk}7FrxXa8!2z1HVu)nEI z&!g?jj^W@yzo6Y^m^)+<I1lHr{+Sjrk!o}3k+ryxq0B$J z@;^+2``@*rej_16F=FUurKrsdhn9KsKbLSm;~e0tn>NSc#5}8^x8m)mAAP9pD}{6^ z+s{Ay&WOs6@fc^l-fs&BU2HdNg`ORvz%qVyx)a z8tTS{>ZS>+GgIYPP zM&;3v$S*M?q4==pnng|Naw822m*7l=)EAAWlhLMmRL2U140X4~=*DJlH@T1u+@dRH zjM(*^c|~Ej#i!#3;=3Uz;vc1bf-1;n4rG%n?Dc`02Bn2Sw|Qstmd^k_C@k%J2roWe zh^D;9x{K_QSfiDe?N`E@{`XDyKYbOb)%$}kQv+o-x4V%6Z8~I#RbElECJcR;vA){* zK0dBXcY|6GgRzgwbBcpq$$U3|B}>~l532XQ1NkVirT|b8hK|Q8hdF*v{Z5Y`;Pi0| zlX}uu%bz2%KlPpn1dF0cZAsOQQNxE_NSE@B31RotiDk-tr7;W-^f==ZcJ7;V>2XoJ zq{iF7No^mbeA!zL^%AMf+@d$$doZYm?&llOvaH&Q$FVbz{IdSh8C|ARY>k%VhnT#Z zh}F~~F&elfF1nRltNHA12gO(}))f`Jdnf~;U%Sx5?dE@Z!&##8`l%6=H1u>DzXJF% zIQCazxjuZ^3C3`Ov4FnB^|AZ!>FU7a7ZVb64JmbxffBWYp38v57%0mN)x}@!&{MO& z8PZMiHNVP_ZRO8mjKiPZ$A7vnX&~~c78qV;$a!tE`Ez5~bGt(?hj&=GTzz7P@b%B&hjQvPOZ%)Z(x{B?ug~qHFn?R zqIMRXcS4`9IL+_irO{$a;#Tl z5VzV4wr&*S4=jo~Z0>aiIHP<5bBqtHM%WNjQm`b6VX}Z;SS5p--4%S|Y}!Y`lCYF_ zu<~|TxH$SeXq5I&MMF{7hjX!Vs^u-T&pbX8m3m!-qZyoP%-$cj|bab#Hr>HzCiII<&_D2# zH61<%kJ^fAM+MU>Ns?L;Yp`-X4@`5G-R zHsAwm&C2$pzck|3(udMzCy-!m$@0ceM~KVMLNO7pXrFV~o16^XC;BTh2maFHT16PV zsG@E&nZjs_Au9NiI_!BY%ONEf6LlBn8&lh2+BAdDxdEiMX`q!p5s^V0klCkf@=SdSS2S@$*l{F&zD$bfaT$MtN7Y-0LDJ9@g-w?i;#J=rH zsMT*2l*VWV1`n!)%QW7;4WB)f9oT|p7BpBBn-(InHQ<41hky)y<#}DTw#G6$|E1nK zRDO6Iv~EYV{BxaA$)nmZEhp(~`r73ToIaNAQr?q7%ex&;Y4Y%xwac-xQ6BR#p_Dj; z@hV$7Bx(ZQ=SsMA--EyAd6jWG=$#UG#tzE97GG0Ql|4On$Z!zOF4`G3sw}Npm?h7N zgR?eNoazALttc%x3`_gIiH}ZzkGbV$_k>c;JIrHYs7KOG%9?_`ztNpi7trA|dCS7S>$>7=Nfcan=0vN}kv zuV$}0iX1YcB5X;ePBL;)a#`Yn_<`6@40%vzn_18w{Ew!+iY|5dOV+x z$Nl2*c)l2y^cBhUD$-h(55#6{7er3PYvji%m&Lv85Fv4aEW|0s!+UAn;+lW*YoxT; zv|TQ~8ZINydu4=Dl|723w0fNKLqb!0YaMAfW)m+9N^F}cZBGP!<48J_J`;N{&~s;z1h5~cf_5e5@oBapaR+QSp(re=@&&7txp~7 zdFd3eFQ}7NlCS&mp`cit=`gsVH>Sni$?i`pldz;nCdRw9%x|5kZlY~khi0dB3IBGb zUodWp+V@IX3ADMW1)}beT{9%=`?O0651j^yF8g!E&!+R%`ghFM7I0E6{~~-`UbX(i z?Tqr@eRlIKHtAfy79+XXF?hZd@AnSb<%(Qm@5gew<`WjPVeB;w-uLAWDgON2sCvD4 zD&-Zdd@J^9c1xdaxl<{__pKc@0>)e$I0=qWeK8A84nf*6O%;LPEhz@l^jL5wT3Fj9kC&} zfCvR**x42`AfT{!BS4yHlRgndv$^8>BWzkRVW){i@k~_%xP=8|qJ*HT3BB>Zv$T8N7vb#HYym9*b{ zE&vyl=`dYov*Ms-!#(FN$^u=3L1!Q5?6fK-__k4QU|xWxk94Ze&N|$WmpfTIV3t%C zrA5{-)3bplEBR{VmL9}HcbtmOr%5olz)zI=8jZka!_R0qld7|)VZAMf?xJMcE&ODY zvjOI*-k^CL3c#ktzm+wJsw@anHlLk3d{E1ZBu~RLMlg*hO3MR+ zUh5dtffi8aMmz_W3m?VyDzAxC@LVdCNv_gnynEQmZ74|jkE%G(r6d}$NnS`}jM#Tc zHDe-_10(br%-_yJMn?f;7j2z*`TzE?^{^iTV2Clp8Ji*jeZ0Og*TG9wH{Mf)Ap}H0 z?0!`V`Hlx@P4?AbNdTac93H^tHenpW1Lp}FPe8>f#aEj^ zP3R8Yl_M_cu$bQZJ+va}PCB zk)`(-a=i}O0&z>a;Fae;cd0a=@g6QNRB%oD;e_ze?vcuk4VP zh0T)ZoZ`nlj;^?XxlGqbfe8Witk1;UM`ee|mR~F*OLSRI8smXJ*z#QSIchQ9^j5A~ z!8#q?qWrIc!7apL!I>=2HvUvHj zVyIq6A{!Y0fVn~h8NH4ahM!i{#=Bm>HZ2QP$k?2x0RQD9W@WK_FTgu*Bjz*_)z=a3TIa)!QWoFb zWI$pwrSPyT4GOVWJ_%Y9$n5ioKv%;Ttfq;Z(8OID<_rvvg*m3R!MOm;-}XYRK7xmh zolWpoOY$>|UdV8Pw6QuxwlsQA|+uWYn3zSEVk$Su=S`alg(44Nto;F6ozmx!oZ z`Y|HewO%P!%qYS`gvtf7s*Fb}CQ5n$-Y*ua7z8<9VV%D&PBAO);4Y`%%-!mnSNkJkQ9Ee7l1wvZvx4Il~? zIT;BP5_pRL=8!IiJ*40DFDtR^Q#{w4OJ>qS=t$*yj!2doM1Xh>E#i#a#^q$J0Z%4w zICSSH!`75l;=0@9m8BCWX6qTgex}7MeSThl-Z<~r{9KuF{xkI^8F-9(p}g{FZ3FW@ ziw9t+B9Cr6j6!NIu`sWdzE)?GN6LNByc|giITBF|ne9l4KY+B{xO|@drKy&pyEaz@ zJ|J0GkvEx-u+0c!Dn37k+I}9LyC#f$=Slw#wI5fTKF`z94C=Y+Xz)W~fhFjr>K%Qw ztn-6|6^j3^tkp77uWaxobqf$;x}J2MJlKQ95S$X)Q63cLvb;y4bv|BWi32*$aHZIMo6}gKFa) z*&!|$zB2|^+G_rV8*z$haTC`wUh!Tgq}f9&pt`figT4YH0Rk$Njes+#QA=`X2Kl<> zth9LkOsyj8Cr1WK-)HUhZ|T5QC|}Fy<%`>dnwod2FmoMP<(NEYbF@?et6Bb_%>Zg8 z8@)?Pv-F`U)|iHFoh!^mk6?W!D^}TnJ!QeeY*OoE9R^6s3Ke;A^(8z>Jk>LvY9QVk zQQCshZN6jsSkH-XWyze6I1G<^zT+gXSDOzOyEEgw8^K@x z>p!w^NwS{73Ysf-XbGBc_FtE)V_4rmV z4Ny0ve#%~TAV|(FSGjNwC)>v@g02%!i#R$}vKg)U@K1pm3N*Tue|oseMvwDECOQm` zte`g92|Rn3>L1Gb;asTYvCp6Tl)Y#rk5PY&6#R=XDoY*9l za*z}E7z;1ReWe|`ol@;hsnY^(>aM-ur8d;pU2eKuqcdS$Ru#1*Is@}Z#!P0IsfKKR2X2npzFXUN zTK?D{ba9iZ>28V3*m5Jho^!RfS^BO$(_!-Q&;2qmKiR#(wIJ4*$Ls&OGLUoeK|n%l z$){cZ!3E_vjZvZFZGKv^@xokUm08)P!>yAZO|=m7S9-d~NGPt4wE^PfDp)pMU|i1X zX8gYer^*!v$PKeE+hp1h$E-MVJUzuR?24ou zaD2wsODI7t_tm8FIdODOGKy5@1Jx@fvKfK!I(}UK!T!g4Tt)c1=xF80r5-VJkxlMG z7ofg{tfBozjtc$p(x!4d=;}nB*|Be6F#ZX63t42xLpUP0Z>19~a*R;!+0jayvCnew zrTj>9w8aP%#oarK_%Cmi^{BKZp&HGzfE>l%!{_C|6{gva zB2>RR3Sdg{O{`*gL>Goh>cA>u8*&7)eajY0BAJ64mp|}{<}1*q(9r(Pi}kHr9>vxl zYc^wqf_qmhv(OCCiB7IEI%sO!D(lZ^I%F%)>;N8|_fn7{n~0p{{gTR*`(zV0XT(Om z8j(Da^_gf%YB7@*sKDS%KSjLzjCsP~m5-qC^LVDZWBJn0^pt_P!rL zg+)#qEpE^`LV4t2+l42{gl`~$wsSr|kxI%x6glmv)h54?ddMX&<*u|R-P#9=9L$63 z`IihR)9eM>z3*D9EDQs~HwUH=?lJ0zopFM3Hw90)+|3~g(L@yX{Q@#P0ktHqZF&ll z{l0LllB=kd&dtd}ty4$mWo&u1z#YT}U_ZGXudYX21mL=|RcYySdBX{X^aY<4 z{N1X~^L@8~6{H?U?r+Qr?|b(^udCvL-*EFs8MuqWQC#>qEaheuoZYP@ul%IrIZ1Wj zwYC9tWklmTEzmmNWfiy#&-)p!{ z+s)T8f0B=~BicvE%M*bovD83&NiG_-G+mbLq@{~SMTmaIY;uiA`@Q6D2Nl>^=#GT0 z;to=mj0I-1!;Z6}3F2kSCi{3Zb(^ii6vmg%ggoMAR-;bXyqBVB8KxZJbkKh%kp*0{ z>ofA`cdkp~3aMTF5E~+rlcnpp2CdTca3B+*2=4EJ(le#_7hl{BVotI1W@*kkQ02x(=V05=Yp7 z{lCv35k`RfsbHWO$Con!;cy4_1zk&{Dirax2@wjP1K-4knyWa=6*-y##o-q1z6vPg zv3}7)>k?P7IFS=SAVjTXNTV#Z*lW?Kym1LrB)dD$XKRLDVHJcSAKHf^T}!{)0T@actQez#fK<-;YNv@N3v9K07IVm;qz&K?5uT#+`ZWHJA? zmxbe?Cn|7Fy=>RJIry%3C5&?WAJ#nnj`r{=7nsJAdFk=|mrvABj+;}hA-fQhMUaL(3DGtb4GH!I2Vl$3NUDC>eGSow=(@K1Vv59>^Ztw z=V!+Uz{wm1b=3<>Yh@VfA-on&_l_?%U1zut4I8HXV9FyjPV=z=EiFOqD^$>Xx&ShY!Q>e!q6t#3)68(Fb7)Y@6 z5(>yP#sV~=gaP@+;2S1rdQ}KQXS#4-m9K@e9)XjJ5#Bfq$YJ>6e;S}~HC-j{EOAj$ z(f3D)_K;!sw5rgnUR+RB_-|z%Jd6yyP-Tq-7x3I0X28`vFmCae;77Ye_MD$WO)k|z z!51isMs98w8l*$-Tc|f4YnL4aVjcT~7+Kln4;GJOPbJ8ilmjg49=xfHY8o+nGhquzeBvc9%FZO>R=hkl2WWxm7@ zmI5?{F+8USUc=9A`(3l#bB^%bK+*q*F&KbjZunueN>=$v{r&Ok-eD$j^*a-fTjgHb z4^OF;nD=Q@tA-<52VO+=i+m67x{*3^{B(e$MU>XkdNV3bVoe5~MAW23wKyg6WP7WV z=9Lz6WqEp4{9nczPfbDSnV#H$I>iALC)aPb(@}FSH^stlHqc=1fB1Bd15cu{5y>YP?o?{NurIEk|P7HI3QQvsoGp_iHa-&^SmX%z`7ga zgTa2~q`I8!u4r(Z7i}Z> zTV)ER(*BJyR^dC9(9_G^U<=UPf~+1955yj>fx1v4FJk|#;_$qhrd*+$Y8@H^KzR+Z?rANV>K1~hYUM5IsXvq>yI^H*A;@44PLrmtyIz3$ zl^7VD(;07<=Y$ftH3{edmCp6{OD>^=u3pDI=U$&N zFqaL(0~fP;Wde!z+M1fXSM`^gqdGy4sSg;coXB+3HEX3wp3sTnyRV3gQ`Iz6{Qjr= zD%$K^#lvS^v{z4W`|9TaLoXBl!IsuEUC|(wN=>iKy-=n11#&V?ovfSz(5?uu1fcq} zxp+c_oqO!_vq*aWfyzX8x_Irus4rrUjPpv$9p{q2I}PCqL8CNH_zQPWJNuFA`T4Q0 zk;+wf4I-5`O35tX927;?cfWz4#4aH@318#?S*&4c6&cjMUm$}&1lA`dk#2S@5NQEu zH!8U2puAJ}R@H5H%rzEM)-M&)gcAZe`GIsZkLBJKWZ#@onHM{b!Oh;VhBCCF1r#`7 zNo%>wxpFFcze#Csr|a*A z-;$-fEj?lZ5LtqxWk0d`t?x9Ome*Ri?k6&?((^F)5u{ zajxaq$c_rdf6uB8i&3{LkI9TL&%ImIZ+|Hex77FQb)erPC%9T{VJGb*4!wF|9U>vO z6BdG#=Yz4*^Zl1X^1)##tgC)mRiWURAwY5tPZy(b3vjT8(4(6r7Fmo#4eO&=QLtVe z1SgUz8a2feajiZ~vD{D^RY;4P>zm|MJwv=O*U!}=H{gDaRrbO3CW?}+j4SGdSXud{K2SaJau4Tb%oRN9)W?$YEaU3fsZJn= zhn9WndjA@eVyPP!zTOEHW|3j$r(r<3WFNc~jqG~+lLxe3yFgrSMdrU48l=`9m2Mf< zS@CU@>%f^c%AK}OlgZtA?w-sIZVGEu11lO&Wkt4hdGY$aaswW}QZGF4_w@>1TB;@U zw=i%nPQB;6;$S-e?(c?;V-wSXxVoy#NtH&#sOdXuFCSu#(Y9*%`vkx)19qNcBvg0B zw%RSEcoax%vQpKrw95J>Q>o`!l$D&f4|3s%N#Q+WjON_kVEdM+yM|}G2K8CQP)lYJ zs&YH48IHQ#kePOflV?{5v>`Im2MQ|DGxQBMi4`z0xR|+sEaH_myM!@FD zi8)K+_!zW-Uk`8_VWbpQqCr6VNMrEe;)O+?^c@kPX^=`-l6s7J)T$`P9GQ2hF>p0O zoWuPlKx=<5FO>1{#a%?y2l;z0N)8qSjT0vmlh1OcVldyT?^EX}{Wd8*QIZ`oP ze@vnXNmheagl zf;L;As#)ZrWhc|jCK)e7Jk~ZNu|`SacV!J%H2swHeGJNk*SgAcE6*`W>o3e^*Lus@ z{FEuj=he=Y8!V@P`jh3v{;4lr_elDFzm_j>-Dc|Td~v38vtx)M+TRB&IIumDcZn+N z+j?z!E31a=p(9CM-2PT;$pRer7guIRtL)JLDdLUNPxfgtM%f&YLlm)m=FZ`fN)!T9 zY0&m};E?n)TS<-u1YUQ+Z(ow)FN zNcF^>OXTF~3)gTrX4gZc{8iAB)RBx@k~a0P3Hic>$rX!P45D402Zq*ye!v29M9{@R zvNE>s%wid(1|9=$`Xb_bZzM$j9NEKkYUm%o2|M#1(|vP^=y$?H+U-E7UhQfZIp)yH zmplDMvb(&JdTHj}Zrv=u=fQK~F8xog5t)q-1n{pz^`4$fsoNL6$_%XDw;s9J?hbEY z-h9X)ci*077c$IValHx>V`i`D1@Wg|`ssP5z~FEGt?koy6?(Lpy7$h_E2%ea_h_u4 z*~XmQ_%V|=lkC)%xF7unwWb;sTEZ0#c3UzXh#NodYCghL>Ib9jB6`$_66M`woqM8* z3xB)n83gZWK%F%l(b%;!>QnT|SzCy=tZmz$T}kV|hhNt+k&_EwG0@9dLO`O{6HA3^ zAj3~=)hKi>&@hO|4989cYGaRMMH5AM12%piZln9jsblmWW4Pz~;D{@M#EtdM!usjG z^Ag3Uw`l#T-HpE^t?|4BI#uZbPDZr;l8RPF%OjPSy{^cv%I3x42@0XiAf@Rs2s(&o zsdQCMWvrZ_v{L#s7)4DNkD^quEE3{JQFs2fFj-N21z|+jL!l-c*T#r&Cx37FB4uCy z_;?f*ubh*IgR=q?h5wAAicMuwPc9=ccs;_~7({y+d`_pzY>yEPv+$D zBc?c}P=lN*lG+LeorG}zY(xjtGr}a7{}4+i%LL~rrsB7ooJ4}a(d7h!cO{in z8a1N>_gsAGIB<@B7CWVNn@>x}zsB=EmG4t51xOspo_)%2IdhL}N2L69_-bN=@{r=7 zqBL9!z1e*L-h5{00bm!@#ZTFeFOVi4f;3-( z*dMrEo{4d#5offyP6zE8wR>*vhF=?UjvR zWJO{p0lvyAPO+lk#?I)@)|{x5<%yG-VRjGVy`tA4aLbcL8a=E_O6PWK*N9I&>GSs2 z{5|cRY4D-!iw-HSdC0Dvm#duh5S24*r;iy?vYrEWEiW2Y#vG@cvPg!(YrNU61sYGl zp70OXOmkey+-4L5-YhxH%w!M*g zvrJ=;l_y%}UWERG{IX_D|6s8Cukgl%jkP7HEnPp`B7HW=v-@vl2$CE7+cj_#sjus^ zm!CaWOM#`wjs0p2x53TBY;>IYJvWOBzWm2^pxMdCWA(7_mWr_TjqS8~B$Di3# zeoxsaXxbD^(C1zm#lD757K{h>n)N1aBqGIWX8f7N>|z%(^A^m=7+GEElydSxQ7pz4 z>)%X4&hN!eZG;=FsmFHlmiH{u#)|83#ru$kUV-=GB!oCQD&Yy7cc!$slw1`sOqVKL zp-AOx>j6>IwLfJ>NlP-sgCjKJc#>VBee^#5*_K`cssn8gox{<#_y!-Y@c}`RMG_Ax zbEHM=5CNWr4mb5*=#fHXC@1>Dg7~O_#v-<83jW3^a|54yO6ze`^vd4GH8M^|jY-)W-O!xOtr)n%1?w4OySYop^Qy1Qz8 z89&`Jn5rsgZdaR+3UgUPm^ibU0X^!>6IjY)Svs-$(@|qi`)PBR-E90R%1-6_e<4>g z93H&K-P9O(7#aLeAbh8F|5%$%3Xw^#zB~b29*U_f(ofiE*Se@lY?vUa9ff9GJ0#F+ z2>TTEUcqZg*q4)E#eJ`Q?z9COr6^k%1e9uH2DqF&d4OBCRQGJ%z=9YdFC+#8@?XqU zsD8R4)d?)(zXkuI342Z=Ps%hK;Eeg*ngy?6SIiwv+;}GkDnbGIY>uE(l$=*mK(hh8=Frw z4Mh%nX1D(xG8C(sImG?9K`OKC=mXpHFWIncZOolvT+yA9n8C1;+1;dkU?w-}!1Z2g zFvNj6x>F?HecdM-SKXx9zt2EHC%&{P^)2EC`yL1egP*%h?1ib@b`fJ2pN{^5GzazN zj4M{Uq-)N2a!|3~(YW1aW-bY4<1x$20Yh$s?f6-T_RrU4t!LPU>4{?Ue$LXF`epuv zk)(3|@>WX3n)2D^^{OMfN{LrbbUo>tJsnhsk6qidB&6*wKwA-R7t_X058?2Bf z@W4*yl0?;es(u$!Wy$>xa522Fxg+)_C*RG5+>@*+pwM_bgKTjztyi4wC0S8+L+v&$ z{7<}*UyDx-b}2Pcl+^T?m)!H?zd&)%6=-SZq26UHp@)z$3|9<^dPPG5%t8`@%oPB* zcdXsRQh6b8HGUdK`^qAQ7aUXuDcXFdFHj6av@Q2hW0e3^x;amBbRaPZ;v>}gTSG8 zpQXa3lUznUBvgz@g|E1b=3i*Lw{?2RZbxr|L~^)|%f>gOYt5CR!vv^BXn?PNbJg8m z@*-aOlu%unT#9meUOJkKo#?@%vMXSAv+ENO4SuKLHoULDBihd{_FMgvJa2)WJJL!R z?=7}xut6KX?%V9eFGlV9_{akr(Ya&=kyFkgNlARnr}zo0_>YwH zl3WqVg&D%+{zuUxC}zV#pM2k_oVs~&E!H9Ez{4vFzD({({EqYkapC5SIbm@;DDEoc zA2_AoNq%6eA_4~=1}R2l(gCuvCg8uwP>QV;;^V&|d;sZ{yZEkJc~<1o6-VoPxYm2Nas8WM1k2sA(}Lf&r#6_L)Scf=N;{t(@?x~*cY~q! z?ql$5q@;BKO*%YCSFXYjA2VBUz=V)q#zss(nk(#b&gIAXRtX?JFE zL|VU9`?=It-Pyb^`W*<11m|0un;mq0H#A)oeR)gqOri0>?)yN^=HlS~Bt9MhEv z6FUJbR(s(W;c+H+>5f^8zWOx~ds6g|!k;;R9oj~$o*WDfS(eR+d%;>zRd6`rXTmSl z_U?~B5IZg$NWKUXfLH7T>1y&pLPE;Ngw=3pc|B6?zV&L1W zA>qd-j)KtBEL|oZj-no*dC8N5{Ggfdydh30T97xz8#mNah z)8yXQv^#jR<7t<3Z(Hjn;!j7t6XqON1df>N-Hw)KuV3L4C~Bts%>OXnTYh?iPBGaO6 )&;^-(JZO}faO(_OGw8CzCCL>}LFVxB0556n?^<=9>P8=qFKPgdx8 zC$E$C6fVfBb|R~>%A&aSbzaakdVH^R15(~E{uxW5o!{D?p!h^m)+$u2JzyVnSr#Jm zemJhGx_RF=s15Um5U&DId61Rz8rYtBA-~r{C5S|!@q$(cl!6pOWL0w!?HT z)U-|&!;P><5UsxZ|LFODdHPo=hD)5hHQ72!bXtBqN=6B#a5pY z8&a}aNb|C1Gn925+Z{)ie=C4**Xua8ydpns;IE6?xf*?vvt(?Bfh^u}wi_cOg9=Rz zcS^EbpEX4CorJ-%N!CVr3Mu$<*>>;oUVI%FX0SYU$z3^Mb)J%6$_vM}Pbha#l25Vs z!9Y&$(U}M&D!vqVIijj}$LgaZ@0?Y>!tdwLxF&EC6EfuPqW1cdh%r-(%}F`8y2@PM zW<4e34O#9?54|L~*L~^CH1CXkjJ56&g2Kyd;`HAj7uc*Z2=3Q_$4~EeP6;xJiK*G$ z?2hLn3}gClO%}n>Pfo#adC%JU{!NniM5f_4iY>4Q3~IR(8<218+(k3coZTZ-R5L11 zIo*(l3Jy9leTL1K`z6Lnf)2ub^?A#01!ckTk4ufoV?}FUI(lPcCYWdzceB&-vDQ=v za`jRTSN-;0taLZ}F1NqgI**+2Exarw^i{!Cc$&TCzg!#m%m2;-2oZ0i5nC9smMj0= zvG~}fy^Fwx&>ru8nmP z!0NhXul~1@ZNL(+QU(|-Dfr6_;*z3jLBAf_k6r>~oxXPgEhA7j`;D@YMU^3cpyX03 zTzK)iuvK&1$%LDs`QuFjtB~{#h9*l^^~!#b1*aN8%3{Nvi%SUPfvslqdBbh4*x<`3 z%b0vr`1O6Qg+&C#TZj^^Xzfkbk8JtWUuE%+#*EXJ2?P|}n{0?z9Kqu*cd<+r>k*^( z`?Hsx*$;w&BBg~P>&G4qlL!_h(Pq5IUIz|HQJmge!Odbrkwga>_2rqrS32eowDS%5 z!I|^MtDa|3&$74Dny(@nENo(|H(=b{p+C5m0Dqr#vYfxm>gjtRN?T=f-0)dciQva* z?B%iTPBj8-O%Z47kcr_?#I6RxFyDZie%t;yWNR2R7%6N0nAlucs{af(7@3iramgXo zdC2U{U{qaVojH03e%G;&MP0(kS<%`j1O_AX_9MuZ zF$OpEO`CdQsNjG^|5in)zY}(euJo27;+tI zSH`;HA{5$djr>={e#|+k+T!3_6c@eZ*nbf2sHJ=7J=qkrn>9LVd^UWOfxT{Y!f<}O zt39~FMvl4R;rsfZE_lftYJ!)WpMO!LyTa|}JX~YNyv-d0GHo>anQJBDONgMo<_1@k zv4Lnl)%+IjaU6chNv8jHF}wAtH-nx|(#K9#-K>l3XpGx`NmRUM$j+tvUdn8mfoH79 z_OQlYPRk6B)xNv#pP36_p znRn`(0%*o@ z6K8~sGo$mb7`$^U#0CrP!dA3={W@ulb6LqOWE z6$`_~R=B_V5p#NX;#l6*pnpT6M{c^(KP*7ap+0Iv)07#mpVjSz&_pjW++0Z&V+;_! z=WU@A#~Yqu=XiGt_t^#W60aOTDT?=wzlzqIZZD=%gspPNCuna8jW-^sVLzMkzTdGW zbinE7HjQM86dyHq)tO;^F5>lGQpry9!OdTzpAw>)-A{W;UvHB4ZUUKtUbIxhKG+A#uu!)b?2? ze{;nSr1=sDqU=N~VVQo^>Cegbmp5yRmVTezlPE(m&vfA?3y4t#+QBWq=^fF~9IS(; zHlhudnx7vP0(;;IThv@k10CftFPvNdbd2R!X~2&#_4&-J^)&2BX$$a^<|S(WV_ zi6#w2N4pef>?L~&o1I)`MS}av1Fghu!4|9TRNLj(xDt^5kx#UoonDd>lSG}N{n@16 z>4zH`xDzv)jPe8Lu29^Qkrd~b2~#u0=>{j*sDGS1V^5gP_#yXjtn-US4;ITE?SBut zT=h&7dz`AD8kIRd5ZR-h#XnMea(1fr$R_W}s_|Vv(uJshG%TOJ`LZ+k?4QZV*$ltp z^pk_uP4pyDW%;W4_q-^q|JG&X$6}OOg~_NpU)PCGmmPafq&s^ zFYQ+JFaRB9ioc4aIG73*XJK6W?!H}s4>f;nLDzKyjLemf+_(R+=9}1g0A~r3Z#l70 zwf8P}-H)XI&2Ne#n5t?o>#cwVcai|Y^uCZmPJ*WqRd1TdTv3HPH~jKQm9MO9L>8F9 zxbh7-vt?Q*=dLL1wOz5RHX!c)|GoQ^mqR+ccoBv>uy^zq#rm3YLA-qn5Tw1&qL@Tx zw7jTzU01IGdT914+<=CjPjvR)s78pC1iAgaM=Q#0Q|#bi)xR0#+tqsT`-gZLmk291 z$<0q`<+~B*H3p9PjEp>wNvBz=Xa9$I4`((*vrTAEn=L?XL@SF+6+A;_5HztZ-Zq`4``lVW7oa9H%EyvR^q8GGFlYd}hS z_rP&7KwEbW;)~#>#=NMXZYlrrX?2q=_PE!H-}6=)oqTd95#VC=RwwvA!FcNiH_~kQ zYN40M+)zzA;St1%BID;G-MO)=+xa~XI)<>Ybjn8L`*df-ODAv5+5Vr6v2B1swL`>OW_y}5ORR->EZo^MoW)VA3o!^v?Z}vujmV?iLl-<5R9&jl~F46x? zJ+z`2kVcbHB^FN@aEbh8yg!+&Pw^v@HLaGU&SccGbm%=|y{<$aiVKtuG0?F(EyeDT z@}SP=2Ci&Y-h`4W)Zsv2(eoKam6<+gb1#mtlJ{9|_0Q~44y;IFZ?MW6&2;s}_3}yb zdULyssFxjd6TA6-q6im0z%PF}{7Zwp|Ne($VgjBwlPFU-8tA1t+4F-DjC@pFhQJj& z+ptzzKA!(Fl{DY1jGl#?zp5~?b$}5HqUHsEd zK6$Ml{PMg0gPn62eEM2B{>sr{BO2c1xQjopO)Q@iQB6aeolL|Neri9WM;l`Txgr~- zJJaGEyLEFH3oWZs%qr=n%5Mc3zK#2|g&_nQ2tZn>`_OkTOzABaLn`^Q9-_q@_L{s3 zl#f?py3B_T`W6&|b}snAfBnq3$0~gTsMNKE;K?^9t5y}!!f#<^I4?^(pg!S42p`=N zpZIPlhR};!5k|Z7;d;%tF%ZOlyJ^m0g4QRq;-atXllT-IdxEPcFChBuOm?yo&z?Tg zI_GzmwSKemqMBh?s~>vwg&~%;DqX0~nds128{dBhW|Q!SnN}LYOiWB*pYq1?c3ci4 zbp2v8cQFxng3Sgq<7e46o>014yu|+xSeW8q87XAaqNSPgAgVb!sPe1c*CE+Ua@yuG z*UT&mGOMi(Q-Gs5oj9Nk_t}_$ zKvo+qXK&~g`lB`@kaowh5mN>ROnA&-RC?Fhz`Pj+W50vE9M|PKgIP61MsmhG% zsY0U>E!l25V&cc>;ZMZCN(Dq#eCNTqT?#A3D6vO{tKt=3L6Qd66bNRhau&#u^mxmf z|Ki=gx2Gqze^H6fGzJ|~1_Rs-K$k@C)c;EG292~-RSg&Z&#JDI2(9X&*WuL+lvFMR zHuPLM<+F)czs6vxKyJ043RO80ez?leH>YIEUnZYScQcsmM7_(KBP+EXPfos+T0_p_ z)=Lj9cgA?=pur@AXmPjEJhiX#H*dY^1<`bS(a~eI)BKC>C$O=I2IdVebGRA!zEj#L z&V<{9Fikn_Ja}$2)COv=b05STMlNL(mhR^yMgl*$z2LJ}!XRC}8oHIwbKM+$w!bUo z#>=eTp=);D(uZ|DKYGr4D&nB|iuA9Tw%H3>eVx0)Qt)ZEtq!ear#HfF_~U{P&R89t zXH4C%dNZ(y?*Fvhez_HkdvWSzx@guYPn4c)_VMlA{HG?Cx@t$|7NT?wDOLN42|<+R z4d13#$jN_Gs%75aCgz}9N|iG@G$1~UOs*Ou7k!`j6`U=ooI+$L59=x}GFs}Gz3Mt) zb!nV`p=~IoC4NeihHw*w81NqY1w{%#$yR=zLbQ+=fAh1Tm|x6Ku4rH@sUwe+CDNZl zpNJW@R@7pP09-bt7ap%LL8++kkjxKl2JGd2c3^@ z!$G2O2AWr;SPA*L+9NN+MpH&p$k!Fn@}ZX23a)E@;h4aBS4)~!c6?j;LKtuAWuiAY z;9&b&8;zl{4rgqPUH{ol`zGy`d%&$a`Er!=P@noUBp^sHAazD)(JM$dHD)5!iRe1v zS$q;3oU6ny40$!K2a2^(nLey~3}HCA^@Lfu!-&~G7Uz@mt9NIf@?Oq(rqx5zv_&1! zoGjjL(6Ty5GsXo+%*QTo)`?ucj_f+)l00kIVHDQdy>dH+%whla!DRI=Q+(8fpQHRt z{eG>`%-__HSc$0)38Ko_O4L8ec6KeYC4Qq*nJ;+P|SEF}H1y$&;>?R(42 z5yvH}s-lf$%eM}K9S>*awLNzz5!oi|gZLlZUHkE=0U1#F1yVE%^!;E)bC5G~ zzp1a2Zj)bCMX~Y1w2)Y%bw{TjDz)xWK^zsHA^-n9l&Q<)YBdfU#l9-Mqs=+|WRx4i z@#!Qq8h?vn$!#1Y<;oQ;NaaZxbFNMz@0hlg%MDg`&a!6O{0gqp4?W~-7~DbP{+QP# z1iIBu$0}a2H)ptC`vdlulZ;+kE$3G=KOCDVVq=?c8aGIM-3VW!K>+=}Ds(~N@!;?I zT7Lj#2*?R`eO1PbqzARR}b-oJ?b>CiP z&^%V~?lcQ@o zqO~eB>4q`DI5a>eAaLoh*tsJUr)?o{ZM!X1YE@9I0+kRcB2tGGsmPdAQR5ty zw!o_p6)^Aur7|N)juaIUNl;L<3=t90iXbw{l!y#LrZ5QzLLdwQ0wy6q=5ziXsNeVf z*SRiQS|G~F-g~WQ-OqE*4Uo5c@C4AzIcIKlXL#=^230)Pd4gQOf1{r1{BY+J!oJuA zlBljYWJxO?Z=HfuqcX*$48vNNr{Yp@JaWtQWf~a>h(3!evFyw69~ZPPt!-4rVTBA) z6!JGbsg{%p33)l==!graKkG#rrQ-YvO)W%{2p^#5;Q}UC!vT?mTtUSmz@^1^(%kGO zD;D%a*14)V<+_afT|$By1|JXge8m#RApU-ijm?hBW_M5^iUfo$%h90 zpQYfEH;`@^U7kDKPyr8}?=0wSrB2Ik&{y$UvLX}zU!DmgP^lx2W`v%JnF++?aPkG` z3q5-7t>_5aHvXp82`tdqisSy^J?BI`jTgvj@g#Q77Rs6c_7iu0PM*ZYxQvYOPVbX( zZO?1%)9x5IWGb9z4yBqbwN^=1onpC2cwT@#<5Khx(J-XhK=KsUp!6s9i;7Sjt4_L1R6(__-+n zOXds8Mm$$?t8q0h)DZMYHCu6jb!rMyywuX|r`^`;*wY}uDBfi1qK@=+S)jB^C~Q$j ziEQEq-C^^S&_*jVJNO!XhBIWUyB=b0qwIAErB>%kZBab1F?u zV=4(;*sm#R##0Ab{!%!Yx+oiIW;O)g8!Im3Zz#$4=f&v&kcn(UJGYLo|sXe&k= zS_5bup!#M^a>3y^tuB;uiZM2OnqRy#pIdz?nK{V1zHa%XsUTU?&OQj#$W;eu!lYN?Wf^821(*}m)JYvE6--8gO9mL(Ep9y8PqU= zq_HH~-)Xsd4>`?cVz9ehWLbiXDPmMQQ-*Hr{wLq`7wBf1(L#EfL%&U9+}o^A z_wWBSTcH(lwR}#gho3Lmn(L?C4_>gT?(i9p?%O%5q|Or1j4A*Slv!l5)8?5|6gxK7@lX}mL>?2*h}t?`TE8@$asVH^in9``%f+>w zPs)3vix?#fQlZgH6XrrKIORj8H@*)KK0;QYhxtYsbv^WFsn{J@4~8)56-*R_5|Og& zL6lwF-6e~0xrhE9>b~r&dceOe$P!C?f5flS%<9-5?L5abAgRh- zoIKDek-{fFs66X`RVPM01H~<|TdP*#17CU#`qKW(6Y`2y=hokX6I8G{MyeBa(>kj0 zwciI>#q8ZBTI1Fx@doo<*Vkaahc-f098Cx0m)8z{-ZI`3?7Q9%V6~eIGUeey^kBvI zD1huWo&873(bP_xyXib*WIUeK}Fq zm4G`Z_R(Sn%(X@1Spkj7hAlBg&vHw4bl$@qI=D89_P|b8(v~4hZf2bB3k9U!B?}Xl z@5!E$U80Az;YJs3$db;aK?U=-T;F(^x9$)NMHX~pr)m_n}DWaWA3*yvS{ zKOzi5Of0|LVty|ab#L9>99mLILarj~^7H=ubrzHr3uagckDb}p&&*+;!3~zdYg6>x z8p^G=gwM*?hV0zrm3)+Zi6Sm4reil_$(D4QSl4>2$*CTFzCY)9LIIwDb~aJ5w?f|ePec_Dg?5}Vco8RWh@dmH4YTF*v$hj4H4<^fN2xeA&t zQYbt3la7BJ+rXrscXD4H1Ma}cwEkI~=ye;C(*6v5NnDWiEJ7JMd`v+#IZLj+HdtQc z5Tmy%4@kQOst!qbdO=>&Su00~&%7Y~^V4VfSX(pfY2NWSbm>Cg{JD$+Df7b`FA)dc?clgz@Y1cxQ6@PR8hv>6%SnA3Bk}lThr85dcGzE zaeCQ<|1-U{sesnlH{%^iAY-IJGNCfjnGv&X_FEY50?STXw*HrBW8Cp?N0{WiVPwbJZrC%QGpF2Grzak=j|8g;6XHq~-3bXszyMQcXpn@Z*1 zqJ&HmR!uduX}52vDSM;OW`ezDJwat;N@>GO_WWt09Zk<0`74&v*Z3x;)|qXs;AN$T z&UM_AHSFyxf7`R+6))3iU;&g7mKk;IoykBOo?C%T2^B1WTLjL;)-#m%^Ksupq-kKP z42r|^ft*n0@i&y4Rv_;=yH3@w@*65`~3y#=mphSst*flsT!jG#63sX-1xD}Kd~pIbSfeWJgkwN*XiF{ zttrD6&tx^+Cd8=W+SUJX84ar|t>%=S1)FJ5WuR0;TdUr&pfYV-A)L6I9gZoyQH2-^ zq$+eS1z`+(c8c2wsC_BrVH%hP8&ouKw83VB0(A2?SLr@RO`Q|@;K>0#8$T2sKpD;_ zr_cw(MeUS}@X)^G274&7bvK`$#>GvRzPROC+Tyy&tMHR*G8k?+)EcdK=FS4DFhHE> z?2?{x_X@n(m;26g?No(lS`nor(h%O2fi+fa?(e-uj_l=Ucl=Jj%&X~PM$BbBN6C7l zbMj<;zlGoUI31V2y_^>x?VxVf?Z5`nn)ITEOk#9G8?4QeXM3}&=ty*`0>y!w+P%@RxxU7nl=y1OE)SHD6Ts9eULv@_gb^vf@E3tRMJN=T*Qn z)Mzj~$5(Mg!T!AeY(S_ef{-{2A69GL6;?nmRA1U9(C!<-sN0O8;pnmQ!nxSy2Yge%RJ*8N z6;3=HM|h{ToGi9@V7?hUgJMuGJ9a6iRHM<&|nzSzEf=n9%o%c6>_l z$FG6OcD;5r;l^9z@F@2_8YaLE9K~Kq6R`$Fo-;pM568dq$q-96_CJk{!PVYG#B+*~!8@tpK4Ct*I(>J$kgkRsDAC1lM)suSO2IS&p7cQa<=Ms?S zWqL05V94?EVJ#ouJ<6EM%!0ZIWMyoxWtiC4ucqJ%9;bV`ufWkYoMdo0!?)`;DR;^Z z4>6E)1EOb6ppW1KpezJ#i1)<>P_iAod5mVEi!CV8K&qVc-I(!e&LY0*<4+>WL>80Nv+Ut& zZ+}xEIgH$w_$m&=IK8nijM)&+7IZ?%Q_oas{(uo(*5He`RgAV1HoC8HFy`wL&j!4S+JLs@|Kg?sdv0Le1I=`` zo_Hepu!ca@hz;*m!Q+kl>&Cef{|V5cCR9O=;m8cAXoW!M;l^q70-{3got~wJJ2czm zE{PvJBTbo%(Udl${#Py|9@I_PUo@hGyqU|00<-Z&K|&qYuW_gVDMAgOGnTVpbshK5 z)?CGivNZe-US1k$Xxa1nXxt|^RW^ZF_4t-Bjv7$C4oxkoYrj}>+tdFO)OXIp9!$HU zk{u4uhN)^V!4qa^cX9PKJz{_I_=~P2e@_>qh&WTU$Lwt_LVuPbFJToO+bBLCkG)ZD zBYOEPckCQ0Xzuy*+^}h<4R`XZ-l?l0!(PS@1sy9y_qVrRrH?#Q-kIph6!{2#mPc(g z%-HNbl*wOt@We5h%Emx7?XqX)v_AB!E&xSOWvpE&;-p2i4T!Y`7kkQ_?Tn9(`JBO@ zxL{;)RBcEeHQH!xRMU!fFTIq};;u>t%Xv!nQLZE&vl*ZBYbC-DjKWNmTg~RMZ#mRU zX{pv@q>Zb_p7u+>Vc5=qLvc2p+MTcXm0BV$3+kGAj)?0wtL_P&FEWXISoKbQKFu=E zzXi}T5pZG$uHWl^yBr9kEFqIYq#Z z(UaCGELa)4B=|wD|czW1e%Q^0Z8`FD6Z5&FlKe8f zD29uwtk?sVYc=-pf8k;#tAO-3)LL1nDNH%5FDxf$&bD#443idIj^=y7XSvtLxtbcM zc-A*4cCin!-ZBryY36=kOsS*lnlzf>Cv_AhcN82oOj6!yI6JAJJxMuVL zz5*|tRx^f8S8`ST#ZGY9h;Da6EXDZJj&|^Oo|X;NluN>ICEH^?_vxHwlzZx-4ZW(_ z=%U;0CBYk_LQv|WNvC^yWOdw52LPHHXuWcn`hj1$Bb?06kTv+2iaQ5sxL><>P=H=cgmbh(BhR4EgB}bU7cSD3eQ} zrvq!P-LCX@K9Sv<*vAu&JRvCKbQvXz3AY?wJ+7T(eNRVOFY_&X`GPapFjI^$uaNI` zt=|`VnT%}ozI|zXm*F-ux$Q1wlece7=oqx;PKi;<{n@(G{jE$ZRXe4PmY-p> zd>KCwA&CrQuX>yo3%M-#1%hU?@>uJi&tHoQq2qu8$om=IPCV(S!IE+Xph1W~sGQjt)s}V4R(yLn`qln!S-x>L%#rtlbMNEKCz;~>(~rY_^*XZTzQ+zVrNm<|7wjsN zgW(uTO^#0HO51~deO;yZVuuKDl9u|gQGmH|sIkXOvN_H9>_fX$>&IFzo;R(0NLy{( zP-jXbD~7}};+JoL#0vl7M&7V$Klx54CeGxi4fGmN-29x$H5YGaDaM8`>#@=Nln?aSo?e~fad!#%))u4;Pww{0t9)Z)@;lsu~U@QPE^Wnjh-yL!fqW9NA-xk+vnHfrEhk5gOr;?aOJ-7a7rRTO07YGW~(sbsH$ z8iV;#D)T1JX^#KyFznC~vNfr0t=)R_A)|E5f3)R#V}f7UN8@(r8wPlp992+OTzCW2 z;3JV)mqoYNqm5^Iet7asd|;f;$`-2S_I2E4wzYvWT6Nhp087U3V6!MVfRi=M+H83l zSrliqAE!&6eyXKw$j)sx1mxnObQIBkE++bTM3D*m0ORqC+8mGMD5wh`%%Q?MNmjWTqYOSuuf!i#^w6OS&cde4t)tyUgt+ntkRkdTaEzfP=2O(qI z#UX$PYc|i_)^9hi1%3=y%XNSu)S$vjg%BD4UbAl3Zqrao)?4P_f}ps6XWG)h3#J^X zQ5tL6HB!<3UWiMWABU)ni!T^;P=t%5$2_^u@O`*!&Pj16&510`=QZK+8M1S-Hn4Gl zZA@&+Pe!%$J52Q8Tx7SjYzqn!wqJFZ+v>Bm2+D?|C85 z-z?=8b57ag)t1GC!#JeW|Hide-RzlIJg(%;V;C9{Yqu8xJR6V70JjluuYcWU!VEia zr80U>J)+7CJNHJ5zm~bH_o~6l+DapRv!Dw$P<{AydfNR~hBcY5%PnCV!#2!*icIL?%#+6+0Z6F;gzSCgeAj3rijS)vU4ldtAe2b#2WEhwi}#Xo&%6ZXp8TiEYraD=6*naTPj%8_XdP4 zuU0|Y=7Wm{TwtTc@h_hv0w5dKOuc^jdPW*cg}}K7t0{G$4{?q3)-)-LBN%5E3veZ{ zdI#qniG_>McY>$kVJS*c)^r)(Q~?gbnZR6LO^i1mTec{`iC%+|DOq>iye7UF3dc8TUnc{nIZSNP)9c$mTRC!w?B75#$s({M+?h>+$~rt@a^J)jM#;!wqk^`hu#a&-1Tb;#HGZ^H2lyaoPHxdr^d-?9-(7Ov*RVDJ(|T&F zewF`ozXuYoxD#g7YHwpZ{tbc?7YXd&KTZG~_dHJZ3=E|;mbX%IPi67v#g_^-3s(&e zNT?ZH^sUq=<1ypYMCfg7B_g~$_iQCiQOBTG$s4cCzEF^SYJP-gQ? zfI&|D%&A_7cg>R!DZz{m`6ca=AXX}qpp$*h9BkjvuAO4wnUoa;f1D7BvMvcg(dhUN z>;1lv2wC7QqK~GYt{Tt@2rW2(BFjpEv*!01po!&-0)vjD|2ut#^=6u)FSRPzp)niy zoe2~r8Bq|Yi;`qhYS$H@E!nao17qN?zpS;|cW!UPlggmk+as~>PTJWMzpyJZnSUCe zxgEnL%8Bo+u%l^g8gNFBh+?~W@d0spHhp8Pvsdtppk&~Na1bSi!X=$g>}HL^8~;hPG@4<9@LF{e^DLRb}8h2^W)Kt9fA@oKk){qiQws*RS9)jBsT<}uV!zLLJ zc-5G^FMt6%DuBfmHQp|ZsxK`gQql(PY&ln-NlJBNg%BkGjC{ zExcxR12hag;(2MtqN!rfM%IPT3UB2ozB4ZSHedpfe$q?#$j&x3xaG6mIVxBkeRf@= z_^s#8dvl&dhHtzcOw%PsH9pO94={sI+8;W5G~j90j+f8u&DFX>fLP9&{x`tabga5= zOHl9Cp-dn}+ayMl;%+TG%>td{I<(<8s6*hm>(wWoNfw18y4@C7GW!NiAquXv)feAB z5NoNnS)myDCGd+O%e|L&T>3gR-5PYQ+L>A=HCd|_&$4n}oT{4qk#3Y+Ueq4*r*&vX zZBX=$l!e6Owcr|EtrC4zUg9c`u4AJ z-eD@ItzJI%gSj<;=)K%`@Sw_wwXn6ihtX%0t7XN$=oor8>ll(V4Q!WmQF^R_Z)Z3O zJ@#CTN;Rlsuk+hc{`+4UmQox*1+pNL6gkfOHt zzw%RXR+^#(chUGEZtF%sCxH2ix~ZyN)-MxCwR^7ei;KS`pdw~>Xbbo&Ub6Pu?I))XuWYfj(9(^ySjokrj&C&V``SPbtVR@( ziT;!0XU)^f)RcZ{DbYd6I5xmohg9PSQug8om6yV*0c_rd-}K=a76d6C-YV`_8Zj8 zG*gEu+F|Zb)X+B8TeN#@nt@U^>B22H=)J1PEj!Pq&Zo0H3leCu-cF2Y)BH2wS(%H= zU)=}#itpX`5N~iIy0VeyvI)x+7oOElV~X5o=`_^X%{=sxjbH|`G-LMLoEh^s>>klB#JHtDaG@`qn@p1N9&a?!MO7+_F>Q%?sJcB>w{+ z!?cUT7>Ah@w2JONxC6nh!yR+vi4NC4X|4c4w*ck4d?6=fHBIlP-)1Kdy5`T+()*_@3konIr#I zxks+T27q%bK;>?06!l0`zyv?x>)Ky9Erk4t6YClWk!~rD+z#Sj;iv_5z6K8%*;|K77giT$$@%}$S6uC=8u_5uC?N<}(QZpqL-}vW*gm9T%d_W>e1<{eHYd;>hX%Zn zAUD$m^zv7&b-O~J0kQ(R#z~uu2_w&(x#_n8-C-?5fZ}all9br9M`0IWPQYbih-V|Z z-Y-KN@}cWOgJOYek!4q)%>uX;=1lSt|4!{Z&-I#7&Wx%!P6g^sN$EHK#zyrqPJb@n zOf-~rnN5lQ{Q2=Zisiy{da(Pd1`DH=MiZ=J~* zJxrdd=@%ETz#{m75Zv4Fol~bDIjwkx(Pc`qG9)?a>a55Ks}uEKog$neNlMZ3T6TSw z+>{|}cy5YS_vL(&foK-G&Z|oFk$`MY7kWTDGau^W$a5%DQ_iukh!ig1(t+V8@Ul>? zLzCVEHbaf;eo@RUhr=p5$SPf5lPojJq{+b$|D)*uFPpkLY&9_(lhnQZi!CNj{(tqs_Hpb~N`3 zN*T}I7)40cUoJlFn$~!7>e#6JMDJsQ`w>NA^r&-e{>b}i)<~x|cI5`;Ng66rVd%%J z0n-J`pc5{#KlJuQ^8EPz@RW(<8}22?75nH z;Lc4K)CI@=4tQjw1^V5{JMfp&>gD2Fsl?6&msIHcZ=3_Cc)Lk;EnZO2bj5%@WI z95@fpd?%YY0%kn?RxOAPB9*e$pZcrImFeECTsym=2U-qq`EWXne8Yg#a^|a~;b*LNTT zs~@ix)jlo)iFU>^-c555^*}96l{M_^J&EO~g(^YQeW5z)DrgTKYY33p#qQv$8+7-2 z9mja#S)j@?ujgavXSVn zE5_t0;vzn0PD9LH)Oam7ZF!0Q8*1rI6u;6Sa2oJ`)0QxbdmR2KBa}l*9jQtiJ9kRuv;~ALMixs6(v`0ihi#7tl=65o z&s5u&6UK|;E>o^TQ^q=VLPaJV+!XM4kXH4^gn^@{AquUv&hQXffzl_73({Y;x|5HF z6m6rn;%$$NJ&j3%W3|UzL8y>dL|SRKve-fg}Ed3i(j)508?(J^={I;*<>%* zJ&P}{_0ILJ!bg=3UM+R;)dEK_;MD}0S6d}`0*>L7&`pL%UC{}w&TWRlg?ag}{53y2 z!GJX2a&PI+Yz@EC+3dLj5MSY!>(sS_nL4L>T-+|ILi17I1|ki?$neNEL;^)!*Rx{3 z8tI^3)dirpy%b03`x?-W)|WLg2YT)bGfQ9l}II_`4? zM^}LNf&J7LtU6IP;ogEK8;?(rE!xNAEb*wSQzr-x9Iq>dLfk)$x%Vq#YAcS5Q^(~a#d`rqsf!&|JtYFon=ER>O9O|H8K9gBSpt@lCC(SK z_*@1Dr^%)IH(zKegb0}fu?Xm;j53X1dNEXN94&<)iSTIJZHIhV>HN8lSqeV+*O>`? z3gZkb9Ka}wmD;3lnVzUqTnJgn5u!c6VWXy|r3?}uJu*MwoH4*^i%CO=LWs7|A12AB zD;(iZGMOcW_#o%dvgEe)SW52~@depPI2gY)*g=3WX|9%C*{-;yy|1;5ubOF+oAO6g z?bY`dP+DP#Aw6h8xBI*kWCP77YQ<0$wJx-(pvvc;vjDI+tR+|SM3XKj$bZ7$S~hU^ zO_I~^)1=wEJWzOd{wbcT$MR6GP09ixd8U-UOI%#Q+8O{nCy$b3T_%=^r_gSKMjLC( zcF&mMmEx`f@Ccpgn zidXnXl8m;@?U33%1*B+9s)p^ELY`-iEMiVw5#xez+2|hs9`tk1PbewJWkJKK(gitU zDBZ6<1r#h?N5u3~ITlzvWZ;QI6dktW+>86cq=ATjSrhHD&^|)5k`h?K=r#725tAP^ zEr(^#-bJL9(1PRgPihWNe!8ik3TiGsuWCa-0Q%>@>5^--HJdRQ@<$3a{~$Ol+B?QM znq9Sun+dzHaDQTFRkf;OwZCd!rzvn}`+VSS<~Fv^#tJ~DO*$gkt23LlsyjCX1Z>_C z`sb{a(Xfwan)S($x)7g0ZR|Kmqsm$h0h$^_RHM)`Ytai68G;eYH+IEz^ zS>@c?poym#W_fa7h|a`0tq-gdI;wnKuT{U`c|W80vXBc4mgC8wLlJzH^(nsRdTVUH zPJA?A9dBo?Hsdb)lB=JWHnNrx#Ljtf@kj2o0@{JGt?7TqFbtH%vbkfLJQ+6 zZRX+wEwGeC&%=&W_SlVt2NW1saypl5M;ad*Kmj52ah`Ey8gO{0z~~mId+0LrRx4TG zhk52-bYhr&QRZm;3XQuF1M|6%8e+A&;vG)rVS0?nf)9i$LB2p(CR5f48S zKiSH<3mKPzR|goNKkli$OPU49h0?~!XgGQo(;#^042L@PuSSWh{cFiF&|}#6VWRAv z^v4^n08y1U2qS8Bw@Ox0v=?oa|E@&c3wxIc^KQdFmFL*%7FNR}jmokZMi6=s3@a#m z;cv#&9rosnf3zj~G)?(LlO4$nMrOA^8nqm!O}&8qRloJv##9HqyDlyOn74vQArDIi+#8Ce`L7=ujH?s$t)i zOj+wX<6RCF#Fu3w_R{vyRCK7U5f{TDKNJoL0eK+-=}$t1U@=ret&sOfND7)whlC|3 zXciJ@5VY<~c>;x?lHuf2q5lHCGPPK1{_{!#Q6L||vpkT$`5?woGbZGJFPr86`@c_tx-1>E7Dadk$Zu0YBa?~f=7NtywYSVkvQ!@ z4o+JzAVlBhsyVuOok+uwmbiE|!)`xfO7=>|>j0FyI(9)yV7=lw@1q$SW>DaUr$31q;t}B)~%Fah5^%ek!$*t9o#WobYiERrt zYMn78hujU{E_!K(HQT&l-ijePl{9h3QukEqtCd7DyzscT5lKK2i~ndPxW5a^7`gg@KZnM z4(vK%V1GmZ^ROP+)nQrN{X;7r;CCZw;IUP6tKJ1KTcMmkqj?7)Epl@b35G=<<^vt% zTaicT)J8wXovP4l`5vEef+?N(nw>2B-Vv6~ge!i~PWWq7D+8;XDZI+L-T26JEXPgy-|CFEf4D`+ z;o0anJ{{bUVz=-tX0>8dz{{T|T<+;rH0*0I8KI}o(}O_PE6sJH?P5}#pRH@Gdmm@W z(_UQH2gVay+1r`H4|N}TO()wz{!gv`!Q*zJhfQNXk;e0dTML;Z{N7?Zxs|A@X0|ZI zvYixk!`|czDa&fkJcHb8rrYHd(#q zX={~ox{w(RhayYYZlsb06_|r*pL@{jM|_TG@6xEY+s*e=uPS&fQRaofDcso`CPlYn z=FV%16gB*6;S^Y!j20G`VWufn?r*zq4^p?l^2?W60zJj<=#n*csc&C`50h+*Y>T0l z>OeXCW?*Q~I3(MsxP?4uvXT-9QC9;bjx4hYx+7cz_|KC5uZV8!KQ?ZU7}vLF`fXDd z4=X%Zb-6>FA*($B@i^DmbYUxwee;DCLLBC+#J4>kKi2KzGj>gF<$s)8A272?;&MZO zq5nq8b;+jmnYWKd+atR=Cv99hbj--cUhi`Ph<(@CkFGq8xt4+Xd4AZh=F+O}y`uI4 zB6;+sd@a_7=j?(5d|%HU>J4kzPDNJ^PV8&H)87sXG2CXI&I;;V*AuDJ9#fl=kCuu( zwLAXc0mptgMZ81TGjO3*#S+A0h~z%>aEwLX*mF^Vvx>dA#RYTS!jR*-Lv&#+F85~; zYwKVK>hg_HLd88R#WB{;aI?V?nlIBo<8Z3%@?D6>)>I(%T&0rqiJ_U~ID~JHOI3o3LK3OD0Rr11G0%B;96fSnlp$xpj&Sb=u3rC| znY64~-VK5;&8dt-j3Oz{-SB&+qG3Sh#d|~G=k7EYLCY*BxchB@FZ9oPyu0?Ui(_ZW zQ=0@UPhZDQO^6@Xj6lV|pdVw34p^LW!HV4-E^NY3L1b~~psA{8Saq9UeejR3XHGsL z8f_#`kExzL-}BF&eL9X7?4xKqy2#Ll<~~gl6(|kK{(J|Yc>0bE6goqg#C*JstY&eL zZL(XXvTd>+q)E#i;wxEzm)?CrM^G!_7lIlc3{?4?KHN4%D<$+Eq`71FU93UH4l=BG zBndr~zQx+W+||@UWaAtm`b~3MwXl9y8JyuAoK6`hTKzHc9l1jlT=!n{-+R}oVap{^ z@aMMjA35^uJIKAyEz@AE$;>1PTcIEQr;K*uET8><#V6Dy;P; z{-J(x4eKpQhjy6U*BH++h)B-vLe8~cHy-YfHL@!z$Zx^R6hya1D1jtp8O#YKr39A8 zun0>}=`wu9B9aI6qJtxzgJ({<#AOXRx$6Nl~qAzo?Ta>kXN{ut__cNhu_$S7L(|>qp-}b?53g{pp|+cDBpJh&mo2 zZyX9S{^m@j!;uK4(UG~2hZ^3D)IEi+=kEx)UD)#4r^4r{Q&HVR%5~jwn$V#g%)O6K z-=KpuXUAS*J-hycs`>-i$qr6U*e&mCCbBYG&<`|O6$>D9S&9dA0jdY!S$muLZsdTN z2cgq#3+hf}Et9I)j-6eE4O&O&)JgbE!A!fnM$)G++2Yt;UNNm2Np5H%=7z2fzj0 z2PO2fJql@x=1`xO5?jB0Dy2`xE^$3kgBCYdk0vRy{wK2{6GHw5;Zux@twj(NEg0ZU z!wC+LZ-q9xj1dPzL4m0xF9$p%XOI>A>KGY5*zlm7gSKyQ;yGuevl?cU7|WN}20y6k zI7;Cf#|eActyrXueJPqDDdkw2F@ryf3%t1*&`;a}q@jTe0y{j}=zsPg&dBrHA;?rC zDwTh^J<^vMam33+-z=2)6{Ra2>Lukr^Z#21ax1$hhi1e`ry1ZcpO@#CW1NV z1qv5%cG#1F`0X*fsx#lU%!zzy2QeCXAdmNRQ_VA(3Rr;TA)k=7ql3J&BScNyQyIE< zr6ZCGV!G@(h~`N6nFvCAINzSo`9zAm-mw5PqLyf#)gYvQJ0I)%KiBmvmh19}^#xbZItT*2!}~8w69KF9^A6H~_79069xP z7I$qanN{r#BSO3x&p~l||4(8QY>81I&k8HriYoP74~3v8prdzB?F$;b*Y^nR_7muj z?9l->I{kgWY{+1)(}MFiEtj4-oU;pyN;M-BZ>mCGu6(CIarV7<>@9y;9DbwwXkzq) zGw1ZNBZ9fT$*ON$SA|Z*B$m?cWIMm_e&LpF0zg9A@EMT$ z8B)8BRGRHjl380y*vJz~a?oUd`-@(iz};!mN2B-j;>7)$S{#zNWdM(McW6E95-_ctaBkga^ zH-`qi^q9^9UaR{1V7$z=zYM?h)WGI$(tCxjeIU>$N6x;rSQJ|$9=79d%^-G7h#Ou1 z*;vJ>{cACoz(K_PAk>v$GjWxq)BDDMn~ee)slK3%>bJlC*=~o{loFLYkqbDAIea<8 zF&Hk)xy!Ocnz!BFz2P-7oIV2q7e1-vmWe$UDV=txs6Ks=99sCRw&7@iJ>e+-PC$f% z3mmSpr{+O3=`FUC)Q zISbN8H_M{ku85EuNLJSJt4_vFIBVOV=5+?zgXA;9s(|w#*TN9w1YGi%xQWMw_9r(+ zPe#;&=T2uu`q_JUQFl+KacvHz37SL)rd-*6{gICy-j!jlP?aF_k(aIT^b}1ycf)vD zZ{$@1TGx7==g+!iXgZz3v(pjZ&Lq6u86Z2^XpfQEV}sUs^E|b;&$zw~CX-~g_&;w$ z^@u$-Opl0rvu@;DG_{d2Y_I)iCS4Q^=hu?3C&j`ZE)2G)GP+0Azfm}vu#Gv8RF%&c zQot#YC6ARp7)K;J3pbT6pHbg`Lj)-`0h)gQV?$J|Hw&p+P(uIz7l1h~w@4N6i>81b z9{@Nv=siHNKT^0{A*Dx#j}q1A<ia73pf#UgMxao++QZtq%7!8wchPj4@Ws9k`|^Mz8HFh;=xlvkP?_Rq zy%l$Svr=T+zu%yL^^d+|s$5T9`-#)6M<{N=j10MKl_+cc%PF0$a1j-+?xJy2#WSk# zV{^%S8Hx`ol=-YuwWK`zwT^}ydPag`jxug@2jbu z@aM}Tqd-=n%2N4F?vwLDR%*a%)ttC249-%TS4Ux+IMG===Dpx)`ZI|LvJ>CBV#E0DBnNY&(1u|*@(qJhw0`*! zg}Nao=-ZlhmBp$qlX205uSP&9(BNp`8Roq_&yU4PI7-onqkruQ87H76+3y&;i!26` zIyk4Q8X44{)3H912Pz77!E@DRU8wtYSO$aQ?;M*aChZtC?gA<1m~q$B;R$08&;gwA zUm-*2(k)144*3i|A_AC$`_^8Wq<#ncP-c-2Tr({z*evYoEt6-EHQbC`;mxbG{og|U zk4A;Zf9+stEuT5Kz*nes%l92SI;q|K)OpvF0NZ~8XL#sD*RKzfYY$ButaqAvN1P3x zf!7-zMoMN*k+v7&`b*B?-_>czie$r&nP$=W(}Q_4BioJm(a>n6BLPCV4DPro-2 zZ|CWzDh}8d_E`2@Rw&ze-1S$LJGiPS`^DknJq7E$Rvl1R$+i6wvx$aKbtkEWd>u&( zCctvc&lSQKvLw= zr!gq4;2k{8W|qo`_BcNd=TN^ReTsqqOUZZqFt$#dMkQOU6RjwcjO*mye!4BBdEWaK z8u7mP-BFWJBL5VY$DuZ-hK%E+43!+?%}T+M$tMJ}rD-Z#bCpTGIm8h{nSB%s^$o76 z>H)@mgn$m-f<*Bds%OwLh=b$O$%9SYLT79gEaTooL`21-!L2EU3Au6WLM`O$;=K1f zp%Roya5hw`%D=pOK7sk+U}IXT*-j-x{h{N^Kr8HD^$yO%th*hrBEaTIurE3xc>Z}O zcN{83Ei*rq_Pd|D1^MIWSyqbTF4BSoJ@8PSQvIfK16SlEIvy8(ERt5r33!yt<6Ntz z*;F9gklv1y@Fibj6RuHnu^Mqr|6Z7ptXWRFbB3fPxPNOZd>9r(K1u{;Nf)_br-HZmE1;C3SndTlOUt81v^`S z_Gr!Cc^vLCXYz3T*PrMXwq4;S#BVv<+OGKF-o}btjo+7A9`R-F_b9>~A3pCGrJNld zxqMXVhEKn<$q5b?f))0TamO~8$!Vb$VwW{YJVrS5v;lfYA?iKDrh&nH$0$oOR~E*S zUA5)g7YbwNBVj9~w2qBlIFJ2V?#yB%0o7vupqw!#bi+0>NaE%U#%vW>&O(7BA6k!a zDX>fmCn4U_4un4gEeLnR=E_uc3w#lZtB~O23=o;63WTNW<$+@S|6DF&61ey`wQszT z4Q)&)yit~UhRb@06RP1%@V@^4FF*F#I^xEao~=9%FVMTX@@v-KD%A-09V;=KHLOlj zlD8mx#~C1GZ@gQGq39fWIp&#OoLA~F!ufbc&hn#K1OilDO3xh*-T|nRQRruGLzO`e zRiILNuB?x2J>1jEF08b6d9 z)AL-WG?KPr0vlzq&}<6q4VCPueFAmCBuE0u5vABg%s%+=|NN$o5>JA6tvg(Rc^^1{ zdxYUDOK0;JFU)jKN%ZNr*kc|14bXbXVr3n1>Ee))T)O3_qGn_ix|_hGqb_Blljh1a z|Fdx)cmd7T!}tywE__0H)QU$F(xc3qq_vQ$UQ`9sfzwSGY}CoUu{+6t9~{p@#v(c6 z)M+b0Mt>0eh4MeDk>J;yQIKp}CDje=9<1S#nDvOO;^1_(wg$}oI& zu&b24PFIzFBgHy;eMsvssISPJ?2{c&{yk|}4frMmSYa)vh{AzWNIh8x2*I>|Y--`W#+`HL5aCj?iZ z1MiL&&j(9qB3@J}(f-L+#MRXx@Q44`-j_x-k#+5gLxZA3M8yG!w5_6`5otwa2!e`= zGOBRd?C*WQ zANT&aKki!hTd&(iLaI)vIMxHj95AnDkMAL)+#pBnRioe-Lo5hi zX1vBDWIo{dn#|igv^4M&KydiL?MPhg*YWu4nLD4YlWNn%du@gow;Rv5e zKWOEpAOSy!EFOiu4Wp8~i7Wz1TvW|4;|BIaILu8DoFgHaO{8AO{1t8|(+x<5jWAVvbgS2OIkUKEQN6{lJkL5$g% zqI6%D4BwwSTFS={r}gpj--QQp!~yun#)D?))*Ii_zI@IOjA5y>>fCbx+viM0zh(-u9Za= zM@)_Tv>xU5(Nh?*B+LR%+=o6ds$?th{P1evy7LqI@{y1Y&0u;@?oI?Z_dQg*T~K&@ zd+8exvXDf;24l6D&svOynN>qocCp>~=Ec}TNZD)0RF$2QJ8Qnc2`TMWDV?Tn<^4I# zkzcLyetRX}esLV`_*F-Ki$ZRC+%CmHX~PlR;X7ERp0fE-9Ee7Uk#IdRDrYSp*GE!S zQ@sz}htk!DRNubKLh=#eHy%ktQ@VCdjlDD&i0NTxG}^KpM3h}jqT)p#xrfj<;vs*w zA6kgzW#VEz5s%m?V@{G#ZuDG;AlYAeCk&QSNK9O#BYh^|cJEfd;l!)sLR`S}2W;3awrp@vEhe z+ZHleiGDwN4)1MoK+_V8t@15fTo@dv0!u`d;gb~nnlQM+**II+XZBnFmV1agLDS1*j zNLeW&chPImHOft{a%y$FDXREbHi~}>4WQ{@R;ry;T#%~N`9eXg5ZdXXXk?VNV-EB$ zv962~fQHA0P`cJ*V8>|5dMUXfq)=j_`gRc{*{8ILr=gqjfFA_Rx9)?`B%E`te;Y%Q zhWVoT((2_`dvNkP9+lp~R2+I7E<1!%^qep_Wl{Wo*X;e<^LfaEk>G5bjB=(_ZC=kD zoJArBo_mh4*UGg~Cs}UBD&h{uW-c=57oPoOi8dt5i`K|rrWKT|)vxmEDUu{;0ox6D zok=+3fHbzkfCXr_*lQs}8k#8MW-u8e(?2g81xSY;5N;BbJp2aBh%Pzw$; zev8K^wz0VZB2zh7#eJj(Gny-arEE`WmTZ#{MhJ}+asjEI0||&jt{~n6*a4`y9Yevs z^o624Ne^F)Yu7qaSA4$R34|iyvIF3FdZ__M0MEk!9gt`BX^lmHib_z9kBNI-&<2II z)b5o>d-B%hrj`A|CA z#`aA%UB1JN4GJhma~pGhXkf|xTuWH%2y4VHIC$^I@T_sbVc4}^_mUeBMz^SIB`rk zb=Uwsqo~lLm*>6j0@n+~H!O>R29lM4JnuM>Wq_-4S|Z-|H?cbVD+}=m?&rNFODWmn zF>S*Z-!di*ir# z5$Ja$@taL_Ayot3{Rc=yjMoA+HbTDz?`ypwh!x-{YmFXn5ggVH3Cunrb^ABb_NGW{ zfKg@%cuE~^+tS$iJ`Y%nVtII~>vHpkt!F^0X?m!OXjI&;uon|}MbLI{`fKd#AGIxl zr5HR<_L4d=!VU7e^Pp0z-*W3XE<-D6yNsAhU1~s@~PYq*!6bBR{0X_gJH}5?hKk zgngr+5_nWW82><&b`(rc`n2IF`H4^^gUlt#uo4Av1uX+OJ(EM$D?Y=Iwc8lnfS($w z%r}mpd$P$*UKg2*zB|BS1>I)?!>2d;x1xXsKPmniZbnfG#-bzrBIsA+Z}M zihO$Wqn*k#s5MC|f_!7e?F{i)@CKHx?pu&Jnb-Sql3@5kY4F~b{!ls-ZIDjDtg+w# z>D7)T>Mj|G?n{Ir%upyIiJdI%YY)e|%`&FH+v-nRp=cTmUy_l%(PWGG zB92OIb*gEuMwk#5N-ytiv_3 zvkcayXrdpUFK%sEBk4;b!#iv~gWQZ5+>a$optT?Y!sG>GkZ&o93c%^919T zx2Au_oYy}fGGlKkA=5_rb1`2ji=6296uLo_JD}^d60CL1)@ZaU9HYoSiByPNNgK zj$It0kV730{G$c`)zqiUghO6WIpV1Mh#HT1VFi-v%My&m=Sz8|SWY(qO<9EwAJK90 zDq%}eZv~)1NF6b}oWW&lRIx;Rq(yyLrow0a4z>vjys%}^O~E3RAWu-Z(v`h*d)BG; z1TL$k4?pS23MKgnERX$H&H&744h>dndz*qHW_9NHsy{r9|HvqYNLyT~2uf>bnU2xF zw}^2p4E=R7$~X=QiS<`|pkrjYa|{q}Vg)S?*YP0S^fBjGL&b;fO@*3N852p{&cA6yb7j~()aLqDCIyA9M^ zP=iiOl#)s{=z|UrB+mxGGzJN!zm`$DLPiylS-iJW@6Hd#bs;e(0P`WULnZYeg6lj+ z`Q#jSEk3FTS_{E~DZ}s>`huN+#vU=m7?)pzorLn?$<7lOA-u0rM-UE7K@RyatCQFd zd4px3v5#0k1bAsm^C9Mo8I|}-d=7#Z{#(kdgc*h#dAtZBQ5zB@PaANQD%h_ju~s)f zvo+|w05YKQczBfU`!SRb`EaM>k85=oy>LV=V#%S4q5kc7Gve^p$R(f4s@UX8^s*l3 zZT#KROB$lFp7xC4(0k=no~xHtDWSo8mS?(P)w;y^vg6mQx5!_fWcq5*)TY08pfR(+ zwr!}Ws99mOlpKYlQHbKZS)rZXvDQMb`4oL=zA9dN_&rY#;z(1h)T!>K3~k`FsTFKm z5>I->oY4|;k2sO@omJ+96qa1W?vC{G3KXIDf#3e=Uy`v>ZRl5gC1lR2!OB)+1|ZJ) zm$+YiC})g9ZxSk3|Ek_H3M*5k(&EA$hi)$=w!7Mo%n=SIOTB>g4UNACLI6`>1JeuzRvRm9D3F9QJpn-s5?Kge>tZh$WNu~jCWN>F>)zUDCH1_N2@Nvnvn|c; z(e=T2lAc1s_MHx3otTVyJY<0dr9<;hOzN1z9et*^2*X-%oGIPP3DGM!e^;SYE~Vh5 zs7mO0rrci}q)JLzLT+faE#B_&U#V*@_sYf3=IgmVU@2qc(L5vIf~Ms2Bk%ZbL28d9 z^I<75fpi+{TE2rt^*0X}ZT8W*OhCES;X$_%=3}HU|0EA?2>*jm-e-m9!xbjn^>|V8 zaqwimVENiR@V%6-Z5z$)=xXB|QJWF+8>>J_c{AJ-upc z@3#JTz5;R-cGi5AZ$TexnK&3P>Vxvfh}9gU)=>hk#R)*TI?csAXIf}8Sz@Anml%{L zv_w1%p}jXL79a3dpPoq~Ap}^!;-(%l7xz}ORN;tOZv@s(2?6HACNT||MGVH*m9p(i z-^C>2ugue)UGud8ZUClG%s`c8a)}vu{Tjj2o9h#S^y26!=TsUIk8+gcxsL|6#&3!du zdx}Y5827K!v77LZ`Li~0IOoL)J-@X{^*RF{=;&HZY7}#%2YzEz#_Qu9W^D;!%GRSk zRaOj3I5d^T0+9KK1kSax2<-QTEn}0p49Lql6qFaQV4?k(05fE+Mcs362&{p3Ja!kP z>m`7&5NsfX>*G^|tnS!uoMJy3EM3Nb_;_9|h`a^FVZE8D)^;lu~1op5-H)OKx^wsA=q@>Kn5E80?px_XN5{k>=ZN*?&gNeMlW}8Z>3%t9VAlR zhh=0*{u*jx{WkH|zln_Sx5(O5M~t*{y4X$kjLr?Aeu-K#p(7z=@s_I845v>wPQt$9 zp23|(+>hENZ6nyU=1O! zCaHBkn^!VWXjT0b7Rw7KlNriW@_;7()KJ1UJ9xs(!BP>*7GuC@I*K@-nMds;)?dm_ zQ6`&^p*&(2yuNf0q}}0Q#|I?28;88-6=0HwV3T{u^|beMR!>TEfRxxzJ%D-CmwoCa zrHy+sdYUR3X229hacHM(^VvnpkH)c}-{wPI@3M7HrG3?I_+3V8oTJyHw%otUKyzzc zEWO6~`e(X0^O~2bv_70)OS)QnxViQ!oxVHz-DAAK1h;YlGL|6>UG4K)#3Z2>K zVRasmu`7Q=J!1lDNg!h$WaqKk7-A5P@B~}%6H|`TRBjJKnMoC^2P1C#Ly0fx!G>Gz zfwTCF4xq&w%e}voZ)*>M@%E3fmH1>IK7pN|u2r#{noYwFp+VCqP9RoReQTqmKcVnr z!|@Qoe-e&3{!pGU%6JP6qq*`H9tDf)q6eYXbVdd`7< z|16I13(u-5T9E!R2|KgkIV`(P)W~w-|CzO7qmf3OtHF}wzvi2hP9)0po#x!>$`lcQj4pt&NeY{IMI(I?W?4Z`ZDD3U2G{7 zQb|Pa!TzqF!pCt77i+X18i5sYbxC{!}aO_T|4EnW&N1EB&0C zFu;11x)6@XTelw`YGZeksL`A5U^@*Xut;LOzY%&^dx%kl8G>;&C0@DLa~HpuM0|My zoWG51|LBFy;(%e()u(QV-o4o#m09Lawz~KjBzjY#9{I+1|4@5lH{nw&x)U!j z?SfF=wr&=0+bMdbT-8K$-Kq!jkS|3-!WlmT&K|1`Y1LX_TAf+4G27~+$^7t?T3QOr zO0bosc-F2b$=TL)^~1T7vu-R1dw7k?|3%(#@JR|MVLoT`s98&i|2EOlkt7Uhk&O9h zgx#g|IeO}i^P_J2ohsI*oHqF-ReTP=w`D7DF&09Xh8~g@C5-qXVA7KxLOaRe1oOou zd^V}6>n@yg3Ht$`b42eF#~o;rgCaLNsAnc1kD>OfT%6|62~dvA7bdd$@jlN=`heL2 z1}Rpw0u%PrH;hjH>ehwzL14nu15mjeWJfqhU_`OP9(Y*$D8CX<=+HM_Yy^C$b5(rz9bf|gc5Cls|A6jPgRs%^3UN#CK@OQX zkT1m(cdsE|=^V}wv4kPsduz7veo*Nne+v`+G(LE>Y>TyA!RDM%^g$}!q?O(q2_%)V zO2dU}vu#w&5{$1mggnp6E_c^E60jqg)*4&%Jc~8)YEeyw4D7V4qt|=5+29G+SbQ&M z)$odTL3qn%blq=4TDBcn-<2+SeIbV|fqFq8!dVJ^W>%hn#eAPjXmH z$$J#JC1W6P6}Dd<7Tv;g7mTJ8KcZqZ6szftV{;)9zIfVNZ7B>m;9E~SzLv+Uf8+w? z2t=ns-Gs`f?0(Rj;!*tldk8PWILh28lDK%fG7O0=gA9I#71LFEB0${?$O)}av&yt= zO%ar2xfay@S66M}y|j4y?a3t29*R}%4K*7fgW+u_trh@N5ZV*a{M6H@HU`VX_=WVG z0{G}i=mLtOY*njm>==VpMHSO{@&wjOv?^Dix$;C6T_QX?KlE)(NJF5W0-GB+0Nr1x zcJ0{RKaFRFy%oOOr{Z$S>w{I4{;4?&X-!9}msugZAA~UaSeHcPZ2Qk_N z?xJ0VOr8*o<$d)Af;5U|VI8Z0Ll8pQHOYQ7k9iJ}ei;KAa`-UryE zI&M(ZqsO7#ID3tkk^S>Qv0I31=0IC6epk5djZU{)5L(zKVYwz^y|!0N1$=8=$ z#~_`8*y4TZ--#^W&KirG#$xKQq|p?DR&I#FCLVuBCsa!Vt-Y-XxEFD>z(#Z1K%lUkfu($mlw$Q2S2Mdb-W*IZ&s7J9>6s&Cv-)w=CcVs!6y`?ju z3I@pcFe=ItV2rEcP$pOqkylAXJ)6B(L8TZw3#tWvQeltL6EGI&k4a<3ig;Gr(O}#l zu3@iek15@R*<+t!09Ah|vu;o1vdZ8Y30z)$G{O=r9jGJoNIz32Q!Fx}%PJH2(hoex zJCWTvFP1CogB5e9dad5{s8*xu`1A+-8NQMQdu8k8=u;k}4px#z5{_8x>mdeW!@S?2 zaD{4*n;b7j=3u6Zwen%&psHPv9ln0+g^yVF9fdXm$^aKxGP^U=#j!f#QDe=hd~5x) zpVq(2XPZ6e%+UPpna=pyl(^@<4QAt(-8^c49>f3YKG9vVGp%F!_Cl&q@q2JD zW76hN0ry}PI)y?BJS77I^D~hIOD&^CKO>8&_(J*9S8+*OM=~$Hj9&^9!7kXva2xWa zhk$CTu_Qx6U%!X9_|Lew&nA*`e^VO}6Z0(fSWp9T~Q$uf?Zl%`+a=Ymz>hPnpLG zJKsPaZ;Kf%M9I?p(PfYfnuU2zA?GBpjw4A#kjJ)^jKe=;Lg80_@XvcvhK8B}moQg= zK2AvyFF*HIt6iO27oj4SQ&tPp~{&#`vZcoHl$(9O_*E=l$YLT5qDzNFY8 zmJ9@m9$VM+=2t+a!W=qqM<+H&JKX<*2Jr>@a^Y)GQ7ra%5C!|*nM^}KjL1fKZv7a* zu0Rd^tdSDC`j^BLBoRDP1bnf<8F`)ikLCO)F?+AQ5ZVA8ZP*tzv#sioOs+3`(c!rK zQHe-ySe-}$waSCYgt&s#$|SwO+J|!rh@-^%)Ad|25>+Y|=d>U=PNc@nX@Zn6di%L+m@AI9l1K|rTI_A7lG#ra6AfC&_<;8Ap@+WpXT9qhN8pIBDV z`C&J8{awSUe+*l|zD)sbM6r)|DR4wuM-Z}AxXCr%3SledB1P*ccq$+-sEj(L&)|I{r5+~qU@AE@6>T|Xcnziq>LuBh;h)5JXv-SxfyG@C2+lMLbR`etSH>n)~19Fd&*aN#g z=knmGf?Ie{_(uP$A=mn(V#x4siA14JOuT_!Q1owMw*C6T)kAac1KKCGJnsBm8zz0-wH`|L z@WH%VwsalNzrQ1MR7TNFTc;B0=#$+mtYznh&==0;OgBdFBW=rNUjP_+`8gy)Fd0~8 z$6f_JsNgBY==K#-U|b;?1I@yKNrOZfsX<8=fe{i)z!I=}Y=lt!NtK6@!LltpXh;!^ z5-|p-5OXjgCdCk#C`vMBJ=eylT(Nc~WXP0#p<~X7=TDL6O0+xw@~CDX8&)WO!2OuS zzb`|$Jm4zZu-7rFD^~t2RPx~-`P!3?f>A|F$2B}O(9etG@$+I^wyUZw*BSL>g!2t)kKGN;?k9w7JI{O54cm z5-D_TC{6VnpUqPV(7^Q|6(4+w{e)8oQUpAcr(Z0MAP7N&7-hL^#4vIJ1D)&7|s6 zv_+8UdN;h${nT^#2i&+D-7w)PpRs|_h>Zu-x`RaL>fC*G=pVo5fsAk|sUJGjzySSa zfXXhR9!L@O>?9?t<;>-{Y18!M_UzbxfD>c-bP*I_5dlX65nEmcV@H);6bGvPN}-L* zSpwD~-*`wVqtwX5QchI{DCsFhEBPkF0Na3}$-;nM#F39#sc#EzCbqZsi4k<6A_&mJ z@coWxw)xu(Pqma8L!QDRc!%|N*WFpS4Y|*^_Skn=&AgCJc9bp2vRc%Cs!UUu?gvOA zKG8^0eF-4EjNQu_J|zsf0nquIa0y3B^A<_ZpsJCkXM1o9?3)1_;>Z+f>LWHs9{aIT z6+}UBYp1eoTl|kl?@y`3doFH}-b#9Y6&J)^9aINu>c)>H!>Yd=onoO(`NgS%Q!e12 z@uIIAn;K_Kbn}!T{XezLLG-D-a1>t*b%}3vKGVb>TfSyOfxW$|dT*3bSVHBBGM-%o*E!>|*vR%5V3-ca|04XaWkMW0c~rOW~B*A#LiF85eXP*X<6823W`k zXmroC@n=%J5hRGhs%(nnLt;F-T-Y1ndk0x>o`R07fMgMal#^X%aWZ4t!h&BOS?*yd z8I*N4N@&Hfo5a1nDM8Vu^ra-3ctRR1B6p;4c~!sugxHG#|KJ_e+ua3r57iGIL{CWf zznq_;%>9!u{~5EpruHuZw#u-QUCAD@0Sfaksm`0xzt|+Zf)o#wM_-j1cSpeD2pj`5 zDq5WWCc&Ffi2=m;N@)-~GDhsA9OH?%z>MQtOvYP~bV`-pka63Sx(n-#xrG5bQYgX)`+B)y=|&A_I4$2C3S=u|P4Lbv_!C zrU0rP2U~{<#zWB8!mE4}f%u@LdJ-VO0S>O)vE?sW)I1`Th4txXVS^CAC6CgEafzry z9oC0MLJ?R56baqBvtsXPBYHz_%5+6Ylj4}-iYn!*o7VxLU%1LSlRub^)7E zKv4KjOjiu`A5@O0Bk)}qZoI|`m9j#YDhTpYfjFx~OR@vMW7@O~mj1(IW8t6U=U5iI zx=H5kaJOUlg5g3(Z$?{jUNSew87$ZX*5}S&Gu$eCetXmoEJ69)mlS=4{8vC45&gkH z{4HGxU*C=Md_fHOPwDkHE6~YZT3l zA1vWZ;woXAmJdnZ14+>j*!~@NVf^JkQ=K^0RhMOjX9p|+65V!j%M=}IJFw?)!4V~5 zQ(?NyZ4m?~)pYal^hc7=;a~2ir#bz(pKtoa$crf^^x->LzY;1L0gpZ_T2ySs(6IL_ z38l?Ju@@+XW*010*s|g0OI$myoOI7pe&GhdD4b3+z}_HeDdJb%R%0h^`FwvAF7#5m zl%vZf-|g$+_-JJssT5fk8#{XM^+)&B!*Q_!5@)`ut>m)Wa@OG<-~J+AN_|PJpe0tv za|eEBd9NsA80=*`b3S$Vc2!@if~BqlW#U+Rb#smj{CQk3<}Te=3=C_qgUbn!tyRfrgb6$tx z2LCLm*d)*Aepo5TdAEQ4FaE`AYF(6DB(acTfO-p@C)xBzka^Slb>o!)o+Mn5^LY6{ zO;lF-$)UYss0+G?eNxy0S_bg~cBQanJVE*9yCqFbuqVES2GPgzgvaXrF7e8bl_wIq zz)fFsyz&vPw}n-J^lW?Uq18X*J?>8a|f2_5wWoxXc*wIz64+Ag&8#q91^`hM!yNA7CB^ekH`fQaG z3{GadfGluu)`PMgkPo1vOiHxn?N{?&u_n4%>kWMlO?^1|Xb+;VAE{A&r+8{I<~R5D zOYM0`F_2KsfuY**jC^mjD0dy^mAh+_0;mK`;-TSTJt?t@uY`Ub@p9`1z&uV-G^Gn$ zbH4LTveyjo>@~k%-vuvLS%`Bnd-^PD-9p-Ds-3}A%F^J5(o-hYAuS?#7ou=m@e4WS zgGnl*g|bmKZ~yErkSmmP+Ub1NqIzswh=kDgb~Ry0J#iJ}>Gp1=V#am<`q%Jegt!I^ z{m57d86|rlh19jDlw3$nPw#x~N=Ci+SfM1eW6qcX`pfn10PZh&PH3)TH(wkCxgfVH z2he9g^E+De5MO!p3EgH=&we>roU@Xo9uCShIRIS)0l|o!az;RQ7w10oA~Ah3x9|m% zX<9_d>USnusP$GIeI#|@^R9p3)e-O4n?QZzTr4T)BPE*lyl4wMxWPh9`we3j7qAVb zavclL@#|{)^d&Z1{Ao7UozBbR_Jn0I-o7iT+s96!{0=cqz6ZsG=BfH}pPt_oUNDE- zS!6mvjK=u812}zc7V6~h5bbxCBcAn#lq+4MCuZWYF-KHmmOR~ek{()o_SdrQux}C; zvH=B^;iM)8v9(mmr&U%hjgGuKIaR*|`k@8%gBV@BUKs=e$lC}YVbfF;yQM96Rmb1+ zejCeC)|2`eo~Bb%Aqv^Qj}`!P2;`gb4E~qbY*HL##5jzULM=0XRZ?{ONb*8bAEP7T z=<+(EcQW+EuW%H4S=$hHduRVg2}w!~K!7lFzLi4{nTyxYq!&KJg)O{#mdnYB%BdBMC%W@kkp` z{Rri@%Bop;@PKDM43ff0DcIlSc4lQgam$uIyxbhu`xmhHNi{bPAfQOY>b?f&)}h}5Px1>Nh+8~$)WJK zYt%H57oYo{lbAB1I?|SawVl5Q>O?6MzC#gU_89<+iWO>?Rr(-p4P=p){|(wJG6@Z? z-wzIiHpf0SY@Wmkd7_mAv*NHLTgTk?y;>@2OruHJ>rIuQiw7|?+nqIupmoGP)OJFH zf0UDq8&9^{g@Q_RVP`KSgvpA;w?01;DB_>q)>8KM8Bpbpseb*_u`o7A~#}0mTE2Fhh1GDce zSAVc1B6+Jzgg)gS(dkWvW`o4Kdv%PDow>BQVr1S+eB!P2H@AF2s_@jd#$H4|9WDEl z?oQrO>Kt1E*32OLu5$2MeWSa7>*FPxOrc#90;OqFXZWkOBs(^AI? zVHv_wc#<35^6r`4jR?R~BySjx<#qL2?nBDbF> z75_ZxH8^Rq#EII4UYSS~y{F5bD7hJR(^p;d+6^@6TH+N=ydwMYW#t4fC6L=b<9YQf zZRJIQl>Ojh1N~l`I&8z_RSg8M|N3lF>65XqdPglYOJ9PG$$%Q?iQXuytSL!LX^%hMe1RXDMQi$c-y;*Mzj%sVe&(*Cjj;-N>KCM%TW{mQ zQ0#@``*y5(2$E>RMa^|+YncrL@JS9L*JGBD9~s``5|q)b_d)=2EhimYGrme0wDjTY zDSuDC_TlZ<)WHQgO=K5k-ldcWWP)E2)=mQa(r8$jq;G-C_b#zl&iG7#2=G(-&v&T? z*taql#-7vY{-t05BEQbucj{Qyon870$G$Ht$cAm)5jUTro24OTbQZ>i_xLqKEww9^ zsmIe*o{n7SoKsoQa{Nf7#Qh``pK_BGw=y@KATPc>#br2r%bzsTnEjcFou$6Y>GpjZ z9YpOOSVC)KU*9*$wfVaDtlTH|;`NK7;GPWHBD}r-^Byg`>VbN6!|};So^!~jLp$`9 z;j4TY{F!GG_cOk_jIG;tkwn4zAmIV@lh+G6%33pyq^BynB&SG~)h^4y*4>T9vOHKl zX_7?#?0&1LQg39=2uN`4;Fn^~aSG2Pg7zn&#f$512<`&5<|+HQTTcm1WZ1uDYcMli z;{>!=1Nn#=8O6=(TLsKeW4wWuE9nV~-jN+Rnqmu; zX>RONDZ3lj8|J9=S_SjO++83Fet_-V6+!&A_r{kT$%mbA9c%D z9bayA4_@#3J>AQBonQCv4l9oCky`d_?Tuj-`$ICWoxOTOP^ecW_EF z45j8jrmw#JW^F*+ZU2}1(1Lwo;pcZgeST}DzdR-Q!Yb&0{!Jqe0C;E`4)TJBXi0kgKl4R`uQdd|6O9%IyiMrc)CidW< zEQENXf!=K1Vk|dEmk{Qb&#prWI!x&P%DuyNZ50k7=SwN~U@qIk`+whxjNMl0ojKL+ zD`+DE@Alntd+G|W-LOs1rU8eJ8`+&~L6Yn4+sR#kk2TcNWEeDWX<%oWS#01<<7$6bhY)*o1AjiApZdnZ zNVhP6>5Bsh1+mXI}hJSTsaQC|A zhN*8eKt7P9PI?Al7iuWs(#=@e=7}>tgKuaQy-;g_-eyq^aB+ipgR^akmp(aqDjruLlQ%L-Hg7t!d~729N!tv^xpr^ zC4y2*9Ih{H6G*AoFW17!PCp~L=B$oaHq2FxL;a&Fub%JTPb<*XX5V_|Zc-g6@3*K* zAHzY)8%q(ygNRw*>c8LCAVZLD;gktIe0jpRdpps8!f)tn#b;$h(Us3|bnYc5hmxnP zf}U>kz(s!jTmN9ERBv4fVoBwVxqiC~+gnuF{kPlV$%omO?a1EZC0rX(L#80!l=FG#*Z*>l-W@@HYB?oet zA>+2AX3+)0Rx&+hm>zdqc-l3V_=G#nwq&(~-!943d4lO%$RQ8%?7E7&L(&}0C-H*t zUv>VXKLH_+y;sFlwf$^pdMcpVVh&5oV?Q-B21s$*?M+ED%tjHjfRFB!L{_3O)ik8O z<+Ej5wRGH>tkFwnrV5**z#q627JccKmO|L$#kXK(>7unHS+K$T?L*uHu#Bg z+<^j1&1bj1Jh-E9&MLpL?KcNkag({dajJ089v}i-+~M4bJx82#noKxX++>CP1#8YF z;m>8PPwO6_{=dBTu5Mm?o$~Rhg$3NaQsuoer;(x1i43gDbUhX`Vs`NY=@TZ*=%coL zvXs{JbUJnzx8yMuOJXs)_IKm8i1xC&$W1!}1cfhEw1T)%>i*RgO7_pkWoBB!U9?Mul_d+OVerg)`G zS>H2SeMy#pu3cX9;ji(4{~<>uyk=sW{v2gr|L6aIdf-1j@c&s4NKU9Br0H+JAE;Is SA=ALWJv$wCWZNA5^M3&|!}fUq diff --git a/next-gen-gui/src/app/api-utils.ts b/next-gen-gui/src/app/api-utils.ts deleted file mode 100644 index a75e216ea..000000000 --- a/next-gen-gui/src/app/api-utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { environment } from '../environments/environment' - -export const deviceID = (): String => { - // keep consistent with ShortIDStringLength in lib/protocol/deviceid.go - return environment.production ? globalThis.metadata['deviceIDShort'] : '1234567'; -} - -export const apiURL: String = '/' -export const apiRetry: number = 3; \ No newline at end of file diff --git a/next-gen-gui/src/app/app-routing.module.ts b/next-gen-gui/src/app/app-routing.module.ts deleted file mode 100644 index 06c734263..000000000 --- a/next-gen-gui/src/app/app-routing.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; - - -const routes: Routes = []; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule { } diff --git a/next-gen-gui/src/app/app.component.html b/next-gen-gui/src/app/app.component.html deleted file mode 100644 index d176fda39..000000000 --- a/next-gen-gui/src/app/app.component.html +++ /dev/null @@ -1,9 +0,0 @@ -