From fb5b9c445b15ba7ab878ad0d71e18a730c2ae726 Mon Sep 17 00:00:00 2001 From: ta264 Date: Mon, 14 Oct 2019 21:21:00 +0100 Subject: [PATCH] New: Switch to ASPNetCore Kestrel and SignalR --- azure-pipelines.yml | 10 +- frontend/src/Components/SignalRConnector.js | 187 ++++------ .../src/Settings/General/GeneralSettings.js | 3 +- frontend/src/Settings/General/HostSettings.js | 44 ++- package.json | 2 +- .../Mono/System.Diagnostics.Tracing.dll | Bin 0 -> 5120 bytes .../Mono/System.Globalization.Extensions.dll | Bin 0 -> 6144 bytes .../Mono/System.Numerics.Vectors.dll | Bin 0 -> 163464 bytes ...ime.InteropServices.RuntimeInformation.dll | Bin 0 -> 7168 bytes .../Mono/System.Text.Encoding.CodePages.dll | Bin 0 -> 5120 bytes .../Mono/System.Threading.Overlapped.dll | Bin 0 -> 4608 bytes src/Libraries/Mono/readme.txt | 3 + src/MonoFacades.targets | 52 +++ src/NzbDrone.Api/Config/HostConfigModule.cs | 2 +- src/NzbDrone.Api/Config/HostConfigResource.cs | 6 +- src/NzbDrone.Api/System/SystemModule.cs | 1 + .../Configuration/ConfigFileProvider.cs | 12 +- src/NzbDrone.Core/Radarr.Core.csproj | 1 + src/NzbDrone.Host.Test/ContainerFixture.cs | 6 + src/NzbDrone.Host.Test/RouterTest.cs | 2 +- .../AccessControl/NetshProvider.cs | 39 --- src/NzbDrone.Host/AccessControl/SslAdapter.cs | 86 ----- src/NzbDrone.Host/AccessControl/UrlAcl.cs | 12 - .../AccessControl/UrlAclAdapter.cs | 237 ------------- src/NzbDrone.Host/ApplicationServer.cs | 63 ++-- .../{Owin => }/IHostController.cs | 4 +- src/NzbDrone.Host/IRemoteAccessAdapter.cs | 7 + src/NzbDrone.Host/MainAppContainerBuilder.cs | 11 +- .../Owin/MiddleWare/IOwinMiddleWare.cs | 10 - .../MiddleWare/NzbDroneVersionMiddleWare.cs | 45 --- .../Owin/MiddleWare/SignalRMiddleWare.cs | 34 -- src/NzbDrone.Host/Owin/NlogTextWriter.cs | 77 ----- src/NzbDrone.Host/Owin/OwinHostController.cs | 51 --- src/NzbDrone.Host/Owin/OwinServiceProvider.cs | 92 ----- .../Owin/OwinTraceOutputFactory.cs | 16 - src/NzbDrone.Host/Owin/PortInUseException.cs | 12 - src/NzbDrone.Host/Radarr.Host.csproj | 7 +- src/NzbDrone.Host/Router.cs | 7 +- .../AccessControl/RemoteAccessAdapter.cs | 16 +- .../Middleware/IAspNetCoreMiddleware.cs | 10 + .../Middleware/NancyMiddleware.cs} | 20 +- .../WebHost/Middleware/SignalRMiddleware.cs | 71 ++++ .../WebHost/WebHostController.cs | 140 ++++++++ .../ApiTests/RootFolderFixture.cs | 4 +- .../IntegrationTestBase.cs | 64 ++-- .../Radarr.Integration.Test.csproj | 4 +- .../IBroadcastSignalRMessage.cs | 10 + src/NzbDrone.SignalR/MessageHub.cs | 71 ++++ .../NoOpPerformanceCounter.cs | 52 --- .../NzbDronePersistentConnection.cs | 115 ------- src/NzbDrone.SignalR/Radarr.SignalR.csproj | 10 +- .../RadarrPerformanceCounterManager.cs | 58 ---- .../SignalRContractResolver.cs | 28 -- src/NzbDrone.SignalR/SignalRJsonSerializer.cs | 23 -- .../SignalrDependencyResolver.cs | 45 --- src/Radarr.Api.V2/Config/HostConfigModule.cs | 2 +- .../Config/HostConfigResource.cs | 6 +- yarn.lock | 325 +++++++++++++++++- 58 files changed, 917 insertions(+), 1298 deletions(-) create mode 100644 src/Libraries/Mono/System.Diagnostics.Tracing.dll create mode 100644 src/Libraries/Mono/System.Globalization.Extensions.dll create mode 100755 src/Libraries/Mono/System.Numerics.Vectors.dll create mode 100644 src/Libraries/Mono/System.Runtime.InteropServices.RuntimeInformation.dll create mode 100644 src/Libraries/Mono/System.Text.Encoding.CodePages.dll create mode 100644 src/Libraries/Mono/System.Threading.Overlapped.dll create mode 100644 src/Libraries/Mono/readme.txt create mode 100644 src/MonoFacades.targets delete mode 100644 src/NzbDrone.Host/AccessControl/NetshProvider.cs delete mode 100644 src/NzbDrone.Host/AccessControl/SslAdapter.cs delete mode 100644 src/NzbDrone.Host/AccessControl/UrlAcl.cs delete mode 100644 src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs rename src/NzbDrone.Host/{Owin => }/IHostController.cs (77%) create mode 100644 src/NzbDrone.Host/IRemoteAccessAdapter.cs delete mode 100644 src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs delete mode 100644 src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs delete mode 100644 src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs delete mode 100644 src/NzbDrone.Host/Owin/NlogTextWriter.cs delete mode 100644 src/NzbDrone.Host/Owin/OwinHostController.cs delete mode 100644 src/NzbDrone.Host/Owin/OwinServiceProvider.cs delete mode 100644 src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs delete mode 100644 src/NzbDrone.Host/Owin/PortInUseException.cs rename src/NzbDrone.Host/{ => WebHost}/AccessControl/RemoteAccessAdapter.cs (65%) create mode 100644 src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs rename src/NzbDrone.Host/{Owin/MiddleWare/NancyMiddleWare.cs => WebHost/Middleware/NancyMiddleware.cs} (56%) create mode 100644 src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs create mode 100644 src/NzbDrone.Host/WebHost/WebHostController.cs create mode 100644 src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs create mode 100644 src/NzbDrone.SignalR/MessageHub.cs delete mode 100644 src/NzbDrone.SignalR/NoOpPerformanceCounter.cs delete mode 100644 src/NzbDrone.SignalR/NzbDronePersistentConnection.cs delete mode 100644 src/NzbDrone.SignalR/RadarrPerformanceCounterManager.cs delete mode 100644 src/NzbDrone.SignalR/SignalRContractResolver.cs delete mode 100644 src/NzbDrone.SignalR/SignalRJsonSerializer.cs delete mode 100644 src/NzbDrone.SignalR/SignalrDependencyResolver.cs diff --git a/azure-pipelines.yml b/azure-pipelines.yml index acbd7e8e7..ab83fb511 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,7 +17,7 @@ variables: macOsTar: 'Radarr.$(buildName).osx.tar.gz' linuxTar: 'Radarr.$(buildName).linux.tar.gz' sentryOrg: 'radarr' - dotnetVersion: '2.2.401' + dotnetVersion: '2.2.x' trigger: branches: @@ -56,7 +56,7 @@ stages: - checkout: self submodules: true fetchDepth: 1 - - task: DotNetCoreInstaller@0 + - task: UseDotNet@2 displayName: 'Install .net core 2.2' inputs: version: $(dotnetVersion) @@ -256,7 +256,7 @@ stages: steps: - checkout: none - - task: DotNetCoreInstaller@0 + - task: UseDotNet@2 displayName: 'Install .net core 2.2' inputs: version: $(dotnetVersion) @@ -332,7 +332,7 @@ stages: sudo ln -s /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/libsqlite3.so displayName: Fix sqlite condition: and(succeeded(), eq(variables['osName'], 'Linux')) - - task: DotNetCoreInstaller@0 + - task: UseDotNet@2 displayName: 'Install .net core 2.2' inputs: version: $(dotnetVersion) @@ -401,7 +401,7 @@ stages: sudo ln -s /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/libsqlite3.so displayName: Fix sqlite condition: and(succeeded(), eq(variables['osName'], 'Linux')) - - task: DotNetCoreInstaller@0 + - task: UseDotNet@2 displayName: 'Install .net core 2.2' inputs: version: $(dotnetVersion) diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 66d1d3cf6..2b05253e1 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -1,5 +1,4 @@ -import $ from 'jquery'; -import 'signalr'; +import * as signalR from '@microsoft/signalr/dist/browser/signalr.js'; import PropTypes from 'prop-types'; import { Component } from 'react'; import { connect } from 'react-redux'; @@ -15,29 +14,6 @@ import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchTags, fetchTagDetails } from 'Store/Actions/tagActions'; -function getState(status) { - switch (status) { - case 0: - return 'connecting'; - case 1: - return 'connected'; - case 2: - return 'reconnecting'; - case 4: - return 'disconnected'; - default: - throw new Error(`invalid status ${status}`); - } -} - -function isAppDisconnected(disconnectedTime) { - if (!disconnectedTime) { - return false; - } - - return Math.floor(new Date().getTime() / 1000) - disconnectedTime > 180; -} - function getHandlerName(name) { name = titleCase(name); name = name.replace('/', ''); @@ -86,58 +62,42 @@ class SignalRConnector extends Component { constructor(props, context) { super(props, context); - this.signalRconnectionOptions = { transport: ['webSockets', 'serverSentEvents', 'longPolling'] }; - this.signalRconnection = null; - this.retryInterval = 1; - this.retryTimeoutId = null; - this.disconnectedTime = null; + this.connection = null; } componentDidMount() { - console.log('Starting signalR'); + console.log('[signalR] starting'); - const url = `${window.Radarr.urlBase}/signalr`; + const url = `${window.Radarr.urlBase}/signalr/messages`; - this.signalRconnection = $.connection(url, { apiKey: window.Radarr.apiKey }); + this.connection = new signalR.HubConnectionBuilder() + .withUrl(`${url}?access_token=${window.Radarr.apiKey}`) + .withAutomaticReconnect({ + nextRetryDelayInMilliseconds: (retryContext) => { + if (retryContext.elapsedMilliseconds > 180000) { + this.props.dispatchSetAppValue({ isDisconnected: true }); + } + return Math.min(retryContext.previousRetryCount, 10) * 1000; + } + }) + .build(); - this.signalRconnection.stateChanged(this.onStateChanged); - this.signalRconnection.received(this.onReceived); - this.signalRconnection.reconnecting(this.onReconnecting); - this.signalRconnection.disconnected(this.onDisconnected); + this.connection.onreconnecting(this.onReconnecting); + this.connection.onreconnected(this.onReconnected); + this.connection.onclose(this.onClose); - this.signalRconnection.start(this.signalRconnectionOptions); + this.connection.on('receiveMessage', this.onReceiveMessage); + + this.connection.start().then(this.onConnected); } componentWillUnmount() { - if (this.retryTimeoutId) { - this.retryTimeoutId = clearTimeout(this.retryTimeoutId); - } - - this.signalRconnection.stop(); - this.signalRconnection = null; + this.connection.stop(); + this.connection = null; } // // Control - - retryConnection = () => { - if (isAppDisconnected(this.disconnectedTime)) { - this.setState({ - isDisconnected: true - }); - } - - this.retryTimeoutId = setTimeout(() => { - if (!this.signalRconnection) { - console.error('signalR: Connection was disposed'); - return; - } - - this.signalRconnection.start(this.signalRconnectionOptions); - this.retryInterval = Math.min(this.retryInterval + 1, 10); - }, this.retryInterval * 1000); - } - handleMessage = (message) => { const { name, @@ -226,7 +186,7 @@ class SignalRConnector extends Component { } handleVersion = (body) => { - const version = body.Version; + const version = body.version; this.props.dispatchSetVersion({ version }); } @@ -250,80 +210,51 @@ class SignalRConnector extends Component { // // Listeners - onStateChanged = (change) => { - const state = getState(change.newState); - console.log(`signalR: ${state}`); + onConnected = () => { + console.debug('[signalR] connected'); - if (state === 'connected') { - // Clear disconnected time - this.disconnectedTime = null; - - const { - dispatchFetchCommands, - dispatchFetchMovies, - dispatchSetAppValue - } = this.props; - - // Repopulate the page (if a repopulator is set) to ensure things - // are in sync after reconnecting. - - if (this.props.isReconnecting || this.props.isDisconnected) { - dispatchFetchMovies(); - dispatchFetchCommands(); - repopulatePage(); - } - - dispatchSetAppValue({ - isConnected: true, - isReconnecting: false, - isDisconnected: false, - isRestarting: false - }); - - this.retryInterval = 5; - - if (this.retryTimeoutId) { - clearTimeout(this.retryTimeoutId); - } - } - } - - onReceived = (message) => { - console.debug('signalR: received', message.name, message.body); - - this.handleMessage(message); + this.props.dispatchSetAppValue({ + isConnected: true, + isReconnecting: false, + isDisconnected: false, + isRestarting: false + }); } onReconnecting = () => { - if (window.Radarr.unloading) { - return; - } - - if (!this.disconnectedTime) { - this.disconnectedTime = Math.floor(new Date().getTime() / 1000); - } - - this.props.dispatchSetAppValue({ - isReconnecting: true - }); + this.props.dispatchSetAppValue({ isReconnecting: true }); } - onDisconnected = () => { - if (window.Radarr.unloading) { - return; - } + onReconnected = () => { - if (!this.disconnectedTime) { - this.disconnectedTime = Math.floor(new Date().getTime() / 1000); - } + const { + dispatchFetchCommands, + dispatchFetchMovies, + dispatchSetAppValue + } = this.props; - this.props.dispatchSetAppValue({ - isConnected: false, - isReconnecting: true, - isDisconnected: isAppDisconnected(this.disconnectedTime) + dispatchSetAppValue({ + isConnected: true, + isReconnecting: false, + isDisconnected: false, + isRestarting: false }); - this.retryConnection(); + // Repopulate the page (if a repopulator is set) to ensure things + // are in sync after reconnecting. + dispatchFetchMovies(); + dispatchFetchCommands(); + repopulatePage(); + } + + onClose = () => { + console.debug('[signalR] connection closed'); + } + + onReceiveMessage = (message) => { + console.debug('[signalR] received', message.name, message.body); + + this.handleMessage(message); } // diff --git a/frontend/src/Settings/General/GeneralSettings.js b/frontend/src/Settings/General/GeneralSettings.js index 785b3d3e2..c6fc895e1 100644 --- a/frontend/src/Settings/General/GeneralSettings.js +++ b/frontend/src/Settings/General/GeneralSettings.js @@ -22,7 +22,8 @@ const requiresRestartKeys = [ 'urlBase', 'enableSsl', 'sslPort', - 'sslCertHash', + 'sslCertPath', + 'sslCertPassword', 'authenticationMethod', 'username', 'password', diff --git a/frontend/src/Settings/General/HostSettings.js b/frontend/src/Settings/General/HostSettings.js index 1f4bf6775..ac435cd1a 100644 --- a/frontend/src/Settings/General/HostSettings.js +++ b/frontend/src/Settings/General/HostSettings.js @@ -21,7 +21,8 @@ function HostSettings(props) { urlBase, enableSsl, sslPort, - sslCertHash, + sslCertPath, + sslCertPassword, launchBrowser } = settings; @@ -87,7 +88,7 @@ function HostSettings(props) { { - enableSsl.value ? + enableSsl.value && - : - null + } { - isWindows && enableSsl.value ? + enableSsl.value && - SSL Cert Hash + SSL Cert Path - : - null + } { - isWindows && mode !== 'service' ? + enableSsl.value && + + SSL Cert Password + + + + } + + { + isWindows && mode !== 'service' && Open browser on start @@ -138,8 +157,7 @@ function HostSettings(props) { onChange={onInputChange} {...launchBrowser} /> - : - null + } diff --git a/package.json b/package.json index 10a41739f..f3ad58e26 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@fortawesome/free-regular-svg-icons": "5.9.0", "@fortawesome/free-solid-svg-icons": "5.9.0", "@fortawesome/react-fontawesome": "0.1.4", + "@microsoft/signalr": "3.0.0", "@sentry/browser": "5.5.0", "@sentry/integrations": "5.5.0", "ansi-colors": "4.1.1", @@ -114,7 +115,6 @@ "require-nocache": "1.0.0", "reselect": "4.0.0", "run-sequence": "2.2.1", - "signalr": "2.4.1", "streamqueue": "1.1.2", "style-loader": "0.23.1", "stylelint": "10.1.0", diff --git a/src/Libraries/Mono/System.Diagnostics.Tracing.dll b/src/Libraries/Mono/System.Diagnostics.Tracing.dll new file mode 100644 index 0000000000000000000000000000000000000000..3c7819569f8352e5aaebb61ddb7db97d362743a2 GIT binary patch literal 5120 zcmeHL3v3)m8UEL=*m*c{A5rQy&IX6dF5z@(Ocbk0>&L~88#^{W$0?1Qdhc$0-ehn0 z*xmD^g5Zi)RR~h7I8;pv6{13FiyBB(N}+0TM1-hBkWy%cX)A&tU}&XcK~YHCgzul- zd#s%xRFF`m&Sn1nU-Qp5|NJ{UPS@R&R7^xA=$9`OJ&z~1CQ1K2nSi-+&5tVS*@`o3 zpI181tnC}t9W`xRNn1;)am_R>SB(kP&X}rhs*&!fnz9liv~1bpb$;lcZA6_)F&*tK zJ(Qj89DPDvqJ)VSLsRZ)hwAZE(VNhTR!F|c!_5lDFWUqt^xTT6=|OhofBB~`vI^}w z#O-FLiRi9e5LWnYkZ38`3w1;{Wp)3_Q_Vq_-sOZ6Dpk;Ie=whk|T|pZpZIrZG(hf;KE9vJX)g%o8_cVp*T6!7uMw$h!r8&?~ zNxD(e+o(zjQ7f$n-9fuRqvU|@0WF!}JQhh*jI4%c@F*j`O-O_g+K@r+Um&XcB; zLg`XKT8&tF&x=Kc(!PMy67bv=km7;)&IHCi8StD8*p>vOfq>K)h`A{cj&7L?19g}~mYvdE7&YUnUJ>EMZ9UCi3qwT2kd`sr zW<%GUycL;A>xK;6D-3N+as~G6N_uRhIPT^LW(}=Ydfe8N!v&_DmMK?yCFfLHGsp9W z-i+z$DZwZ(b4)jMx4;@5cU+MQMRW~0b6h>{g!*hPj&sf1u|Cb&?{Tfe_%I5?6FS8R z*sBi?C5)W8R}2{<&KdK@db_)AEG}dk{eU`fn8NnwvH%)FDDGPJ6{hjLc8Kv&%T731 z2Y;m@FX6(D3VTG43x~Y=!J1IS5M}0M-SufWzNMJwJa|#zy7F+BR=>C<-)v{c?6!3m zpyz45@!fK5e()~M)Q5!Q29(W6$q4o$pp>ZEWF5*E{=yS~d-GSubKO6_|GA$WI(F_+ zDp3`syjUfr6iihWi@EMC_}Z6F|88`};h!!V?XI7_werYcO5gs&?AqyXzcV|R80cI&b?Q6E>h6iHe@d)h zeet!e?mgD0&(9s$``zWIAKrTNO<%dDRjg7|(^CyycfT@KuJ|{j8b;Gsv~sttrMZ~1 z)zCL=TceHwKlyc{H40TV6iz4A6kNZ+&jc)_^5k0#9t*yt$ zq8BPcbwBUmPJP^+g~4a9^U(OUCo{yYfe?Z_wFy9gXgVj{l$nCo__k}FC0F#Cv)=2 z@Z7KG&cF86$F@%H-+Xdkc;(4oe81vS+wZ5oG4c?0G}q+yFeedh@$W>e>StbMZeh#b zojl9Et(F}zj4n+#y%Svs`SP;$@+T2v{xk~%{@*o<1i1>=tRNKcMu%-%pl3h6JKohq z^gix#+4g>M1K57tNbUvQM!n!VaMRuixdXJ#yLHoxCGTAF?oN2IWb>YP6i8kH`wBcF z=nA50WFwA_JEcHOlZMEWPV3}7rw>*QS_gKGT=-d#y!O440bo^t$%U5=eew#CHcOu{ zWq%D615ZGIBgVEOUJ5^f(Jp2oL>tG`g(iiYS_E-5#5Li^TAku}hRDMe_h)51ow&4S z?pZh^&`HG8S=SpzzV~;&Un_qpLd7zV&ka+eSUQx21f{aF!1Bnj~ljv3<_kLBlE7L zu@AiBBn@L$oL|U5J+kLj3}ejzvs#c>4H_5TmRt?T9g?`a{Wx4996g&?mb~5hZ}AV~ z!=g|R=CQD<43N47*(u!nY`yUgs#r1Cb`m4F#soCp8^ATs)n@y#GT^w18sxqO=b68I z^Y?DE9Mg<=4q~KG{RT42c?|A4kGmCpgWPw{-pB1DlL6KHKZ9ZU_xVQ#^~XT}M;Z7R DtO-f; literal 0 HcmV?d00001 diff --git a/src/Libraries/Mono/System.Globalization.Extensions.dll b/src/Libraries/Mono/System.Globalization.Extensions.dll new file mode 100644 index 0000000000000000000000000000000000000000..8c0861579e934bf6c285d54aea26f14559e2545e GIT binary patch literal 6144 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 E0%~bA2LJ#7 literal 0 HcmV?d00001 diff --git a/src/Libraries/Mono/System.Numerics.Vectors.dll b/src/Libraries/Mono/System.Numerics.Vectors.dll new file mode 100755 index 0000000000000000000000000000000000000000..10205772c39d70f6c83a120b012e25c7a206da7b GIT binary patch literal 163464 zcmeFa36z}Ibsku#s;;fqzYA<^Bo=}Ii``8U0=NJaMKmc<61CFYR9&EDQZgiNA|*nS zZ2~Prwv)&SW!cJ%lgUgRTCqZplQ>T7c+HFydqx^Nk!(ty$Ucc}SUHZJ8K3bS&v?$9 z_zXSYckg}gfA_B+RYf#fQ4&H_{rBDb-Fx5u?tA~zZy)?%rBiGX({6(em^SJWg zn*5IbIS=vNzF(ZH{7m~x2Y!Cr+rD(*fyW*{v;Tw7KJ)%(AN|1o_dfdc)6aZ(|9ejF zfA%9!?|=O1{cm{Z{rf-g%(tJuVRp82jhVjp?n>ot+iI0Rd)=Q8g0~BmtM^ZDJ5s6K zQms^4%6;fT(EC9@f@`I+AT&tkoBzVK@&cIn@Al;y%C+{F2<3nI)skf4Uk!HONdk7y zPm!>HYt_mp?f`whTA9J+6Zcmu$AfH=>;9N^LAQ{8bE0>{hfja!hmn5xW@HvJDJ#hI z-+tukhG)+_`(7|5Z^&b%fy8kST&q;pZg}?eQ_ld%yfV)fLhcyC-9>Tw?^f0;{xNUW z%2cKQ``ao%_;jVx7H*~TNAIjw_M?NeQ2z7A8+F-R?Ojo+be0cSpXx1vcv7exLfue( zs`gZGXF~6ah~1Ie6R9hOdg5cqVg1nc+2J1Av2o@qa1XJB_2tge@}48L3j5NwN+sLg z=^@*V>N9%*>HKc(@b6Y?t25Qc?q~Lav0AMyHAO(W|_yeUomXqtE_ zP5gPDxa&7ih1K3+*gP*5(Q?<)L8a0?QmxFQP65c4I*XMH`!O+8?rT6KGWHFqsO*ZI zPaTZIpaZ4*oeqYV>3BZ6wDBKsc$9F6kMeR#$d;a9#;$ncL1-|)cUK`xxVAn6OYec(XIam{OyrS{|z|UCRFbTP^+yS-TLv{U@d{b`4D7>haejc zvJ+PRZbuFwTZn# z3mmQHtvB7g&{>*U={8#Z+VgC)&k}rJYe#>k)jJAjNB>~y!t`=ttk71snK9(9-mPc# z3$shzsg#I8l!7{b(&yd${E3@4G4lFlQ%Y{x6ETbdTyPd3aq1#^SPQ!9Ln@%iK>;7ySmZ!66 z%`+``Qp<(D6fECF%d^?+h3?E!cMg_kvbnGBj(Mim1KAub&t-GD<=ND7p$i7fSeEPE zg>2zMcYdjx!SZ~TC6=l6K$gLBmSwr+h17DP=LXBS({eZKUg$0^b+^OvVz&LOdt;ud z^%nXXEbqv6u58zZ?#`v|Zdl%#?M`ZgS`THrVR?79JGZRtuod$OyNY*X=F*;O!pRd!Ww{L0k0A9N=k#dM~|lX14e33Ql?^`nG~)uV)qJeP1= zrCdx9C0I-#C0xt}CES@(F6N&S>|7~#zLdLA%FRl-Sj|ejAyRQOil3&2tDo`>PFk+| zg^ReZgo_}rgp1Isgp1Ixgo_}sgo}W%gp1&?go{A2go{A2gp1g)go^;NguB0#d!Uqy z0HeeLLJL>@enLO-kFi?0il%&n)B7+N(O(G{@mmQOfm{g}L$8F3wk_czBPCqS|E@TG z6&WrL9B;0sjP&HIjDq-CA)-s4BT{XDlA>=F4q)pa1|To z-dxJPrIdSX!DnGn^FAru;QHo#3im`Q_qI~*YYRRL%iZwa*B5+ptxCf0RcD>T4K8-W+`DP#aJ9D7 zL+|MKSv|y>wd<#Qn06ay&{;aq)t8RW)PCUc`ns%#H}2#1xFPiOY{zF` zx2^KQ9<&>0(2F|H^x`-D$+C$Rxo zIQQoP>plrsvngkpt3e@h{jD*P>zmCwr?w<=Js2a`%x9kSU?GJ4%`qWtaKY&&31JV$ z5H_23rQj@7h-K+wR4f}@a27~n*@H2b&E}j7P8o%umM%sGwZTOps67~i+AMP}*oO+y zEnSR?Zi9#JhAcD&7q)I5Q;i z?!g%EX1kpWP8fy2mo7#HzCVg^(fTdtf+X-g7z5wzD(8h0MlXZ)e& zGyYKX8Goqxj6c+T#xH5UAQ^7HAQ^7HAQ^7HAQ^7HAQ^7HAQ^5xk)Zhwq|H}|!2cjd z;OAs*I+z{2(7igl+BKi9MpvVm{OAjOIU(@_&_ugL7`b zFq-Hp5&7{bB2>Y_v)M*QNY0MxO5i#@{JN zEWZ3E+9e|21w@2AU7O~KOL9XPEMo@o^(1^)UrDVO_7B0jL=D$x*I($aW~)in(>xV+9e1VS z#cQ(HT<9Ljj<`H&KyhTK#KMR5*XUjZdrjSo{5EzD^Z8yRIiTh;msH1VYMn;C-?0pE zD>2i$FugRpGSh7L8}0s^?rZPp&$jgx?~j)*%%sKO%$nF*t8vFr?{{s#q??FF*0|7} zTIx1e7PG0W+0W|udE^*x2K{`cU+h4q{gypNe|Mt#hf4~)<{kD?K+FLmcvc4o8LJTrk%U#@SyKbuE6 z=CgS%2Yt+?<=_zaK5#@`=UmWTFds78EM|)ry4g~<3m;k5B_LbS>suely6}Mm=-kI* z>Vrep`@li8`q*whWcJ7pns-VBP9NKG;Ed~A9`MJ^EQbrb3o`)PV`sLr&O_hsPV*tN z0gjUqO)qu#pd7ohJp>>Wm+RXe%=Vxhd$K)wId-SzD9i)!fiq_Hai#f?IRR1eh3-{L z-F@(JRklxjT&Y+1*asg7o^v02Qy(0TzOE3YX%|#)z9AbrglN=p3UPGLvv1{jLxw|! zx|~1sW_k30Ueb9kIi*!)z; zu+nZVeToy1%rJYJ^>)8sZTE0m)kzL3k_qQ`wvU=``n3-u+j}_a9O^b!G10(J13h%uDd%BpO4t3|?b z40SM@t!MM*bw2T`p^=-c;C0ko1+NSAiV5pbcM)C}&1(j)i%Xy4)Mj(mK^#Y9-9AT2 z71LB+R$>a%7rc&|z2Ftyau4sN9qKN@>ymlB0$!JvKE=t-ZkOR4p^h7R>UBrrm6*bW z2Ct*$Gk8T;-@{4mP)xeLaYD42?jTMWvVG=tU*eUR!lVbUqvkw##TIoBcc+KC zSHtVo=Jf!)UcK}wPLVeE9n22Yvx8Q{2NSOn)%a-;UPsJ=Cw5}Cs(l!zo_jbO9_k*3 zzel! z$;s<_r*ZPS=KVxs^19w_oV>1iACZ{6uCFvsQrEnJNYK8#uJ;C3Ue~;JD5$)y_Xk#9 z*9QVCuWQ~h6ve!*dAsnwkqgm@-@rm|YY}!wel5Bll^xe2Y@j5hwVuqN`F^l4ZE|7K z?tRFZJ5wfaDH6FWW%7<9nY&Xa2AnW4Ddsk@whHsgl!>KJm{+AttW5R$M=pR?T3G|J zTk~t+jVNtg1F@k?ON-?P{%-W8#p)nTS6WQo!gQs@+$v00T1Z2OS8p|#{*!wZ1JWgnJ!ycNQLRL#aouh z)~KcXK3lqP%5L&Y_sz&wT)J=0Zk7n#>VSKd2f;$NxQ=VZ@V@4QqgQM=lNI}x>=wUb z-wJ1O#lAJWHFd^K%Y$G!XT13dXQLKsRJbhE$Ft*pp*{g;aiKnuok%O3yO)Q+aut53 zF3>bKZ3ep_~%U&ddHY{g~#wb^UaI^c%qU0}I7xSLF_-m+); zJ5?N5;J=z|d2X!NmTp{r*<3xDR^MnrRW4bHT=Tv34 zM6-tIzxjDWhx7p~)93i_wzKvH?oDbO*>8QG&>?*W$0hlf69PQWhcl9_Bl>TVY9G>P zm|LPRyMKMKFPo`xRK4waLWlGju$Jh{&K~C|I6ukeOrOvpeTJST`t0|7v=28uvjxfj zJDw+WNFP&mnLazHJ>-`yivM>$PpIlAJMVYbK7ufA&-K+MJnj8il0D2)*m?7L(?3QO zi2t(Q3)ncXE-!UfuB*B&aWL*>Sfen+1GIQnmPq&>3`zvg$ABrvU=MXh#( zT>CymTsC(xLNyV|wa0T2Lsb)zTzf|@V$5nHl559u5rbC~kzCuEi%{kuBDwbeAr`T0 zFq}0ll51bjMHqlVL~`wmxo9RAkzD%+xd`E8&?33^L%C=!7m++>C%b16CIl6d#~!u3 zE#!QX$L=vc=A3{hdF(aDXAo>5#=3`uKc1XtNV{mPJlEvop)BA~E z&HEc?cvB>q7Lx(AiAbi!WEgEClChZA=6sT|m>B&5Pcjx0-74@&#$qDBflo3P6KhA{ zlPne!ykr(ki-|=eD3VNziKQcmNT$Waq!dIXV=*y>2R_MIOiayzPcjx0Q*PjsjKyRu zZPiFJ7L(z$@kthoi4r8mTz#(%VJ?=YMKXG4yli}u(K|zCy#%0^I zNJeW6Z;el~Xbo+iS_|#6mTQrWHFIaqCmCzzuAEOYdS?`C-bog{qi3bwL(MP*FfEeN z+TA&yq*zM=0x zAg0?{?DWt@f=LvcL9yOM@g@s{6DFs{WO`IjOm}8+rib1YOuE-zyvf3dgUMMjIh)ia zG2OYvxgI)ZFgc4{B6KdxWntLC$JyXt}EoaY_v**j%3+3#roXya-q?C+o zQ+B#R@e`1155~30tP|RUF>MK(@oWj3v1|#Oacl{jF>DE&@oNd2v1iF>48% z@oEX1;c5wce>wX=Ih(O+iIIZ^cG@0(3P16Sm3(|KP)%m-&>n}%*$hxi(2P$@*bGlg z*ytT)Y-F;Gjdj4W({lNB#FYzkeLYz^Lb)(sm$5PDma#E!ma#GSm9a5@m9ZI(mgI!N zXv$8@Rang49dgf5G+Dd?n~`V7 zGw>{7Gww{;spGJYTVpdw!b{i6dIM--p~^%<4nV z{DirhI>Q^{O*m_0O>-8i%pQnIoYBht{_(soX2bPXn8nQ5ZkDXY@cam#!)x;#8YK%g zXhhvVHk!%i+8mco5`r%B8 z#2KwT=pRM=Vm3GAtT5xH)wsZIz!5xiC@;;UWV#)IGbIvdwDORDmHvy_!jQAVESHuH z@6mVRY!MGQYRPn?0B1@h&S>Rb{sG7@W{c`9*)A+@*DpLI1Djjw{6@ivyQ|ouZiw_C3+EogorGaX0>g{Pa9d#*lECodG2CMqh9oe&cnmijh9LW=Qtm zFE7=5SKJ&Kh9oe&cno(+h9LYBrv>q40l(C zAqfmG9>cAcVMqeQi^p)^Wf+pc@ZvGtgc*h;FuZsScVvbk2@EeD!|j=2NCLx)$L!BB zB!S_@W4LiMJ0yYO#bdaOGYm;!c<~r+=?p^>7+#M53MQDOi*e~sy4ay@#aWD7JTpVG z|B`$-z`Ht>V@LwSi^p*DXLd*e!;8mUn`1}d zf#Jnt5UK$W3u|vR+1cn!n!IB>^B!S_@W3W;O z3`t;k@fa+y0YefPUOWctV!)6Dh8K@Hnqx=;!;8lp%P}N@;pO=2YOWWeTGA@~A#5kS z8SY)J_mJ$%ClWk_`&fgJ>~HYbL%63k2+95ie?5fzTZ547Z}9i{4`6Xm1`V2KbauJp zH7g|h@^uC8h`V2dknC^p*F(4yHVDc727f(-yJCZo>~HY*`0rs9C)q_WNwT~8R&V0= z+yu$Ke3ijNxM4OgB>NluJ^o$Z3;NjzFWf(y36g#JW`p-~GKY}tZ}9hc*Ly)vO}w1= z^iWS(%dL>?Ke@q+_jG5DAqfmG9&=ZYAqfmG9`pJfLlPKXR^FS`Jw{EE-_L; zknBGtpM&t2yK@XlV0g*4W4e;P_vPK&-nXTD+uS-sn=Rbi=8jNJTfMi&s8Brv>q%yf<+2@EeD zGm~RT0>g{P%;p%9!0_TRb2)}2FuZuoe2yUr3@;wDkYh*!!;8mcIff)Kym-uFjv)yQ zFCN34thEMA70<&+Z+4twoP7)YieAhUTV@LwSi^p7@V@LwSi^m+yF(iTEWwU$R^$GU2 zz#JON4tKKF<4FR;i_bE5vW6iE3@;wTovdL<0>g{PAaDv8lECodF^G8rh9oe&cnm_7 zfFTJCFPq)lu1~PH1qRVXAv;*|Lv~04!;8-{R_cHu2@EeDg9SEVNCLx)$6#Fy7?Qy7 z;xR{a3`t;k@fhx8t+_}7!^>v(w(H~fw(H~fw(H~fw(FN=Z@WHzZ@WHzZ@WHzZ@a$v zz3uw=z3uwu_qOZf_qOYoWpBGa!QK{_v3px!#_nx_+1%cCeS*C$Fk88|y?;yhwz+jo z$XmI$%^hJ@-|D?>?hMPrR_|?dXWxLmZSD-K(N^zmb7xrAwt8=yJHz_8)qC6A85Y{D z-rMHRuws9M_O^ow_O`%`-P-~)c5e&J*u5<i#P9Cn11cnzM(oE+VlECod zF@?P?Fk|<&z>MA70y8({Yd+5pNnm*K`B}&@B!S_@W3n7W5*S`QhC5jc=STv>i^p_x z3`t;k+3enSFu~pyn6Z0XV8-rkff>8E1!nBt7MQVnTVTfSZGjoPw*_YG-WHg#ds|?} z?rnh?ySD{q?A{ibv3px!#_nx_xg2}j!329-V8-rkff>8E1!nBt7MQVnTVTfSZGk}) zR2ZcQRl-On2@Efr-P;Z(*xLd#c5e&J*u5<ec5gcvzqcKX-`ftx?`;Q{Wp6tezqcKX-`ftx?`;R0-`ftx z?`;R0-`ftx?`;Q{Wp6u}U~db|*u5<vqfYVSclKfG9(=yAi!(Bp>rW620j@R&*+e;y2zO~)QdWRRNt1sq@`Y;_n0sU9O? ztHUrz^%x0T9fm=w$4J=fFbq;XM#5HyVUX%E61F-FgH(?p$Cq8gR#!g~wmLfuQoS7s zTOEc$s>ev!>M#RG`N!aQz3{t%v z30ob8L8`|{*y=D0QaxtUu+^ElCO3n)ZW}^3a(owKkZP_BLypHVNc9*ATb(ZksU9O? ztHUrz^%x0T9R_twqfgNzn1K7zU{xBVnthJyF^o_>h8&M!gz7Qmc+A2Oh8&M!gzD{(<1vg-Jtm1+ zt)3a7ddw?}SMh60;hH5vs=|F{@!1p?XXbvl@mG zs>dWTt6>Mh60;hH5vs=|F{@!1 zp?XXbvl@mGs>dWTt6><*@uPwfs<)HG ztY(K1s>dWTt6>gz7Qmcnl*{k0Hlnuuc~+5QeA@^2%aXv%(P7te_cv%xVyZs1B0EtOj9->L5wX zY7mB~4)O|ORx`m6)l8r}`Iyxp3{f5A6~wG&f+4DzK!@}(t3eo|I>;-FSy^vX$&mzx7mwjK)-WW2;l*RP zjWrBOV0iJEc8(zl3@;wj$uT5>;l*R7a|}sfc<~r+V=X@q3>MgcAqfmG9)oo;U`PVP zi^p&qYfV8C7+ySv+gQVp1csN*K11~I_-BY79{&u{!{eVJdib(DLv(8VGeoDxKSOkC z{4+$SHvbIK!{eVJdU*5C5S<$T4AH5}@(j_b37#PWMmDx?&~h7V9hW39y!g(|ZLDEP z0>jItfBWIo1kVrw!;LMz{cyDRE_qt0&!>g>9f+yQPgN=vtlU|6cBuCP|3=8VokyMH z>B5ESc|CR0)Xe-gc{oT@_zSf4m3_@j{;W<*FK=tx_1hlI@V9mNw`Y3maC!8%R^Dia z?a3mVx9TAIZ5O-Yrk_URvBkGZ8#k7>JnxUZ!n{8+KJSlwqw@afE6n?&7MZWbn=T0tz3erhFtY4f7r@w^>&udgkwO!W|jHtxI3 z-|@_?BYtgc;aN`Z2tjaTi_da$XIF|d_fh@J{T)`!uSt)rrzX3p0644Abb<7&c1jb~9@o2|j zBoi2u5ysOUgON;NOhyfiW2iS~wF*V0t!W)_4eH)<`BWCZjc;0~w5D0%J15cobwX zk_n8-2)iu-%o@oA#$<$H%?L1(35>}I0*qt=V=}^c zdSrPcnZTGVsa>!yTDyqcgE5k74BfV2Ulz66y*`JLOkhmLtnnntvPLq2F&SYu<}i{8 zjL8T)lEX+QFeW36M@r_DWCCL{!g#7=Fp>$3$q3`YlEFwOFeXcC7uA;5?vEjEh{pIN zg&8BM#u$Ox$&=OY%{h!@0%I~-yCsK_Okhk#*sVE?WCCNdqy%WfifiW3jugzg36Bv^vB|z^=O0e3GmTu22k-Q-fl54RUE&fwwcO<$xhaxn3bSp)POHr2$s9*A#W7vt61_fgdE#f%p0<`-BYA_1nm5Rb zb#iA8C0T%y6`^oK9#clCfK;msb%C^O40piWgYFm63AP$G-oh?5LI6B!`KmwbR zsm*4>Mm`ggOkhmL`L&tDNG32QBaAzKi(g14FeW3cox?~bFeW3clfy_RFeW2xI){-= zU`$3Bcl?$&k_n8-2%F7eBoi2u5jK~@NG32QBWym0kxXDrM%Y3QBbmUMjIb<+kxXDr zMi_VeR#K7)jL8V&j^AJ;6Bv_W@w>FM05!%XXXIHx0vmM}kibTr1thRhX8{Rp)LB3R z8+8_tz);-0cCiYE+9jF5n2e2p^((+gCNL%=3@cB7kxXDrMi|zN03(^en2fM}IgDfi zV=}@pO9yKt6Bv^bb|8n5Okhk#75 z_+;c+Kmt3IW{o?3%Nof9#$+tPwK}I!-gWjNG32QBaAzKYXp)BjL8V&j^AJ;6Bv^vwTo&?Yj@OHz^$^* zGXfoX7LdS3odqPYQD*@OZ1ZOUS{)2)N1g>Fuu*3L32f9^KmvPhc^ja2C2er=vw&Ok zKzZa@K#F@^xl8oaa+h}iZyku_QFX~^dE{9@iaS~E61_fgIr1#vsEe96*2JKNi`?y7 z21yp6WW~Y2-M&Fd7NBHBD0lk?C0T%yRWf!kK$8+({4C(;P)t7ZET9DbjXMi?Y)j7q za_d`s7LYsQTD!$(0l70SJzIPhkUQf{yv1h$xie0hTYMHE&hja4i_ZcQZ=Bt>_$)xY zxmj(C&jQ5Z(7dzdX8}hhI}1o)qs{^n*r>CB1UBj{Ac3{gxt}|JoBK&7Fec*?(aB*X z6Bv^bHl4#rCNL%=j5~gFN-}{l8DWL9fCM(`EFgi+rPk(irz8^?lhNry4kMYsn2fM2 zhmlNROhy=Y{8mzu35>}IMS6EjXDcRV580g64TXWCCL{!nor%7|8_2WQ1|YZ!nSxjLDK-iM?*xD@UCLsIjqU0SRo>SwI3C zbrz7oMx6yDuwGgp7<8dNNG32QW3{0c0*qt=V=}_9wGS|o35>~-+6DWfwTqo>Fh)|1 zjXeuUV580g64}I{9c^q$1ea>9HnXV$8{4q_O{`PVMX#i znp)+T5I-Xd$&ya+ZLC_^=N z+nH757qa1+#4>oXvUQfa)9}{Grmbw#Nll90v-C#lv;28i=3a96Le|lmOdhpKMuG2# zCw>_N$KDUJsc1XqCFy!{x6#YEnNS%iFXIkEWu&}}+Xj`9@-prfR7T3nxIs`EDKF!$ zKxL%7j9USfk@7Oe|0*NpWeoaNM#{?=>8p&CmodCo87VJgOs_IhUdBLPWu&}}(Ywk> zc^N}?m6382nV$zve7#a^Uxt*uXV8P>&XTu_foU>QUdCuq^+^+(Ff81|`*lq)~ymLB#EogUnER-g5X$Eo;17M_%sF)C6SDKBFvq%u-o z#`s5Nq+Dh=^(2|O%`4tP#Sbwvq`ZvbhRR4eqbOHu^rK#Jjf$L(^tD+pyfYV)^uoJx zAxSS}uwZ6Mx(sm=N=kXV*W@#VJ+e0${Wa-MHUw&ygU2lJSc)9TN=xX%M)%NaI zJ1({J+jOyrzSFgXM(Df9OW!m6>EXqr5Uf0#uk~G7hVPYSSk`-6hUd2#q1m`1rnL@e zaHU&Vw8<>oZ+Nj}mG%?PYML2YrSFwhS~H`~0Ox6YFf-}$O@oq|BC~Km;>D6hyb!5b zW+Xt7Mf~2OMI6r424ZH?)}TSjOp#f*U-4qe8eg1>w3#F`vc}&lYrIBFy2it~5_vPF zi$4uYW{S+h{frkw2#~D%g)r4>CV$WI-VyIP%CKS0%qEct4N7K;%);%C7stN_nPx^_ zgS>adYmhNB^GRkn(Mo2D%);G}7stOPnf4iZOY+_kZ%M|?WJzW?K}%+e%)*_L7stOr znbwTFL3!_pHz;Ffx=Cg@IZI}W%)%X%7ccT=WxG<@lXN3L<(~MJ8sE4`|2Rn<)3}}0 zkUTn@Bvz&^#-zkvw@h_0y9Zs8QJ0CR%f$F^Y(-t(Zi%`~jQ{pl)NQ7^OpN~~SJZ8# zx=f7!R#((To#%OGV*EF}qHZVEWn%ody`t`Ps>{UqZ+=DHnN*jF@!tZAy0fV+6XU-T z7Io)RT_(nVJ1pwXr@Bmx|E5^fT}XAA82_!YsGFs_OpN~qS=3!jb(t9dZL+A_O?8=& zZ%s z@n2kvy4)>CT_(nVi7o1KHyw4E82^Q~sLS1U)MaA)m)oK)cjHl)iSb`_i@MycM_nd1 z_~REl?%J(ClZ?7dL|rDve*rG)a9hmfoa>T|x=ch}CdPl=FY4Zs>M}9@vwuM}X`5vIJ5cJ2<6~e

RF{eI=gCp`63&yO z?j@WjN8MJMXC}s{Uq^W><@BjmWYGBN%k!Wn%n!a@6G!a@1vF z{CRTJy@d1RsM}5bGI1HslcVwNsc|O8pC?D%D^gu1#-Arg-Ag!6j=Gm{o*Z>|rg>&! z{CRTJ-IeMxG5$O`>R!Tma@4(q^W>;|WtwLu#-Arg-Ag!6j=FnOzf6ojPma3#Qe7s- zpC?D%{i!Y!8~pJT%K>m~Vj&rInTP|NiSg&jQTGzglcVldoF_-)*QAAFV*Gh>)IF5y zGBN%hcIV`eouWoF_-) z*bC*xnHYbb9Cdkw95cqm`19nbi_@)Kmx=M`$x-*lRF{eI=gCo*N60bHOpHHIj=DTT zj=D^YKTnRjmvEjOb&sWfnYaw+$v1}>{u0iUqwXb~Cr8~&I8TncTXCKoYx@$;lcVk> zoF_-!OE^!Cx|jVtIaW1~kYk@`V*Gh>)V=KI$+5hbaGo4>d4wEG#KdJdPmacUdK`^2 zvB4i7VB85#a-JM@$&b2Bj6Y9~x|eXC9Ca`Id2+P;hO|UXj6Y9~x_76#OkDgt`3$zx z2)On6G-Gi5U_BX;a#o>A5-u*Z(?%j)@PV!eueyfo_euKCmIXpk{15nyn~bEBaT{$il1|1wv&l$0 z8MnbEBdN0I*{q*s$4J{6aokW_&$g4-aLf)jzk~Pp-ydb%(VC2;lX3fMGLlZl?WxH~ zIvIDICL`%&+)0}3T8A&JO_Q~`}IvKY|CL^gbePgfK)(FDWwytno^AB-ZYjgW!mPig>>c5mo z59Lyl1u6G4rc1IQ<&MRqBnwjX%OE9LkfMVIDanGA`wFv6vLIc_r6hy&c~->GNC0BE zcJOYX)eiRt=85FsvHoLxbI_!W{!L1bN<&Gh(St1Ebxr`yJ$w)dGLsFBGbTWpVCL^gbeYLb$cWCn@ zcdPd-J<)AtVLfF`X?94$PA+2rX)=;3d!FWoY5+Z_=mYP5TKO3+njMm`lgk(ZnvA5% z^rh8eIne(SA16M6K7i>6_q01%$2_g&)<_PX>?guIb0JADWCUmVAnAn+(M(9vg!-~; zF_#zzNiJ9K!>p+Py3<*B_Mfqs*&{i4s-Fn&&V?jRsPDRpv-FNW-}C;HnMwugj=dlD z$F))KhrKR+KkPvAepvn@*i$_$RGqJ+?|yx4?7LslF5mq+ki7eqzss$5-=DtM^|i6@ zb-lItUe|R)?{(#`Z>fiu3NLJZdHf4osTX-+>$;&Aw$#fC8dtqMR=9KiO2CsJi(;;hy-H#=m1_g~{Ksx^CzlD|N$JOT9c^xY_;X@h?wVVe*%! zmWN)RQZJk?tT0a(ZexFG{A*EG7;z)7MO`=aT9mrs1f+#|qHuru%j4g7N~=uXce-xq zeJAz8DM-CMS-A21nn$|I&X!gG%w~D=VDv^@*Qtq09E@?02z9~paduc0?lJ-*Woq{fDFYP2!(q4)Q zG3t``QcR3dO4>_j6DesgolB&oy>vd2lJ?StL`vFAvqVbTOSzj0wL#iTxt|JB((WZ> z1gN9ipSvj4e*Z_EPSwf|RtEa&Hx+q`h>1A|>sm2NEf1FXav^SSIbIh$fjMj|Y3pCJ_qMS^? z{YNQjFXc{3YfJx9y_a$)6{MtHeK4-b`^W0Xyec2rzmE^i@AM@VkW$p zJE$mT!etD#n`Z1eugg7?mg(_}e6k`)xr6e$+&2X&X)k?kA|>rIg+@&?wdPg1S+W8> zEobsx%>7anGvQ35N2I3j@VeY1X`Y|B$X79flshJ87@a9KT&T#kX!v3u#!$ohm*jn> zxG4%!(q8)dL`vFa3Vk@uRN1#NFm+%2+ZZamTg>{sPqosmVv@tZi$+1KyQ&&gVcywR z%SrrE@I=S2oKc5t(;?|~{A?R_$Tl64UdPW!QHN~PA?bArqc)U>Y||m>bqWJ2=#XtX zB)v|dzXu(%O^2k{DRktZL$>LV^g4w;8g$4u9g<$B(1n5y*``AhI+w41)GwibIGqXm zhtrv`e>j~9`^QvTo(cPh^D|-paDFE2AI{H&{ln=@*gxu*v46NJZo>J)=}g!^oX&*( z!|6=eKWdlIKb)Tl`-k&0VgGP`ChQ+hXTttb{Pt3{a%SxMa3?OC8(ynHyRg#q z-#^JMl&D=;bNVk$a*H8q7gnGCW0M@guU>8CF04cSNlwV4a$!O0PjrACJQr4`{zNC! zLAkI*^(Q%Ujx|^yaWpM{^s!wTJI{4D%;K&D@R2jJ)*1{D78 zd3|M1E3510KD4La%4uXF<^gS%^}Z|Qf%oV` z9^@OS?cQ^cJs@A8k9i;}&x3v|Rr4U=Vjj*i59o2sL!?6MaGC?Q#mAWg3{vJG(xD#MANDg3-xKms(iYzf*~D!j;9?%Wk9k19V;&+M z^1!yhxX5~cDdeG~E&eiOPfXkvicWLEw)iW|1@7507m?PsU>-2wvfd{~rMg%3!#WI%l7zSfTZOp7Dp-=nKu z(QJe4_xW9E4uP-eGes2tLG#RC1813bF(p)R$1{vK8}T#f=`9_}E$Qhm-5-trI~d=o z#svZU?A!c%ali5wNd_ShujxF)Q|2FSyeh z#z~=G*x%lN@p@5lw2ONAWU$M6!9Cj0_si;q{phig^`eMqANl4Oi}iwqHT3JUdieoh zA0JsSib(Bq{6za$145rIv;QezpL}Rky(=QM|AT73R>9&`!5AH7pQR7>kN#2fOw`5M zPO0t{=yTR5*rPrgmH*I`p~q+W=o_n8*83r#It`h2IU3k!Wzuc+STj$(OUp*z98IW~ zT7|yLbm`l4+1oxEmEl{I6W^<&+l^y8v>Sa}n`pjqm^W%HP&bSP>RLZnU7Y1*y>%eU z%gok#;=#PkoW-QftS>|$S?|*$b)Qajxf(6sR_*azzZKa3ZD9X*WdH5J{*gfZ9TMFmRsIbgwrgglI*XOw3sm`0{4C*~ z6@7e$VEXvGltdpt7TBL9JNo#0WTU@S`P?VlowjMB_ac7i;K!Nx?*%5ft1g23iGT|k z`TNviM*aaMF(dzwY-aQ)gZv*+E;%aXgBB*qhrxne-?=U zfdnA`V_!PC?AtU^sT}K7&%Qk-_EgWlGbZM$XYUOO47a7_x2ROvXP(?vX)M35T08qVKpRv8 z{RC-hE&pP*wn6{Z`UagQV|Rdv$#{kDrO>u|1^&y*a~J$-Ul~^`)mPL8X*mi-n5Yecx3WB= z&83~Iy%x6X(>1xEU8RPsZClpUn=)d8Y+O53EANKwo4BSq+uDUrHwUxYw@$YPU2W+& zyhb@}Tcc+>tV93*awx~u|9bvc*LrmnW}#BO`X?Uy^v~V0B-dMSnv+6Jugm7hWZY@QKV&A?Py$D3Ey+cbYQ$)(aF)QMGd zTa!F1Ey6IIhK*P2v9-}%6DPQAU_a82nQwJ)0rIb_$DwC2w9jHu78FZvF zDK2$XGAT}V1ispbbu7KwhhsmHxfi|sKe0aiE{qla(}hM`_1K%jaOQwGb|Op=96ZNX z(iP*_Rp}~lY(5m9l^{Da**gu?h4+Cyn@&4o^>%0V2Iqgj^ReiC@R&o_!)j%V%ku8p zN2XCxtFzS_?~he`m`@wneM;Q%EXsHb;*Q>%uhLp!SHSmco0Sv|*DC?t1P&#}@&a(DF{XQ7bXQ$5G3wPf!%xy9D~>6=3xi?80@ zkfWBnz5Mqi*ujszlU+zkSae?Y_Pam!9;j*jRjvF%$j90Hz!j}E zlpAf2*0D6sDv-u3ZRwwu^rJD2BQwbF*L0<__SYr-KumwHq#q3FV;`-aML0oY*uy+t zJ&Qf7B_6Jx#Tld}-dR11o@fc2JDiFMoI8ALOyJxB8y17%+~M0|0_P5ogoO0&_b5~` zDm?QeJbn0KoH#rQ=msr;eu^{=f&Kt#+L8VL$CzpS!=VH#42KddHx4COTpmiWm^_qV z39C4iU^Q0fnj0)+b*{C+@>S>B8!TFNuCu{XRp+KRSiRM`nGF`KIyb91nVZvG%*{&< zs&fk)taj@|Kc`tiUSu-U3~30Uxsn>zoGB@p)5t+Hs9}<3QX!I0B_*p$N^X^u41>h3 zPx7o>$+pG-p%U3UHTw#Age6l&Ed=ii-+`$9R*XZ=%WMH{hxMv7fi?sZYy)kH^&T4x z2dv+Z%XIVV9Y6Qd@tfLHy`My~v(jyd{bsL;p?<3OH^{{3`y{Tt=kYVsXh~vrvEI@K zXCpEpEzYK7LYkZnx~Sbs8g;SWRu#4^6QaWAWkOU~cR_`fw^(ne3agI^QDFr#Au6oG zpu##_#O5LPL?&WaWI|L}kD`JWU`1xyAEqk5g}OYxwRJgz+WZVEQvPT<+%>NLkxzd2 z$y>g2uHLO9BW1EX>XL2U!x4RCN%}+hIsZ8sgDd*fN#Is0!d0uW{bt+syfOJuK-##o zZEaj>Y3x`V)?U)YNaRh7MBc>gGB}dr&{%9(uaG82B5z_O@+LMFY2pTs+N8mxjhV_D znW?;$O-ouC-%ZI|8Hv1=k;q$FyR){k#;`ahuAGoIMz+xbq;UEf4jJ>|=uLXB@ug@+ zOUdk%NcyhvrRy3ku2U7Cwo;Bzqx^do_`2_&2y ze+}($8~WCi^i64Jo*hp~TS}5=$5YaplEfkGEZX{;pukOQsFpQUTh>b{a9&8ZQ$Bud z1YX(zvi^N(Bgn>sO#L>rF16V(erlCJvv$+9Q`=3NP!63r5RFF?EvPH0@i;WK8Ffbb zW~z^gTD3TbTxxf$UgG$xUiR^qwqv0Q3$3@Q@pm~3JW)$={QYzE)z@N;Z@k2b zq-ibJk{~J__j_-8|YhIQnld<=|qdd0{l}7_tDU%$dbCBurI;ot`0cpqU-&7UQ zwrLbMYaT%sOXkswwPUlS4N>o#4@X+EV+xJ?4Qoe6X?c5TJFeq4iEl@u(7t+`WOxSs zhE_n^mO|tjVXdzFm)?%8F;OLorXgh*gz9$k@J0LmWm^j`HXeOD>bT?WuDCkNIMsHv z+g;j@|IsQSv?JPXOz_5i^jo%7DzD=_B&#?+$H#)Prq*$DK(K5@CwrDY%mNS;A$`RD-9jsUB*RR2C<$pbY&)Tii&;D;X zFg)|W;@IvvmIya7r=P7>18XX<4o6mf3Q9RbT#3<>1G&w9s?(^QnqI8h1X^9h|HR6n zt{y67byA#2(Ie!fqN=A$L?&3)nqpa(z{>bc5u7~1=hkkj_peZd>zh;csr6GU%=Y=u z%`R5kT7hy}^Eo+^DmZyGI$WqAG#PR2?e1SxK`!Tr1ek z)%xN@P{JN&nIQQke@CK#!xU%FY3%D*p__=p@FP6}6<{ zr={VWho+_Bo5|X=R?_U#a@a~Vo4siZm5e-g_t?@QbJLP@;#u6L1su7Qts4T}x>#WQ ziac&I+1CHUm!q`3MwFzqu|||d8)L%Kpn0n`lwHPa1((ed`}HRL#ZniaOID{UZQH10|5?M=ZV`awVfPw8QQIG+vjaaTyFSn$#g1I^Q&GV~ zs)4K&TkN)*w40yttnqWPuq*x0tVll;dW}9H%}-1P54+MF#GF5=`4zjH4|#d^vw#Y? zOU7iqcN2Of9+vtG-sYt25wzFqrLXQ4$=Ua!qGeoa4;VC*!w#U`V9+-f2ak4!L4VqK z431CFBs!;O6ZC1sm1n=5knR-D(al5C!p8`60-z9Jrh+M>HBz&5MMQWE$wlQf$7 zxqPj%8+H8#wnL9~jYk;s%0@6Q70>)(va_&j28R3L(3u+N_i z^@mp+LuF)BAZ6}w*+dksJs8Dn24$;PCMu0Y?(76usn*)O7`=>?nwgcD5$m4^N-6L7$FV`xSH~wc;PeawzT^g#U)7>3I)zsZN zR7;Y$r)})LOQSB>QBoiGI;yPieEGoF<DA^(y8GZ?Dk_2Pu4$y^rkumM0ER@UKFignvNc9F<`21$#fU z3bqu1qY;^zW?r%H?_Ld;lE7I)_aG7-=9P(SkRy@skn|7|B4Oe%5^d&{iEEM9tC6XL z%+xjJg;NQ4+ywX(V*?Oh&CXC4) zo!U(6!gOr(v_Q7FaBxmPWl{X-h zy^_qgSLLZ6orHA`7H?+ItN=wTIveX;oT75e0MfEf%Q`9Nz#8235J5IJxXvRLtSgg? zBbbRX<#VBIzoR?b-ravkcdosYFLAyDQ}}E)chfRIMSs)u&TeN%cY3KivvTJS{I!W$ z@LD^Sab&3r@ytq3xI}`n1B@L@KvJ7XG8!-eWIO9Tz59v5SNreKCrkKu2s%$|R>^)l zW__kv<4k{ugjH7oSWpzCJenZzb~c?lx)X0%cH_?@pw5X?h39IYp7IK?JMQuH=OrNP$ot3&rY?EnujVq6wEC zs*{=Kc9$LWWPRbEW1 zRc;YA6Nrk{;`x&}UB=vn4NqOVSXRGjzTR6Vat?iIsk1U$&zilPBjdu1#!mIhFH|cP zJU5utWGke<{Z38xN&4Hzds5sl>2Du*dbwRfh^Yi)Or;oPssWq@HGkdmrtzuu?ku9BdcQlzRKGhfb6?gF#6s3-Gr9tRaaE_??{=dc zlI@FE=uCu_M<*k!Lw%i)usm4|95C|%vk);EFtW7{n8kqUMhwQXF$lSI1m3#W7`6 z5DXm)MjQ)(D`Iu?vL77QJeAco7E9?T)ZRYV+1)F4$YYci{vGAam$js+u*1w+SV zMGPGaMjQ)m`RrW*x2zqDQ=^#KFYa#C<4q&B65NW>C`ar%{Q zzI-)XWw|=bYc(YJjFg0a@~gB6r5{5m!Iu0gEkZJ>DZ!2WDlI}XDP(7@*_usSgwm5C zWaq5OuhJrvE({^NX-$5W7NPWA2qkioU!_GzCVjG>)@=DFEkfzU5VBv^1t>Bt4%J#v^{Im zs@k?9WFu?)Rx4kx`1U;=J22bsbnL-wtZ?gFo9T4y!)%Ul>r@-!bnL~f@YAsyvxZN{ ze#|O89Xm4XFg(w=ee+P6Ufv=5u70#1+RZ}X^6qDI zCU|+TvpEvHytDOPy=fn_sS&umYuQu?9&1l~ludo$vCn8n>Q7Lg;RG|S&vn#nEOq*m8U>T})oIvvh-_Z2?!`doKCuqL&-UQ(ay zy3{~7ug`VYC$&D;UC-3|Tut+%B|W~ zZi;ekE4ONga;tVIw`yCtnV>d{70ayyJ(OF!WGJ@|ZYwvLxwe&CwV4B!TeU;ERolu< zX0C1JR_#!3)ehxWZ7VmKxwe&CwL`g8JCs|st=vpdn+1vG)&UgCty57bw@yMRv zT~FU=oJFdIJ3qDxh1)(h<_CVvgxfwgP3plLO-bhqH+yW{4E($pZuZy`6Ylg_=Nb5k zaNy@aJ41&fsRKR1qhG0n!A7VqsRccUdNuw?=l6WpmPVJR&*x(HPF*ka8J2ynCKG>g z-sI4EHU0>zU|yfE#vj;K_@O9K-K+73RKu(B$E)!Nqg7qSUyVQh3yDAe4DlELrK|B8 z4@J_g<{Iy4gfN-`;o9egkw8aci8E$vmN_T*q$&} zJNDkNxnVeY?7^@%U?9vdBxs1uUw$D$gKGZr3ki&ib(haCBxrZ$FTarRh{*V>Ur5mR zjs6@t<3Cpie?00*pXa~mzJM=G z@M!?~!UTyPp7ND1Ot6nMD+}BFHzwYUhr#zP#E(oc+zKCxz(*$7Z9);+ z93(M4Q$5E%6VtQRbL=%SJy$*VNSdCno@383xqbnI{pR!N#Qay4d)D$13$23iZ3~t^ z&@6nQS@J-$*nwu51I+>ln!`WPENP%w%0RP(foACf&5{NBk%VT6l#l1^7Ai0+|Kyey ze)3On$nz%W-vejM@5Tplr}RkxK0n3}P4Gdhv(kO%pUf3eOXbT-dpEQMc&ER$Mx2EXOZ`!_369yZZm!QaJ}2ocjW`PtuM<5aEJF) z`G^P~1mXJ~Ek5qS=S604#j_s`dG=!#4|3rVkr~|2>fkzhar*5vkqGeN!Ia-xBNW_4rGyYy$ZrMTd{% z;U2HteP=u0bW^pu+>o5uvwzLrKSaIhgL2JNYBqUVFd-3H(XeGJl02P~gjQ`N2lzsJ znRnr}v@-B4oZ$HkMmx116Ew;MyH|T~2G2F}*a78k$aC$t^ZqkFzflMOaAM2~Cwaw# zcQI3NNK4&%vms}*gH}U!>Ud_WA;;OMX*@W}5kBYx!{JQ~hqhoit`);kEf_i40*1p` zFdWN@;Ybz?hcWGN2n*(sh~elR^sQtZ1IIB2-pX?QXLnycX`n9d#Y(HNlXsVrCc-m! zS77wuF*XbyJi&&$z+hmgEa9OvGH}Zm3_Pf~gh$cH!2MlqR;@%nYmDcj8#}XZ|y9ns9;a$r$L|3o7dvJs&mtJHI46V$KlgocJAZ>V&kxsIu{k(v4015=I8B} zclBdKMEzW{gK)1Kop!ih;t)J`(q%Wc&#`Nb&)nAX(ZqO^!>J~}X*`M{Fv<@;pA?Oo zJ=V~%P`;uc1bnF<4fwcjf1ml$y1J$+Q=eMw{U{n{bvTELk*U^Pv!PIQ(Uln^&C<2T zFJ@`jFv73d5Pp&99p+@UB=1|@ht@1%N{PGf?*?Q)k^2IF{Qe%_e z*T7hQ&S8ElVr;d4f>XLa%onKhC(o?X?eFRf3c}{Mu-^yEn!q=y_)Lx_@DMT|$wA`$ zNol|5u!75*4fPGG;7d{jbAcyr`JZ4xXLTt?d$a;|X;rj=kvEQ;lel(J_VCIC&0`pf zdHH4k7ccV56n*LMppRDwntX-e>zAl6?;dUG!)6URoXD3+Ey8ZZNDc7l3Bz#5L=cG_5 z%F9co`FT6?ga2oI*tv3YFx}K|7vm{5G$B9EBE2VZ!eO8I_<$zWMLR8Pz48J~;?r~b zB-}|nCzdtDIwCXuP_jAY9~M)$xQbtUv6X*9Oayr3t)(v?Pc62ysn(!5$PiudgNeUVNc|QdIXodH$9sm zmYF^NGYBiv?7ZRAa1$8J*c;fH;P5ysRjiySdyE=OH?TlHi%b*kH7 zEiq!#^7k{mn4+1JtY%YrrWmVf9t0IKl~g8`nW>fUwknafiy4rbkt*pn)q3+(x5W(P zHu~5R#M((c#)dLY8&gnD+KtN0Kr_sI8V}mcsZT+MnNy#H3^S)bQ^~Leb4|Ungj&6q z9d;#M76JfegS5pKWhlKZg+<(<`$vbsA!HuqnJr!{)>IvMk&1q<~{m1VZ$6 zj8ed+6oC+Z9jFwrE=3@Ojn*lvqfeub@HtAKO4JXTC)JWotDdzzRpXwg1j(4v$tXi>};v}mx~YN21X(dE{=ZOq(wvjP)& zx6=ftg(Pd{}O^14S~ zmTzh3Dyhn+(a%|x%%9MB#GZK+8V}i1n~)A(GRCOkdri|E7J2|S!-2u^ga_?8jyNVT zYT7*R;;506G8+xD1Y^{IhbQhaLS{mr#48MZZ;`y&&?oh+s=2(oNWA>&xY4!z)p3Jv z`RceCKW_eyG;ZNNMUHbF?YyU$&(%D`$!7{V1NaY47dPS(-Th7%=Q63Tp3C4Igu5HL z4Mn2&=5P+ul)u55o`d{-oP+F+x1cyvgj+Yb1x2?ZiCa+2b4cPA6dhYq@8kTk#usun zqRQcB2{i*vH9J1R+}*_om2ft}d|^#~`HFeux3PSe1cCCSe-11%@Eu_J`(R|)W=R=h zt6JHQYm;;MIsT~{@zZHk&pihwN^L9-*%+m~4F5bwLJ=$2-B~_%pk6*A-W!sO;6nZD9~4^0(Z3W? zez8$$4cc6y@V=X7S?s1yG4Nr5e^!qy;kFt+_O@R7I0mB_2KfE;kFH3qfbcfgg@i_5p!(m$t! z=1>>x!)s0axh1yS0J~}yEi#!HtebP5S-r=6t8%BIe|PH$_I^gh@pwIBEk&eP& zsIk8c6A&0LIRXO(7VS^M1jA)^c?=Ql^2mW&yvc)1%E;5ZO2IQv3y9$DAItEc24G1R zgQz5LnyIKOwqlB16&44QN9@F1OE#Y3kE566n;=7wNP0PERq_9`F2ptq!l9Mgo z$;7UU6(K#F?_J`1H(DDwN5d!aR0_IpYz5W@XjR0&r}<$H9TFVtLVPq3-x&z@c@ceA zAY{Pdk4v5<1Ao1>){|MAx2I)}s&BAIP_oqf05P0ju-wSM+V#`DE~dIOH(}a*uD*1% zQLp{L<45;A`){#W^)g86&tH$P$@OLlI!+Me_4S=MF7*}*Qk?TbYLns%HT-#at%Mhr zKNi}F8R8?oe`Q|^Q!lQ`XX=DW(UMXE-iZbd@#;f)Kr+#FFI%UJ^ zmNmJltY(E&+d+s`(0s~NtHSR|^Xj^*~u6X+2==}Sj@Zv$P`&UbjeQqxuX2K2{s5HQ}!V+b7lNesdJIgRCq zq(kRF^4LSpwc9p1*P;n1*xwoutii;LV(G8SzuRVU#*=8(7-#S_T@n^&RL^~&z+vRU z3|iztQf`#_$m6eYOvK1TQuJG|{7v{B8+*tZ|MN#ML~8YC-i{G)(Evp5woQg)K}wJ% z@e0oIk~I?X3Ja;fachV4gq}%v_LZg>y}&N#3C2VG*Lwk7d=;@w z!9G@AHp&Es^}g~WuyrcN4UB7k9M#`x=%2R8;~E;(yKjtZ*wlLeZ5;xcR;l+Af3r!mN(5#F%_&(7Ycxe;5NRz64kAqnFqBqs z>*j=g{xc{S9^|k(<66X9{+(K*_Y=5sz^(~3U2RIsxmtur&6)VHj-@Sy2V}IS10g(N?Yzjkez$x?Er(QIk%74d)nmvgPe}s16lfHi#SN;pFZn2YE z(-o3Rh3uft8kK3BNg`-{#c|>meB(n4ARR`(^kH?&4}3^K$qpbN{a`(_ z$tYoX$x2Z@`Tj>}27U!3LisI_U=SY!37DuxsMZW80O_72{(tt~1U#x@2^;QnX3b>Q z47&t|MRt;zED#VRAqfyjLP)|Y0$C;@kd2uMyAnW91O#OfP&6#c4kD;1ppgrRhzf{; zYyyI!fCA!zBK%d=eI^5l*X#Z7cklN+U*J^tTU}jUy?3APnZc2omY%B=54K?9tWi8` z6%P%AuqwsxVQB&Tepr@L`%H&R6}WKY3mr9Q4uG{PW&(cF3+;!C7??l4WUW92G#A1B zN&5S{Yu4I-wr0iT#hMjO0B*uB!h zu)EWFfg4LJWNw@Aq9}Q}J&GJ(H)9$!YjczN5}F#zZ3Pe2fs))N|LB66MPt29TFe_< zTFf2XWHEo-CVhJ~1s3Q4ZZR&EpNE#%jM{!1nShF;+cb$8oTn?Mah|T2%l(?Dt3)(= z6twaizb$qIwtz@@`&gA<{1^acm8r6M+b$J98z!Au9)jA)`Sf-2U(5TT)PI2r(gE&QmDRWx& z&XUI2UXT|4HLDP7{yWlwwaVX0gs)9htPFH#`PW6b2RKPN&^E)IRP6yU5q^;s_W)Gn zL~##5WlI$H06Z7`0xR5uzimqZ_tiD<78LJK;b^1SkPSA#6R~hTsNaD*3HD?spGmC ztq|*G^y0c1U9oN^7B>-oVvtOAs)BHljaHlBbQawRWO1(v7TG~ndC}q;oBnuSxY^v# z7t3f4w~uFlb1_|R2)C4Tt16S5LwACOXo@3l|;pg=JXYz~P=JxzzTlMD`*I)n9J=Yy&gEvI~XUYcq z(d}h}&D&o#*t-2?gR)A)Kq(&2-!Mp4>_s&nHpb2v<#>v=KfN z7+0)Hsy>EP6;)Y`^66c9$Esl??-={h5ZvFQi~C!20}aA3G!Mh5vI5M*Fia7{&rIUN&IZy==g+yKUIZGbuLFs8@JVjqqYDzO|-?t3=^j zYl_0X6oq>!3VL}?zYVM>sCkUUa{&OOakE96;C6~h>$a${%dUzFpeiZ=Op{0L{|Bkw z-Z}!Kiq$KRDweRgg+2iNgm-Azj3W@;`!g*n3_osq6~$k8yEgjjNC#u`1!*cNd0 z)xe+O^Ar^4dr_hi)yYjsI!KRxMzl83 zYQ%RDjnbzZv@C^UO%FZ<8v5#x&eMKm*Rofv7`ohqn#XD}#qTu8$uT0QJH-trnGIUx zPbbbdB(sL}uVKiqN1T2p^p7&l4AHU+#*tvv{c7YC1R?E9^b;fcyK@bsZ86a=WaNKG zG)6*yAEG0PGnZ%u#fm4|hVT}gBX(RYdd45?#jHo=01QkPh{WM%DD8f%Ilxm4+f zD%75kjI{=m`A`-`OwDRSY%B9ZjUcK;=!9vfIR#K{Se;o}Tk}ry2tbWl5TGp#Q5QfH z0lf-gDR5UQm%?^Q7}f)ROAIA9$%xX~X+m#n5aqyckYLy=21IVwf=~s;D`3flnozvq z>`_8%2os7$faiK;2?aiZ->{%xXd zSuCWojOYXj{rAblA^I`V--()4$ge@P9#M&6^&+aGxW|aLBl+z_>#}%=^#su(3H?Db z(nUmf6TL=Mqe6Zt>jHEd(P9bx1{vvdMBgL&BT=o2LRk`ojwG5Zp`Xb}gV}keVxx$< zCG<-&QaekA&BCmP10!M}*;ND2KRGSd5@PpR0`Kq0s0Aj!qLgq&wC7HYZx z^q>amqm~OWCZs@IExSc@5K$}W(Zc-|q_x2BV!zsZgFjI6Fv&ziTKIMVDUWrY_+5x! zr9sOs6F-&s#UwwFIKL3>1AI)SKj?WT=wYDY(4w?#7}1$TR}k%b7yoXVyEz!E4co24 zlAHo1pklA+O_CPAZywXSO0-f!emp~1I5giohXFZ}X1PW3kx+qYr714DNCfv_gX@lF%Hz z$AYK>p{H3JigyU7Ys&jF2x}9rpqCA`S*#xolIpM>4`A42D5bfox@?~x9hO4b#!eVE z5yE~@)o1Szn##@^Up6?{K0g{HHDKk*C^=Q1WY{1#Vl8_Dkan|OmUV{4ECCVwO4?=7 zsP1PS;H3;>yV(VGxT*WW6?VbkJ}> z6U~MQl&M~&YQySo=3yO7t5h-Uh(Jkz+OxT@L6|gOZw@)2>A;obYvb8zKkBGWVBh;u4{aj*-H(=Qy0GBa6`lRHU0H2E zdR5bnHT9!BO%jXtqgk5nECG=;2Wn-Nss~HMmxzF-LP?~sfuC{Y1Jsk{e}yT`VHH8E zRK3`Efi?lkU{4G5hSjI(%~lFjX+EIo$4bBB@#cY+{;X0hk(Ns#t5gp$D}0d+>O5dQ zrFodmZp_gTD8a#Oh6|ANG<(NlQ4L{hBRMKJSX9|;Uo=O>fO6Tzwj6C!O;_cy!uA|3 zu})VFWfwbelwhB(8paAcadc8WT~){~#&Ptz{!Mi;+Y-;w4nmJ40%CmBC}x`p&1bU= zuNsP3OcxA8^qye^+vZ2d4L%mzRSCOd7{zw_kv4cN>()&PyD#_=w$G28!R0J1iHCg^ zk{CRht@NY5!Bd#NyAoCq{5TuxN8^I0u}VLh89bdidMJ`F2G3;WezY-o7CR--6PkYn zKh5gGZ9mMz6Po?O&#-|4wbYylp2HUS(YL{K*%5&{Nk0eAW7Sf4Jcpz<&Sz-?JtEtT z&$1Z;4U+2_7qC5uU^IQh+S0g?9rmNn#^>2re$?OiBD*ZmE=!qlDZB1R(~ZlSE|qK9 zWm#-|nOXg4gK;IR=SS}vSF@%9=?tG3*D|L-&)PpRu4kD7O*NDmUttgX(RAahY`7mS zHg0Af;Jd;vuXl;4k_$m9RwpqjYdplB7w96S@EO}F(0EAU2)iUu1?1r<`^k@{8;>(}8n?3|XtD7mGx^cG z##5|@A8jz6VT}d)2c&S8B@2`RDSSn11|0dmSB)^7XJsO+(tOrsWVhaQs5^~n~J*yCC2Ye;mWwuJ771lE2Rrabt-R)(@Yiz3@X-z+| zoqkly^b7k?ptI^SSnl0~3p`W7awR8ydN`gSIrR9~ROayOG+YAVoIa$i%B6fTfc$}<_HwgRO} zWu{=MlR(AtVbk0;u+9%M@>T{+#Qr!$L z`Lp_>sjgJ+M<-47r6Ydyqsbwq!6$)mT>4pk)6`I^@FTUkk)(zXnPAxNkaMQ{r6GQF z(bQDhC(xkao2C}h%066jP_WwEQd-*=(NuQJW;C~!_7R%TCWcr6ZRy7)!%Vf!;gYXE zM-8=&%n{NNfp(}`m?Nbj19;d9YYTI*0(*B2dSa*A*xt$a3M;U1-CGFkQzVC!xjO0P+BWc3}}g!<__XvF`%WhbVHye_6N)f(vZPCY#pF3(jG#% zdWtu9lUfeJFut-%k`e^M)l+wAr$D%}>MqrSPh2UpT6d{4A#4Rn=I&B5A>InQOC7UO za;pAILn?&z5$Fd)2B7gAK`ZEQ?jh-OND@$y`5|c^q4{jK+G`#t)pBFld{(9|HxH3+ z(q>iOmZsb7JDUN9F*9fm1rmbp~24p-1h^9ZS0p@KG= zz0wgsdc*9KwiWTPB-KXqNNGl~g5EHXk{&DJs1U-&NSUPydc!|-q(b=22Ig;Z$b06A(#kT9j_KbsPmwmmrF;z2oA;ZiNk>L;v`Y1! zd6rZ$nxl)R{pLB+Xt;onlB2B0%nPOQV>#-kK4yMi8a6a%s8e*t! zSu0ge=g6w9XIU>*%-|@@biZYTRQ?o44I%7RsdN@cJ5=G8O;Y7-j#gO1Ew4$|XE^F^ z54XG_vH1#$vAij?sAt(KT^8tAaJXfg6uXdzEdunmG}=v<}d2X~q&n*gLve_DD0ABI3Q{eQBjY*gN)0JD2e=>>Ych*~<~} zHQ!$8XF|ABOttKlEH7agqCS?7r22mJuw}oLj0jdbUz>9*2c+_sIl81Twj7d1ui$8i zq0I8Bl(teqk6I2(bKy1_#_JvOxaD){yg;3_vnT$3sUiZt!F{3Pw% z#KXb>U6*oS<0w*l)N(^=^*TolA-$WDPoN#Dqn2CJA%Rv{k6I)-3YsX|+1-BBqLOd; z(U%sD+-(aFJF7lw(aE{cd_fZ0?rKYryjGxN!AC6y+4vR@TLj1`7YY;uI?Zwv+(N^6 zG4?MlR=HfD-G-x<5P2b71;Vf;_6wG3@}~l=15`tVVcq^AIzk{Fy*2G>EoTEbhJjw5C4Np?ogSm(Q=+ZDSB$PpBJd1R%?xymm}gMiZwxA??(?=6Xmx_65pz1S-Z-6co?93 zYd5+5PK<|bqS)G99)O6(keiks@+g7k8BEp``E`LVg_x{86ljUvXU&#>5@;QuTv@#v^M`GByfsg*BhU&BBBwyvDnwlbnnGxxKra&V z5#n!@^5j<$iI$uv?-gO#lJn)`gm_ENmoF0HEqSOs`#p@uTk)+>Lm-N%D#qxN8np6eh}=k9b&j z`)uoE+3ZJ^)+usLKiXq`Ty_X_Rz2G~O>XW-mDcHUgg_0o)2uV)_J~B=eM(OBqy5%d z@&G?NY<*fDLGgG?o-2>{qkQW;`8r8rOFn6RR&M$+x3hQ1MC$@M6A_Hb^9-}Ci{x!0 z>{7^V>+^EM{amurJk9!o++Cm{A=9ic${voOCC|22$Q1%D0<={ANT3)fiRJPQKdQ98 zEE^7xP8gy0SXaum1lkR$ua>*`QKfaQoGH*j!!+x9o(FjAx5R$dx1Jo*!i$J_NA@>zkfUB4kG9KtXhp|4oqkoO35Ky}6XrX2Gr54)tl zZrv*H5vZ|=*|y1ZKjUFxreNDUGCRx>tSW6g(=A)DMX@`pO7#5QL5#n9DG6v%e9@6BmJn4<(xbO z5tJ^D-e1dwew1%LFJB``Z1aV-Z)N96o}=C&SF9K0LV@NPde|<>mj${M(!=(>?D>LA zR+_I{ugEV6G$iD@^{RY?BWUwIY(L2MQzQv*!F$+#lv4$YfpYm-p5{lHw(Ih2RziF{JR@3nA#|^5e*!-EbN$u|;4N zr!pH#oTH#YW?zw>d->54nfWLcq_VIrP@Y>G$hnvQ&n0>2-=p_R@vrc|rgB3`l_-rr zw=+=ZUzZGw^;i6d)zI45nGhMQ%B6bzKgl@*tw{{Q9wS8Mj}dig7yzf!jUPv*~i z{eQZA{(7(Z>ym%P=0Df=S3Q324@w1!Ut|HRRh0<4FkWH)ukDsHC$_x zm<6cJYLj23LB9d0#H#!%=AdzYIL#4AWx=UMl1LR!f0*55HlF6VD!-y(A8A0U$k(^y z$ny)$3qO<1xWrYlR7#QO{axZePB#A)|KE#sFU@>LwXA4FwtpfV3;*MM(i>A*L^4KBvaYzS~H>;_OP8vuM|M#f8rGP?rvg^I0) z*-*_U(aadg-vA}a`S4kH3ca&VRjkn+HLGH_yXqG?>VKh@@2(v0!;+L)1z4qG?Pv|N71EX1Lb7}i*r{R(#Mwu2cT!rsg#@;0?ysf6xwR-* z2Ft61Jg;RLIOwlWE3W_j_K&MpzAjZUHLO(C>}9I8g;Z$$LGzL7-nAK+>%bBY z^#9*gn^<>B%lvyik=H)&iM&+#D19&gz1rd5a7jb zz?uWK!aZ`N?TE$^O(NQhXn&$vL<@+H09u`m0a^#{%NLs+aK9OKt|Iy_(E~u6uy24` zSpy00UN?}gS&D#OCi;xY1O5XcNEaHA|3nDVVc=f^u|@;!0(V)P!#lvofi5x30{WO0 z&PgS_FOBqhqDzUcVjI~&%|`Y)rM{48GToR@rW^CgbYni5Zp>%W4f15VRi8=s$TzYj zcDwYngw`fAyg!@_8U{%ViL*rTi_M3Df7UoiS_Pas@OcX^k2iOd;clo#l}z_A<0L#8 zOd{HgXn&$vL<@+HAUcNVM55D(+N2*ek)Q`}?goK=Eqhb?K%Jy|Q|fI_2Tr!SkLtPz z%~n6GT1cnX$qa9GqRn%G;+@W9hIcuUvkWNS?oDQRpEH@^eduI{w|_T_oZ`(~hy}N9 zw}Jc*s+~mP_8j;R5j{@yEYXWZHxs=^^afE(>j%(-cTd4j6z|I>GrSp_%LSQKtqdkBfJZuQ8-&iEbu( zgQ!}jCVGRY8fx$WIwS>3H)e_z!S4-F`)DpH6wS@P>)e^Q$%#j@=FWMRq z4JX=>Xb+;7=W!kA~OAXb> z^+wpO%nWMECbLEMFc?t}*ur2@cNFM->^jg!Ob!WSEtv^uB+&A@0#W-vF< zhu9*ZIczP^;Vc=nd09HqVxo`Pv)FV4{IYf@r0FID64vN&ZvhKSh2PM3O-m z>L7nC(ILbcLjH216+|nEo+8Q&6qjf$(IG_3iB=G;BzlS{3nqD@Pm!OQC@#@hqC-rmVF>xl$zM+X3i4NwzmoiwLH(NjcO80jGzOLPd) za-tPPD~X;W%EC#WXn7?1D~Ylw;uDP}I)rFB(F&rKL{AZA(Iihamgo?orb+kvB zSfZ6gPZ4DgP+X$1M28S9Ct5+YlISU-tOLmtjU`$^R1a&IXJBuLZwn-#8r)3*4_*<% z_hG^719(pcF9l%jq6KQe@5qAQ2}^lc@nAF)@bEnuyeNS+54@Ct6%V|QfmIGHb74IM zFO_g}27lE^e|6GdgY?%V{k2GcZPH(d^xsGN>yrLZ(qE7C*C+iB(%*peHzfUyNPlC} zd_U=ILfV>=o@S(>ImK;3v09RRE0S+b@?j(&PV!EYcaeMq$w!iW6iG&tOdASqOZ*t( zwOEBpb-H?-db)b8`fc?d^+EM<^;hc4>R;7rjZsrg zQ&-bi(@GPi>7a?%bl3FK^wSL1RBGPWhV*JzNAv>;6}JO}g#)e@jr>@NZR-o6@W zQ1E)@ACnx{fKjp#C> z@j&tO%PMGHR_aYQXwx$Egdm`HC<_^Sf(dAKC<_@rqih3uKZMBexiRb=P2uy-GVBRz z1FeABPiEWTv#~O?iTXg_W(}ZZ-vKJK(>N=_CoS&>`X$7Z*jb=5d}6sd(3Vn5pvh8e zpea%~&{W9_3av{rsi-Xd?6-UWFUtwdY zRU6eI>RRf0>c;BDnrC$Caz5tZ7=t6v1-*?<%fT!D&gcs;kp57jB@8BZD938hnyORJ zsRiZQ5r(Q-P7DZ=e3++M=X5N>bb_7!e_;XWkXhlM*>xLLx@5w2Uf`NAy_?r`B2 z3AaSJBZTV}ZkcdLftxL*s3rK^x(tsB9yL4~c(m~7;L*bq1djopV0eu1nBXzPV}ZvC zj}4v>co|H&%;fXIP9_jY@GD~_VVezmyFHc!OfAH<0 z9Hm5o4tPXvxd$={N%FiYX-Du}M6jU|0lA1^r4b^ZAV9f^P;vudn41X94H!$gi5v~d zkqm$gU_wdlYDo z0`1_!lM)FoJSkD&!s9OHNvj&(>W$t^2%KL}rg3aJn60w45b8R_sNO!;o# z;NHarzP>EpLh>r6nE=NOh0uT2_$DI;6Y3C1su* zH%dG>eDL7TSvkX@t#m1H7v>7{`j&XWUs5`_bAfMEf!7^ZoXg@$OWoiUyM1U_E+j34 zq4vE#PgX&(JGXN|VPSSjRZtbqEAfD(7(L6AR+v@nR`SR4?qqaz`;tH-W}b0NMF}Mo zj4a4?GYIdN->3vkFDv#H z6jAAPgM5NwND#}ai>IVWaCoxFh#Ottjl;o|WfApvg$(L`e&qsUz4a`1v$0)7uG!dB zr9@aMmJ66ylnrIcyn{=CVuZj7V|{R(L~|%8zRcr+aXY@O&{yVhvjlfeNl|Hu*Ud6L z&}hk*gaaRNQr-E`Krr{i$`oG?aEg(eP~wX(De>eM5Q_79-9_1jV^Z9{tXvqX<9sm6 zW|x5+bUgT~BX2=Y6$|VwEr8b7)m_Y+U{z3WOu8Vi0LOZ)uBs5fueUcVKY*X=E`z32 zsN~q245eCjn?!;;yDUE+HTrcxA#-~(3w(dX?gHlZfii&pe0Q9rVrZJsHXtyo@Ghwg zcTSn7z&9q%?I|ko-mU?9PGL#T@N{=xAq?t&s64^#&G8hJ-X56X&dY-ON`WGFd+s(! z1jiK?WO=JXc`1{b>F&a;(c}vVSBfCb10!6HuPUMv5MNR{##4|#G$1HGIRn)~Ld5~x zv@B0nQJlwH&ByC ze_UZ93^?8lw~w=EO(6O>)PT1X%XuU;WvsEng0Z|0;AodxR#*sb87^Yb50%0)#&~`1 zqOeq8dJ1y9VK`_&r^JMm_Qk)ri)EGw4+IHcF0UkkN(+2BL)lO$KAJb!sDfPIP%$L4 zlI&r;yJ17(reS{)zGAYKb){*=lQ}f2xTgmfK!p(9>kY(ri$HnYDTS;6!9WT0lV&ga z?#w-}32#^~gjEe+{j)};Vcyb9fbBt&k24FDn^3^AOMJePqN)tC%&cND9tYSVI7*gS zUl^#1SxNzj!czoKcUW=+B;3ot$O~90;f#ZWJoYp6_b$%zjG=K08E6>|YlYPKnfDIN za}_87LrJk$NMmelDPpDTO)AcySun{PkPYasJ{$rux!eFhSFJ1xprH8bKq=&|@O^IB z9(hD%%!s2w0rQbjmJOzGDC(tZ)DVRdz$nCSno!{3qY5g3wLt+Hl~Yoz__Cl`XQh>R zc>pv*5ck8;e%~0sZ>--}s>BLhAS8~?anne}L~P=)Q*jXG zvpX@eXpl(9nxFPgc1Yt_^ zfLR`~6jL}QzM;SugC>k`-Q0zxz@QZt4AKguu_SN_*)uOM9!5SCrLcg(vMLB?@-$rd zgP0qZEzr{Q)7|;t0ftBM^egeOTN;BN0FgL(cJY^T1|?YHg{aDkE)y(;6+O=l43Dt#F3Za+$SI(S6gyYX zJY~pay|Mo*h1bJ9MyZeFl9J(ZK5VA2poFHxSM9u4iXJ0!sEmZHC$$WW!pF0$!VK8L zt!tB4$^0|r|^m?aqB zc&3+t7u!GVUa~ws@iv3Jw9P;{V2fUr=^pK4W3oowJt)y$vu9qP0{1AEhx<<9=c8A~ zaA2pEDDg6eX5|7XgeNu2;{hinYc#we8O>mm=z%5xGYZ2|c67k-pv=|$C4+dM0^Jgp zwf+VQEf`BG1q!gBv~Y}QqJ9{#E)Yw$xZ*K)t;c{BAQ&itez6_{AI>Yd?1Kd|`jd*? z3QF?wiFu?OX0QUN5wL_WU6r0sHI@V`Djxb!`toAaE|O|FgLOS_@&=s_=#2}ej+P` z5GA&kB|&Hg$drM8jFSYX&RxL|`(KFd245x{N}3Y@c}Zh2g$d2!isn?%Q3(I;v{2!v zL;O6(nk({|a1*E$c&U&&G%<)>3jazVMNBuBu{cE%ZS+ET9{ei+D+?eT^NyN};C?(> zi6wAn-k515SXu~DXgRBn)@PA^)R+t0(Qu)(D`Vx8$FXrx%t%w<2*btNgU<<9pB#iE z2)h8|KSeG=$VCXb2>3Bgj2C%_T%?eT5^|A3F4`}LT3JoZoEyx|rOfmpGzz|5xEQ!- zb{d3X6~pcx!gywisnS@3yniM!vF%^YBF}OpWkt!Fi$(om)|4VpvZfRPW)1a3Kw)5! zwDDuEr>#HhyvUUziV<>35w-JUuE*uClt^I_R@Uv+5+#b;1*vl@qC|nasA|xzC}9`o zOOfH0x!{+86n#`J&Cx<^qaJ8$*qW-EMgsYj^cj_Kzku-m{%{AJHDNq%QD5>a&4dLP zRJBHSkgeGsZyDNS!{owlkJ*dXFttOH^eP9FG~lt>5So$58iy>I&BTo(>NZpl86#D< zHJ0p5i(XqR?l`#<#hn=Lbl^@bcM{Z0G8%!HOmLEJ0t!il^jGO9F|%3ckY%&2e4&=f zW^-s=kTFBrNbACxJJ7mt=#CD!5YJY=7@}(6QNyEyM+;97JbHM7;W5BtgvX@i2FqH# zPI%2aO?6u`WWu7c!0!zK7lBG^bI4kaY{z3sIi!C`{|9yTESf~CMsHCkTGe`sF43yf zTeOK*t=4c1!>iB^N&VobCe^%hg2)r23I)kC(VP`K+-s*Ct{ zR8d^Af{Y*=$ON*0450KWc}iUi30ow6x~>^!l4nB6hLRB_D@taR>?j#hvZQ2+XKSpk zLnx{Y)#yXjpfXgg57mLnP@O(h3o1jk`p_Uy85*Py#S#kD>qCPvejqK1i?E2kQ-bquvA^MGx0LLdvH> z)tb#A_1vtAGFvit-vK*X$d0GOiUj=71hIy5v3HX&#~(0Cwk&}^X1K!btS zqUUMr86|CJ*f*f^bfT{zw8P<`Fp&!@Q5g4FuuWsfv_N@*UP1;qNWU!-aW!C-WVJa6 zsEP8(TJi@Gq*epbYJuqS7KFiI(HWsG zHENwUNFQu4njBble^yLJouNt^PQc;JTL%%K)#&x)gLWf)&~k(i+LQ31L0k??i2I;b z>Ge9e!GR+IxuA<&&_pi$NHMvfg62!wVxg%%7& zP9#9JZdVdl^GG60cN-Gpst60ZEi9NLwGL+soyykAXw>U8=)iQKlZ{4=4!X)TXp`8P z;C=`?FardGP^{4w3;&#Eolfxs%Za+2PzN?A=s=y|qE3WHXjB1zHYWraAr8$wG9)I0 z-vDt@F{n1_3_Ko1Ld`M3kXjT$o712Z>TJ$hx*7qB1H3JC%>vM$dK>Gi$rf8|qSY2_ z0jn}C0BlZM8aP2gK@b%m6`ld{EXb!ueP(bWp%2l|6x2F1dyTNev0 z#%RPc1jh)4;Fkl2F33o1WMkCY`GJ`sV!a!!FKx{5RFe!pX@CpQ-oi179!t{L|y_o>+9SjlF?9JJ{3VdilHwJ5P_Y z&f2XT)wB8e2=g=FYA&8V-=OlP@6TV!9hhvK^X{63%^uEfwc6b(^w-Zi`W`N6eeTMm zgEm-p&hOZy?vo+$?pltb$~jRf0}jqnAG0LImM;mD${)XDM;G`mCrsJqRlVQwzn*F3 z2sm$T540jiI%*kn+?TXCH%gK$% z&CQK=RhKelYsT;bc>9J2bMU$bUUf^d)~M2k^n#~9+?AH0aAp*_P&4r&NW86v@_ozR0Hvo^y6ztF%m@TlRzA1%?s69i8%Jn%aM%m|MO zrz}`U!MaqgTaBq`IMi5iXwh-1FgXk&W{|?0UGV~f7 zsz#-`1B54*{sFv$71yc1@L8|d8C}wa)n2NY6l=4&sshF!EWvL2MkyJq{<$5@BL0@w~ zpK>r;2MzQSTZbTr7B*Y>r)4nh!Z6HaTPv6(8O^`84zSWhHppYR5f;5bZ5==j$jAV! zwpK6|gADu=N?P$qz#wrR4b`-QM8QQvoM=QW84+uDScMT}2@wiu1s`dENsmv!v1SGn zw*p`m1A_qSAaN%|fhmhzTP&tda%i%XOm@QDfVKb|%;thd1A}pgr)WSWTs2o4qbP~u zIEpgsw89DlepJi`o>;3b1}K+v+G4nz6U)Mh|FC$tk0(rq5QhrEsdK0)(^}YmfmM`W z__V#s7FA_Olnv7bP86(8geL~n8G(@?T+A1VklH9LTe2m_hLW}z(2YfelQ)_e1M4AF z0KsS~I1q%o$;V^BypB47LpoznX9tP}?Esa4gHgb-bwHEJ1C2@Xl0sy*QpCu7EeT&BK<;HI3@n3CqZT*9Mm;5ssZu&<0_K#Kd1wN(3DiwZ15FTZ1Wh0>nSee@ z89kITcqnCDFJeMqh}NOPilFp(VS*5*$4fZDmVoJjgUNt{|1cTyP%;USFG?msNe{yW z2eOQYVvz78RCzE7R75r<7$He8hL%0pQ81K(VNQg-gjx@CB9i_pJbn6nLI_#MLK3j%qLSUgJgbsp2+kz1IR-8vzmPe#waNvUyN6P?lRAQTSt zbB4ADwT131uRA;<%;kv9@Z{j-qV|rVK3}P~eRz02921pghvk$Mh3A&|U;!1LQ{r*w zjSlyD-0tv5XI3_tke8Dk6&W2B9UYOK8x_;e)yCB>%bA^<8{=|2-PyU>@akEEB@UlQ zVAZhz{P|aI)IeERmsqekXv7;y>P%0Wn{(43LANKVIJ;!D;KJ2nQpuwg0>bJHN-BmU z-vYd#2Q_$W0))bEbTKtu!2o|PST23E_xrao^3(7XiuzPe;k;prFWsF52hFVkeof9_jx z)32`H{CrVv$Xhof-@9%!?2oD-0mu(zBp&?d;AsGHA5>o2bN639r~ozn6cu;A&;NJN zD(ZmpSVj5YJyd}9z`_(I%Gt)9nN|4w%wriuX{JQ^XU87&t1z|b5X><7>rq}uL9v6M&^q|xyQ9GC$mjQy96q?91jOwKxc=gxV>78k zoX>&X&gU5Cd_>XK%-bA--46F?yqx9EMdcVBZweIQiKL_9IM*W$!z5QGaDg+lB$w>W zffI8O9|e8Yk>x0cJ6k*%p!^zFm{dOlbiytL#C&k!5zQ; zhGXBw&xSRH=Xj>fj<=Cvo0^dE*3pNf1~>dMar=(>wO)Mip|&WO*nVK#z`;1o4vcI! z&^wTiTm#`o=s>tkF%ZraduD_IjZf6K zZo@?Jy!N{&9xUb^&E}3D*y6{ZSKQEVYT3K0h!fH8_~UG}4UmH$afX>Lp|CIo?&UBV zD&20n6Gw4x-49CO(>edQ{Yhjr{9$m&F%@tU&@SM|zwPSpZ(_^VGBrGN@`(wAfA$YQ@r2)ka{FHbKnb*F z!P!_BIFiJZWgJ%X;Is_?8dI!H2*qPdFCAa{PozoW74>7FSOS~i<^yQJ^Xt1c4IvpP z76~RA#CJg7iplu8tkK#FZm^dZIGh#eD_5!C5$mT$bc&3N) z$?%CDMNcW&ItF58kxn!-g<`^0GLSBT-1(qUVX7W+UX3T^;xwG~Bb$^w@H6uqN;}{* z{%_dW9$)nO%La3RwkxT^5)Sg!2A&9@(QpD94Spy1@vk3zGD)JE;V1fdeqR`n!+)*3 z!r*`!&-odf#KJ*uGT4$&8nH}Dq3p4w^WoNj4`fzBS^f3$3j=8f+#o>xjsQJgLU@Xg z`5p;07xIUHLnv=Og)Fup%$rjFh_Ge+t8JnxkD2i4E(wm_vG#M}CIPmaKW`iB!B%W_ ziEwkm0hVCvM;k^_i-FZP{5I{vyL8@JqxY&$2a9E$!4B+gKJbl!vt$R=NEY;p)^If- zfqHvS)|dKs9Ngwegq9Eu-_ye5^4^#O^@B0-rU&-hEGpY@Xg^`ldQdV0!g8pc<4p;a z#TxZN4SNBF!@qp60F|f0ep!LB0JqW>@ zA%##r4(jiLy#>qG1rNG7j{Hyj!D<6Ph2Agvf5MvoXT<;E5_sZ4NKlrUbJBz3v_UPV zcANUMNz%y`lO8Mt)=>DlGgq)PNULdSR>^fV%$ck;wA4y!X;NERQdd0a?BJ{!z_B=k zR1$p4`wSf^4@B_-O!hRFvABQ z__R9B$kf?&>F(SjxE$)L=d6nygZ+tndeUaRVLL?(QGt96DCZhJe@;^Un4 zYMNYaob8-#U2UUdqS_7s7!v@U6JPz0SY~t@(6V5=Dz0a|tEuyTj_MW17nH)D-;t1! z=t#^+ZSPD-jAwae=o}s#bm?81H!8u847+AoPhN&h=7HAKWL7pT@ zY|9Vl+FsppCHC01m`kOLJLNS${r&Y@U+!A*N%iqpe(W{o+N5`0{N;mpChi&ZMVPns z-Wk>QoqhIKOVa+iPc`V?>dW#_s~7mS9`3FVe*1g(+>SKHCKAM;qQjm-x#sG-P0E~KQrj5N1h%!t9;kN)3tbaHrQ$GrBlTYUa$X~zX)cYT@v(%@=0mwr92 z$?uJ>tL={B2XqSN;*b$>Pk! z`1RE(&K}OBimnxjQ{(Yv6TEB6@f3y?DR0r}l~d{P0_295@=s!U!~NBW&5~LrG|Dg# zarV>dp;Kz$bW>6%JG(o(DcC7b?I02uHEPr!B>=bI@2%PAw4=TEtFZ}+Bvksq<^uf@ znv29rcXWu4zY_P*6}RKZM<0mX^5DFhzs|~wSUBs`>8a7n+#Q$yG;sIcYtz5GvAyY= z`Fkqu>)w3x<)h_~9BtaRS@6)>o6o*~q2Vu<`zLMt**K?B3SCm{4)?)axzkjSf_MoBZt|jZgYSbZmL7ZoJ{Bz?TtTEv5ffr_fx$a4Q zy=$GHkcRe8Tj>1p`WIb_o~ZtaW56ey63bGig!-;MwrIqr5u>_SD}JnBy@Fwt!#?_W zeWw$S6Yahn_uvB`RQ8`U_1l^a_YI%PZp6SSb_qtYPpd{TY0UVrzNzV__kIRz1162R zqtu*Z{v%^WBWDBN>8swm!b{a$w~!s&9kqWL&h1)UQo|3_>y zIfK!*V7oe{XIi>4Agk^%Apg^WX=U@9hpc;U^)|e)kg3vV9e?GW>uY}Lb#vg1%pFZX zN^Uv-!m){UKbYEb%-*s`x8zT5GIjfB;dLK;uixrfCG&n8aAU>p``-P>`Q)0Z4LiQ^ zP@jPl4iEUT^;=tC4thsB{<{S?W3M~648Iv$;@h<4vzVm!_dl)+cYX2tw$x?&Uw9*X zT%Xdip84-+_AOigHhX5reQ8Uw9nGH z*|F*J^L^5v__+VVCG4>_&+Tt_u!Sl7EA^Ro?DL*8P*KMKP;Coaju)&TUh}P_(?OGtqTN{X{tcN&TneN|HM*I$u_F_5*w0y3PB?41POt&C84SUT^-whcO?&xkvL> ztKW9s|ISd;7nX6I>&$y^*7gCzX9xHC_3#^C&w6!JWHYa7;DlveCOyCI^){nMKJJ{P z*#-mNaz5Z0vLc({TW-c&cNCU$FfGswLC-XqHs@-qT&Z>q6&$FzYU8>wfEnbU{9LV_ zEwSOL?2Y|1o&!HH;)utOYSCpAwn#s%cf`bjC!wA|l|tAp%CT2mvDy{x=8O zf3~)I)zg1qZD;N~p^Jt%9Gw@A%qV>DzRybbeQ@R5;WwYHVLkOld*9?bZ-iGwT)cI1 zN9WW=pL*D_=)Q(&d)GUXu3a9wHl@eRm$r}TG2*$dx}(3}|Hb06Cq7>7O_*?a;;|pL zUv0De{eg+cH>~S;s@c$I>#TUmIBt|=BU}~R9&01(8-so|-XU^9LTW4QM?D+LsJ^QK4k{1py0&Jp;Ud!1oIAEbg|k%)EBekk~#=pFMZK{YHz^hKzar&n|bS@v$t)3BzYad_~;U zPJewY^S5^oH0>N{*))n}Dx(&RV%?lwoCyksQ7q=Kjbi*D7CQ{~7tnh5_$5oMf4c`K zH0iS8LP_k#h}VW)FozefO#1o4;Id1}541iUzb^RZ2j8@IEo-!IeAT_z#lRf%go!9yKD?fby-Ir7AM|IfJXVK-1MpIfWpHz3wnYp^qt7lSwnXzQ=dHYLG zr|!A$;4II)79)zDtMg0U%Nd{L?`wE#VCaWSXKZi!>X@892}^r@c>SAY{ra4EPEJe+ zAAIfT`a_caTm;M^kB*+~z_sZD%-M1b%-RJ!N z(erB!d_TIz;1hE~lb&k*_PVHqy5Cu=*I^Hzh<>QSM+@Ey`fiGOM$aO%J+{MPD=`cJ;F*6^FXY3#bQ*H5pS&}FM`NLTle z*wj}#UrfEUY2=tAhNz&Ty5+9=XUv%=&Mo=vTvzM5+y%GNYJ`n{N7G>JnR#(d3;r=@ z*1Y#;9(k_8degw?FE3p`b?9W{u-02g4rihB*IlhK?xz})8^8I)$HP{3b%ifF{^f{{ zhuNdqT@QTp#QR%n{c85ie0N#L4RY+TTLsU}KVw~K-P|@!|Jgq}Iwxs$Fn)ipjNdhe z;`@3yclvLP-_EwqC?^cy(UHz}IDWebMmiDx_qO~0Z1jF{N#VvXj&*yc#rWZ2wNGz9 z^X0CEy&9#h`{+ci)W(+Y4y-tkyw2xz*e>Wk&74;~Y3_ZUpIN_PptH$QHvIg!?H8x% zEI*so3ocLFSO0^^#*Z()dM&?hs~h9Kej@bSuTz&TdACu<-kHB9ejK!a(1!i5bXG6D z{&L}*{KL(Tcgc8V>i%=hx`Z`dJGE!;bmLi7tKWvrp6x7t{D=O|#lJrK*|VF@H+c5Z zUk=)T(7%yUl)gD}_KV$E_pW)irp@zKK6~~P?Zoa&uRpfJ*0oyDq!%B%)O++zX;EmJ zehRZXyIgwXWTP%ycec)aaYMb)ajsGOp8Mi~$#a%u$*+Z)Hs1L8xmTr+8urM%bzSq1 z9gbjS^j-s__liFoz5j40eB0=4^$)@@dc%0moD&z|*xE=vakg{f%<^h~H2f~fS)TPD zUtUbIj#*b@N%xAE)+KxUUDMga-2c~%`hRKf4rOh9_Q`(?R3)@I@y+ISqmF$vrdNuz zG0ZpOp(3Mw%|~yKo3$nEP{`65McG^W${(aU>}d;6jEz0hck70Ji|U>Zm8P!UI{NDL z{TCmQzB}{wEQ4mx%x-5cXH-AYbImhnzn(el)ADz}ntN3nK1KD-(=8e|EdA}L8)ru^ z3^V<#J5#!?R_fxXh8sNdwk&D)e17X)z0BWc5A0N9!E{HbGrBqv*Y~-)k92ix=?UKR zZE43_Qw;Vmb{Mjrx_o#`%?qj1C+v!DIcV8C7q&eT++r9yPno)}SD*8XOXn7@ZI^uLr!nuWt~EBh`S(koYaXQ?RVVxX zj`fP_Pr4lZUaPGi$8S1!{o*69eYt$4FM3Prt`Utwnv4wYkUnF?fG+XXwr$$Lf&3t|1u#VG~jLh0xyu|+UJFB~14k`Kl$%w*NZ+_8h z&x}Uz=WSmc`glmL+_CkB{3$(`8E87PGP-k41eFg?046sX64RF&BnvKUMF<7|K5uUdi#6Vdy}e{&lMAw z;&M&Aqz+QKi~+Bn)$o3|>eY0x%j^sct&WR9C8F8|i@}JXlQ%QgEp9cIt?BqpwR4Yt zTB`Zg**QRk(FHr;ifZK{cb_9JsZ(C-UW>87`4e8IswctitPe~BM;j}exQ88Tb;5Hr zMU$5P`x`HAeKF|uu0Kuhar9u^{OZn5uU&k+`SMrCJsZ4p($o&GonQX&m6@X#&N^`Y zz^d*28a6zY(8F{j@8Xzxr=PELF22pyr$1mj!%nRk{K(7uSJ_Ngpg7=$rcF z%`3*Y-alQx#r*FhPK6%dU1w2i)2UtGZJn?)amCe7_ViycVdIx=`YbN4Xp^X)c4kq= z`si(`zTE>KKB)fqm#d9eKDzPbnLWp{uHG8hD)HQyk2@yq|2Xl5v*nt_*9K^duFU`N z^{dl&kF7i9iLS3b_3Jm&&a6G1^!AIbto18D*N>jDdPrql16_k!sZWL5;^Q|qyu5qC zs64lV13;(WPcX*OWiG^6Aaj3LL919h_~5IWg|s)^0J4y2M8p z)T*C+=8UG#;n4i#w^#HYP~l3P{npDXnk|z?)HwLU<=OY0|9r{v&bwTi^SjfQZa&j( zRhN*-uYElwaoyQ<^B0BdUODpQ#s9Cm^Ny#o|Nl6SJtC65$qLsw9DDDPk!;D{A$vre zV`LSwsgRYdB;__jW=2Mll${yb5#o0prPO_QfA7cl`@J9c_xF#-`CP{}Tt1)c{dzy& zulG5)s~7#~=12qH6}1l{@H8ofb>+M(C_`fV`zS96K`_q91&1oZ?_9yd#ec`?%Ns*N zifI&DxqR_aalD^GT)K#xkIRSS;cXd4se6mlInjF#`Qrl*UtOgXx3)60i+*2KxKA*v z_qhd!z%AFHZQ^>w?MPQYr20Fq_x`y#|dhay=f z!EK}8?lr15#nJICNwlo)jj&BBz?ts$5g;Bo`bf8QCbPoY52FY8kWsN%@Jn2c;w zRtkx+vt7RvZ=%8?(`%Ze%n&u+5{194m z`v(fT>F9rz0KB9te+y0J08W7l38W+p-zmw@q?Zu|cn<){SKJE&U;;;Q@4*o8_*<^; zYrXB?#=GH>m8^|8F=;`*ZM)bkT%z_j zh7yb9y5^9w*ph)#?iV^c@S!f(8`Jdr3$BhGfw*2CVlQF*B^5s5Bv z4mf)_1`)|2OaAo<4(nZiataSnIUJHG4AA*?r2GsnX9yCM%-AOx-snHoYGcv7bJpk8 z;@0P`+Wz{LPc(Xq=i2O*u=<-i7AAI6jT23e3v<+CDUgk&`G(in6uzl`kD<%*Sc1hz z#;Ig$1?$RvswGJ=t2NWW2#G5S0;QAk?TqTgu!{(lv-ADS$7RFt^39 z<{_d+*=gU|&_zh{?(0L;8=QkPVecQ7^J(dsV3(0cx6xTXb|;)1@H<7!m86gl3zkfh z)K~FliP$S6X^ou1B|qEDZ3HzIaY(wG6YiMA;j+?HGGD7e(P^Q9sL-KSx}K8jRPW5* zvc=BOw#ZmOP`2`%AT43S;g)@Hmf$NV#=X$~{l>~eV=!@ z%rr;N@tIF|S+!$K@Wrcc_vUkVGKQ%KM0Ty7IZ5J9`3IL3*JM{+?oQIn@iXWkRkG+~ zTG|zFf)Z@d|dtL;_uH@8!o+M+vLl*a3M6Bf%p-Upl( z98~VkC3&^g;x_GxZIrkRVL#C8rg~Yuk3F|(AY0p)1;2iqo< ze7?7AQ4$w=76jzu=xX8!y^xYiO@lmHm>AcUKP?SobVKd{NE|ZP>{3;!@w4ZFPug2Ulw|LO`9+E^|plbDz0aJ!74VUAbk=DIRY+-6lm8!r-Li%AL z#}?8-f@R^Sso3}1MEcQBKxJP<5cyhNy^>(Yda?Aj%FQ{u3w@*M=N{mnZY+IWz+K{p zSNbZ+u-%LrYtF`N7N&iQly9aGSF5{R{;|nm6EDQG&~B~6X-)Ke*2#6nsxe`!B4;bO zPhO@aG0zL>_^rti9HRd7zPT!#Gel1^$-JK=NG$D4avPB_sOxdu@pBs^6@9F7VQ_I# zE-v`>SVB71s~}<5Hcgj%wCkc1uGZ8ud9ayMay;r}=MZK~5m6QF z_Ak3pLd%k0V6$M(;4sdia?3P)g3aUwlbWP6?1;@>-ggx@t2bS;TWJDm9dq^8O0%8XDdF#@uFDN9z<*o0p!_>c0uTwxF&8 z07j@^V88?;5ull=o~ah5^4F^X;PM66EePttde*mKXA1C}!e9r%?!sYk z1B0D5pap0gj=^9j`>Pa`@4z~880r4x_#OZv19kC?n22NmkpLj#zHvBS2xtQUqz+Gq zfqs(z&rXL5-c~M_?x0)6-UG-(ija1pBfIeTs z*amA;-qVs>jc&b8!Fx}nX7Ss_>XZty*X-X|)-l6#lTEBcVgsTSwR8swZ}|67tI@8B z%SLJU-2d!2DT%|&nLaH+ll!uQ&O1S5V$SNByo8S*%NkigUaUv(we^lu7>Zm^BT1@r z3a|*%7wxv~coQ?Zq_`XnR4f_r?46BLlHLWc*T<|Wa*yna_0+^1IyOg zw?+gd%DmBv2am7Co2!NgK#(irFFRY`1b>|X3LPlgju%*`v7-?q?T zJ{gs1J;w22#F}p*`D51kq(QWSEX7*Gwd7npHD!|2Ach_^&gQc8Tqy-_dU#9UojVzR ze$3m-i4281N-RMex3}sXAFCx#y!Z5>UYHk3^`%tXA1G(B^PDc--i>-s5HxQuR=Nu; zVxGA^I_l|U87I+uOHV_yE=Zp_%jYM=vve3)01p!X>A>}cA^brOcqFyVL^OadMf^hm zMTH3kAvgNJ53~5brNelbP-aZVm87{_wz$O?VWu*pW=RF%w??lAJ_r}{l^k0(;=;kJ z{+qxSG_!m-hh!6D*?a7%o|Uk@TrWa6SDDfM{$7O$X_G#cd5sxDFwfLwRNlQH@7*Vu zM-^6ZH}rVF7DXuggunYMv1lgzCs(m*{P-g&A7qnH4kcl8k2aq=yDZS>cZF@Luxm0W z-gSb0SYm2((sK+d)Fmhw$a(VNg}^E}8=7x6AY*+j|Jf4WN*`@hz{7`gCPwqI52Y2K z3~h#w;OV!!O`V?1DY2hC>swhJHJQUb$|7~TBZ$`7Ca3bA5C%d-Fl0*ELsqfb=t6~7 zfN^Y&Lc|13$c;ir_1Aa(lV@xTM%$yfVymeNbe*0SlUD`uSxjJy*|JqM+JBsrcMIsV z1)Q^xWQqWi>^n7tKtNzY1_QV8e)|uhfCU-IzsP7X&>_DE7D#k)Y=XmyK>{)hBsw<; zg4n?*#{$lgujyWhAwC2hw7UFAJ1`kl4C15EaU+7-S_#E7xLehz8h9k8p06u1#3z_Z zN5E$>u#HB{(PJ-Cf)@pvRS)@;2(OTckP(0bo*)PVeLWLVnS-eKr~RuiD8CR7zmN>* zEs~Lu`HTM5Oyse45XrcL20X(jYF{43 z!O=s9q(2uQ4H>$#=x?WX+O)ZG%KWLzoCTas$(^S8JH=q8YqRqLg+orZR1Q4 zI%VDQXKpk!<(c%FlR0y=P=5*wsvb4pRcv6VW3?r9qh^XMRkMoFvM4k8?9CvttnXP= z8!4+UeYThQCbpz);D${-BuW0tQjgka=c%;zvb(E(T=>xCAr0)fN8z~eSs&W_bE#e8 zGko$@`Ri}3YID#V81dqLx^`>opUInCIC&EtOOsjs5Y9}4cWUBKwP?Sk==n%!?3&zV ze?^*RemkR3C*42uocwhy+W%3nBWCu_c?-Kf-V3LAjkvb zKhUB71$W&qjty$i%mWRIGX5YE0mKae5%Yr#MF0}tR3!RI=r3icmYT{J4JxN~j_>RD zF8l=%ss(lmeHWpqj=;YpLUE?U&k{|vjHO5+Tp<#-va^_6%HQX8Za|-OV@(w7ZRt>{pUY*j?GU;#6Q|zDr zbb$h%!m6j{u_Q~61XZ9U?fGmn(C_>r;S!BOiZ!C=Zmm@T~OipuJ!rHc0V*1 zH9($~S-g7@lA9D`$XWB~Gv@fE445kWXWdf1y+>2m`Kv8V)86Py&#gR9wTX@1cd!T? zZdN6TJ2fG)K&9$xdFwKhQ{&C-JL21c0Z9(AK6y@k<2OIi?FtVjtheOiCxzwI-li_W zlnt(|2uf!bW~=}y`NHOU-4#niHex?s5q+;auJ^Dbi6c%{ndcwxNEiPibyvA?bHwe6xuc7B0iq)x zuSleNp`~LG`OvM4ZoM)Tojz!XCW1>x*8bchFWd(1N9^}j3dDcxOqdpe)i7PGpxDD{4+5@IGYI%7hHIk2}m8sOQqU5c- zc(Ru3tE|3e-AVfq{f6EVp#F2`wE7NFor&h$EqW?pzM5_WEZ!xeOfC4(5*|BkfB6+S zJ#N*I;F}kwF(V$~Dx_e*c@5S98jjQfO5rj4FPIg))DEIg>Ce&UpM9hsZC(+C3x25r zeCcID4gX&T|9=^$;%BA9j!}2GE<=d&Vsd88NqfxaV;vIpsvc*mt8rQlo=p=ov5hYd z{~-5r*yQCBscVLl)W&==SDWZ#-QzFw`71o+)l<~R{c>A}3r=^2GvK;Nu5^b?jb9|5 zY5X99h~ngDH{8;Eo|}FR8&WdzQstb*q#^spEdH6AmN5qX;okIp4V<2fW+};t67yrR zzV8S)IzH8sIEpv8A1ApmF{%Ag%G~drLaC=ru^M5;oB;K4p-Y)kvqjtwapIqp1V4K{ z;SqPq{64v#{CY?n(e4{oejBt-#;6Ysb`q`(836=SeRk|&x9uO?E2mYZ(D{U8zvu1J zD%EncZ$(?Hur*mY`!G(?fOm@Gh@UcmUbh7rIb=qFZFB#`Ylq#-q#wtB;Q4Ei9Ynd6%aSTzl-5*Rrp6+=rO+3&twj&eQ zif_$)O?5ePSwSQGnz&=^HM9vw7L_7jTgDk{{*vu?%nhUG91-#Nsn4t|OX*a0Yq`u?z^;RZAlU|^w*@Bl===b2#5{WQP@;D0>;NZwI-BG@B}$^G>OHT=uF zd6!?J-W5EuF9sB>OMi3T9AvGT_y8_yf5^#6v#i6{^dUh%UGG6xg|rPJW3(#m+Hlsi zOCZ7P-ITXwV!?c__fjpyT%0u|PzIH}-|L_FKDLeIi^VvX0S3HQxcEh%t z$!A+d=A^Spyc?!GM!o0J@)|HL%=*q;h8tC^KLc)*CAnzV2OD*-@z#h$t9oJwyCds% zR-w|1)W^|y;>pfoEwM%A*B`r{D7*hwmTHW}u}83_vct~QLA%%(w8du%*>-Npa2 zN#GZ*^#_{fDAz)w!vN}Ur~|ZQ5&Zx&fS*AXc1IkE9G#RB7Noi@L-}f zXw7A}6U2}~05Ngo02qW&L?3|Y1`wS9q73uIA1z(bDGDHha<2R@-Yn2&=CmQg&P?ZO zXsmXTT#kp3;`Sv=$)htLj5sLUk8xYDnC|FFzb&*fj~XwuRSIUdY^tSfRl<1g_%x#8 zkInqPHN}~E865G2ue`*V{8GtSYPI$W&*0%eT2|^}#&OBot(V&dC=1^?4KnY1kc*Xt zkvI5LkJzAbk!z4!K_s>c-v>rK*BUx)L}x%!7jz6em|_c8xbBk!=8z^Ih2(AK1u@ub z8S`shYnAoQW#=JE(Vk&76_1bI{eY&B)tJu5Iw});(&nS8;nkIyP~38YwrVo}MB>2! zJ3f!0V|dIRTE?F=?YPS%l7mF0TQ_TGJT&@EGfMV0rP)Q2-8$wdDAN_F29mr;*fqKz aok~A<-HE?W^)_`9L%-_>s-#m5X#WSf38Nzb literal 0 HcmV?d00001 diff --git a/src/Libraries/Mono/System.Runtime.InteropServices.RuntimeInformation.dll b/src/Libraries/Mono/System.Runtime.InteropServices.RuntimeInformation.dll new file mode 100644 index 0000000000000000000000000000000000000000..e55b5eb23a927d37141e2fdb219f16ddcc51d72a GIT binary patch literal 7168 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 Z{vR&5(D*y?!v&e@e|R_lSN#+%@Nc{TN0k5o literal 0 HcmV?d00001 diff --git a/src/Libraries/Mono/System.Text.Encoding.CodePages.dll b/src/Libraries/Mono/System.Text.Encoding.CodePages.dll new file mode 100644 index 0000000000000000000000000000000000000000..414770bc096f591deb3b2473b719e19d3c9d6cbd GIT binary patch literal 5120 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 literal 0 HcmV?d00001 diff --git a/src/Libraries/Mono/readme.txt b/src/Libraries/Mono/readme.txt new file mode 100644 index 000000000..f1251a98f --- /dev/null +++ b/src/Libraries/Mono/readme.txt @@ -0,0 +1,3 @@ +Copied from mono/4.5/Facades of the mono 5.4 release. +These are the mono version of the dotnet Core TypeForwardedTo assemblies. +Using these assemblies is no longer necessary once we reach mono 5.18 as minimum version \ No newline at end of file diff --git a/src/MonoFacades.targets b/src/MonoFacades.targets new file mode 100644 index 000000000..6e1cc5484 --- /dev/null +++ b/src/MonoFacades.targets @@ -0,0 +1,52 @@ + + + + + + $(MSBuildThisFileDirectory)Libraries\Mono\ + + $(ResolveReferencesDependsOn); + SubstituteMonoFacadesBuild + + + + + + + + + + + + + + + + + + %(ReferenceCopyLocalPaths.Identity) + $(MonoFacadesPath)%(Filename)%(Extension) + + + + + + + + + + + + diff --git a/src/NzbDrone.Api/Config/HostConfigModule.cs b/src/NzbDrone.Api/Config/HostConfigModule.cs index 23a59b676..7ff299988 100644 --- a/src/NzbDrone.Api/Config/HostConfigModule.cs +++ b/src/NzbDrone.Api/Config/HostConfigModule.cs @@ -43,7 +43,7 @@ namespace NzbDrone.Api.Config SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None); SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); - SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows); + SharedValidator.RuleFor(c => c.SslCertPath).NotEmpty().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default"); SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script); diff --git a/src/NzbDrone.Api/Config/HostConfigResource.cs b/src/NzbDrone.Api/Config/HostConfigResource.cs index a63cc876a..048dd4ad8 100644 --- a/src/NzbDrone.Api/Config/HostConfigResource.cs +++ b/src/NzbDrone.Api/Config/HostConfigResource.cs @@ -21,7 +21,8 @@ namespace NzbDrone.Api.Config public string ConsoleLogLevel { get; set; } public string Branch { get; set; } public string ApiKey { get; set; } - public string SslCertHash { get; set; } + public string SslCertPath { get; set; } + public string SslCertPassword { get; set; } public string UrlBase { get; set; } public bool UpdateAutomatically { get; set; } public UpdateMechanism UpdateMechanism { get; set; } @@ -59,7 +60,8 @@ namespace NzbDrone.Api.Config ConsoleLogLevel = model.ConsoleLogLevel, Branch = model.Branch, ApiKey = model.ApiKey, - SslCertHash = model.SslCertHash, + SslCertPath = model.SslCertPath, + SslCertPassword = model.SslCertPassword, UrlBase = model.UrlBase, UpdateAutomatically = model.UpdateAutomatically, UpdateMechanism = model.UpdateMechanism, diff --git a/src/NzbDrone.Api/System/SystemModule.cs b/src/NzbDrone.Api/System/SystemModule.cs index 1108f1b38..452f20171 100644 --- a/src/NzbDrone.Api/System/SystemModule.cs +++ b/src/NzbDrone.Api/System/SystemModule.cs @@ -65,6 +65,7 @@ namespace NzbDrone.Api.System Branch = _configFileProvider.Branch, Authentication = _configFileProvider.AuthenticationMethod, SqliteVersion = _database.Version, + MigrationVersion = _database.Migration, UrlBase = _configFileProvider.UrlBase, RuntimeVersion = _platformInfo.Version, RuntimeName = PlatformInfo.Platform diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index b9dd48b8f..460fba632 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -37,7 +37,8 @@ namespace NzbDrone.Core.Configuration bool FilterSentryEvents { get; } string Branch { get; } string ApiKey { get; } - string SslCertHash { get; } + string SslCertPath { get; } + string SslCertPassword { get; } string UrlBase { get; } string UiFolder { get; } bool UpdateAutomatically { get; } @@ -99,12 +100,6 @@ namespace NzbDrone.Core.Configuration continue; } - if (configValue.Key.Equals("SslCertHash", StringComparison.InvariantCultureIgnoreCase) && configValue.Value.ToString().IsNotNullOrWhiteSpace()) - { - SetValue(configValue.Key.FirstCharToUpper(), HiddenCharacterRegex.Replace(configValue.Value.ToString(), string.Empty)); - continue; - } - object currentValue; allWithDefaults.TryGetValue(configValue.Key, out currentValue); if (currentValue == null) continue; @@ -184,7 +179,8 @@ namespace NzbDrone.Core.Configuration public string LogLevel => GetValue("LogLevel", "info"); public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); - public string SslCertHash => GetValue("SslCertHash", ""); + public string SslCertPath => GetValue("SslCertPath", ""); + public string SslCertPassword => GetValue("SslCertPassword", ""); public string UrlBase { diff --git a/src/NzbDrone.Core/Radarr.Core.csproj b/src/NzbDrone.Core/Radarr.Core.csproj index 02870ccdf..727200217 100644 --- a/src/NzbDrone.Core/Radarr.Core.csproj +++ b/src/NzbDrone.Core/Radarr.Core.csproj @@ -3,6 +3,7 @@ net462 + diff --git a/src/NzbDrone.Host.Test/ContainerFixture.cs b/src/NzbDrone.Host.Test/ContainerFixture.cs index 5a0018152..08bc392b1 100644 --- a/src/NzbDrone.Host.Test/ContainerFixture.cs +++ b/src/NzbDrone.Host.Test/ContainerFixture.cs @@ -15,6 +15,8 @@ using System.Linq; using NzbDrone.Common.Composition; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.SignalR; +using Moq; namespace NzbDrone.App.Test { @@ -31,6 +33,10 @@ namespace NzbDrone.App.Test _container = MainAppContainerBuilder.BuildContainer(args); _container.Register(new MainDatabase(null)); + + // set up a dummy broadcaster to allow tests to resolve + var mockBroadcaster = new Mock(); + _container.Register(mockBroadcaster.Object); } [Test] diff --git a/src/NzbDrone.Host.Test/RouterTest.cs b/src/NzbDrone.Host.Test/RouterTest.cs index b44807fdb..0c43ddac3 100644 --- a/src/NzbDrone.Host.Test/RouterTest.cs +++ b/src/NzbDrone.Host.Test/RouterTest.cs @@ -57,7 +57,7 @@ namespace NzbDrone.App.Test Subject.Route(ApplicationModes.Interactive); - Mocker.GetMock().Verify(c => c.Start(), Times.Once()); + Mocker.GetMock().Verify(c => c.Start(), Times.Once()); } [Test] diff --git a/src/NzbDrone.Host/AccessControl/NetshProvider.cs b/src/NzbDrone.Host/AccessControl/NetshProvider.cs deleted file mode 100644 index cca3fc8c5..000000000 --- a/src/NzbDrone.Host/AccessControl/NetshProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using NLog; -using NzbDrone.Common.Processes; - -namespace Radarr.Host.AccessControl -{ - public interface INetshProvider - { - ProcessOutput Run(string arguments); - } - - public class NetshProvider : INetshProvider - { - private readonly IProcessProvider _processProvider; - private readonly Logger _logger; - - public NetshProvider(IProcessProvider processProvider, Logger logger) - { - _processProvider = processProvider; - _logger = logger; - } - - public ProcessOutput Run(string arguments) - { - try - { - var output = _processProvider.StartAndCapture("netsh.exe", arguments); - - return output; - } - catch (Exception ex) - { - _logger.Warn(ex, "Error executing netsh with arguments: " + arguments); - } - - return null; - } - } -} diff --git a/src/NzbDrone.Host/AccessControl/SslAdapter.cs b/src/NzbDrone.Host/AccessControl/SslAdapter.cs deleted file mode 100644 index ed9c3aa95..000000000 --- a/src/NzbDrone.Host/AccessControl/SslAdapter.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Linq; -using System.Text.RegularExpressions; -using NLog; -using NzbDrone.Core.Configuration; - -namespace Radarr.Host.AccessControl -{ - public interface ISslAdapter - { - void Register(); - } - - public class SslAdapter : ISslAdapter - { - private const string APP_ID = "C2172AF4-F9A6-4D91-BAEE-C2E4EE680613"; - private static readonly Regex CertificateHashRegex = new Regex(@"^\s+(?:Certificate Hash\s+:\s+)(?\w+)", RegexOptions.Compiled); - - private readonly INetshProvider _netshProvider; - private readonly IConfigFileProvider _configFileProvider; - private readonly Logger _logger; - - public SslAdapter(INetshProvider netshProvider, IConfigFileProvider configFileProvider, Logger logger) - { - _netshProvider = netshProvider; - _configFileProvider = configFileProvider; - _logger = logger; - } - - public void Register() - { - if (!_configFileProvider.EnableSsl) return; - if (IsRegistered()) return; - - if (string.IsNullOrWhiteSpace(_configFileProvider.SslCertHash)) - { - _logger.Warn("Unable to enable SSL, SSL Cert Hash is required"); - return; - } - - var arguments = string.Format("http add sslcert ipport=0.0.0.0:{0} certhash={1} appid={{{2}}}", - _configFileProvider.SslPort, - _configFileProvider.SslCertHash, - APP_ID); - - //TODO: Validate that the cert was added properly, invisible spaces FTL - _netshProvider.Run(arguments); - } - - private bool IsRegistered() - { - var ipPort = "0.0.0.0:" + _configFileProvider.SslPort; - var arguments = string.Format("http show sslcert ipport={0}", ipPort); - - var output = _netshProvider.Run(arguments); - - if (output == null || !output.Standard.Any()) return false; - - var hashLine = output.Standard.SingleOrDefault(line => CertificateHashRegex.IsMatch(line.Content)); - - if (hashLine != null) - { - var match = CertificateHashRegex.Match(hashLine.Content); - - if (match.Success) - { - if (match.Groups["hash"].Value != _configFileProvider.SslCertHash) - { - Unregister(); - - return false; - } - } - } - - return output.Standard.Any(line => line.Content.Contains(ipPort)); - } - - private void Unregister() - { - var ipPort = "0.0.0.0:" + _configFileProvider.SslPort; - var arguments = string.Format("http delete sslcert ipport={0}", ipPort); - - _netshProvider.Run(arguments); - } - } -} diff --git a/src/NzbDrone.Host/AccessControl/UrlAcl.cs b/src/NzbDrone.Host/AccessControl/UrlAcl.cs deleted file mode 100644 index 8ff7e9602..000000000 --- a/src/NzbDrone.Host/AccessControl/UrlAcl.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Radarr.Host.AccessControl -{ - public class UrlAcl - { - public string Scheme { get; set; } - public string Address { get; set; } - public int Port { get; set; } - public string UrlBase { get; set; } - - public string Url => string.Format("{0}://{1}:{2}/{3}", Scheme, Address, Port, UrlBase); - } -} diff --git a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs b/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs deleted file mode 100644 index 77a4777d1..000000000 --- a/src/NzbDrone.Host/AccessControl/UrlAclAdapter.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using NLog; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Exceptions; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Configuration; - -namespace Radarr.Host.AccessControl -{ - public interface IUrlAclAdapter - { - void ConfigureUrls(); - List Urls { get; } - } - - public class UrlAclAdapter : IUrlAclAdapter - { - private readonly INetshProvider _netshProvider; - private readonly IConfigFileProvider _configFileProvider; - private readonly IRuntimeInfo _runtimeInfo; - private readonly IOsInfo _osInfo; - private readonly Logger _logger; - - public List Urls - { - get - { - return InternalUrls.Select(c => c.Url).ToList(); - } - } - - private List InternalUrls { get; } - private List RegisteredUrls { get; set; } - - private static readonly Regex UrlAclRegex = new Regex(@"(?https?)\:\/\/(?

.+?)\:(?\d+)/(?.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public UrlAclAdapter(INetshProvider netshProvider, - IConfigFileProvider configFileProvider, - IRuntimeInfo runtimeInfo, - IOsInfo osInfo, - Logger logger) - { - _netshProvider = netshProvider; - _configFileProvider = configFileProvider; - _runtimeInfo = runtimeInfo; - _osInfo = osInfo; - _logger = logger; - - InternalUrls = new List(); - RegisteredUrls = new List(); - } - - public void ConfigureUrls() - { - var enableSsl = _configFileProvider.EnableSsl; - var port = _configFileProvider.Port; - var sslPort = _configFileProvider.SslPort; - - if (enableSsl && sslPort == port) - { - throw new RadarrStartupException("Cannot use the same port for HTTP and HTTPS. Port {0}", port); - } - - if (RegisteredUrls.Empty()) - { - GetRegisteredUrls(); - } - - var localHostHttpUrls = BuildUrlAcls("http", "localhost", port); - var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, port); - - var localHostHttpsUrls = BuildUrlAcls("https", "localhost", sslPort); - var interfaceHttpsUrls = BuildUrlAcls("https", _configFileProvider.BindAddress, sslPort); - - if (!enableSsl) - { - localHostHttpsUrls.Clear(); - interfaceHttpsUrls.Clear(); - } - - if (OsInfo.IsWindows && !_runtimeInfo.IsAdmin) - { - var httpUrls = interfaceHttpUrls.All(IsRegistered) ? interfaceHttpUrls : localHostHttpUrls; - var httpsUrls = interfaceHttpsUrls.All(IsRegistered) ? interfaceHttpsUrls : localHostHttpsUrls; - - InternalUrls.AddRange(httpUrls); - InternalUrls.AddRange(httpsUrls); - - if (_configFileProvider.BindAddress != "*") - { - if (httpUrls.None(c => c.Address.Equals("localhost"))) - { - InternalUrls.AddRange(localHostHttpUrls); - } - - if (httpsUrls.None(c => c.Address.Equals("localhost"))) - { - InternalUrls.AddRange(localHostHttpsUrls); - } - } - } - else - { - InternalUrls.AddRange(interfaceHttpUrls); - InternalUrls.AddRange(interfaceHttpsUrls); - - //Register localhost URLs so the IP Address doesn't need to be used from the local system - if (_configFileProvider.BindAddress != "*") - { - InternalUrls.AddRange(localHostHttpUrls); - InternalUrls.AddRange(localHostHttpsUrls); - } - - if (OsInfo.IsWindows) - { - RefreshRegistration(); - } - } - } - - private void RefreshRegistration() - { - var osVersion = new Version(_osInfo.Version); - if (osVersion.Major < 6) return; - - foreach (var urlAcl in InternalUrls) - { - if (IsRegistered(urlAcl) || urlAcl.Address.Equals("localhost")) continue; - - RemoveSimilar(urlAcl); - RegisterUrl(urlAcl); - } - } - - private bool IsRegistered(UrlAcl urlAcl) - { - return RegisteredUrls.Any(c => c.Scheme == urlAcl.Scheme && - c.Address == urlAcl.Address && - c.Port == urlAcl.Port && - c.UrlBase == urlAcl.UrlBase); - } - - private void GetRegisteredUrls() - { - if (OsInfo.IsNotWindows) - { - return; - } - - if (RegisteredUrls.Any()) - { - return; - } - - var arguments = string.Format("http show urlacl"); - var output = _netshProvider.Run(arguments); - - if (output == null || !output.Standard.Any()) return; - - RegisteredUrls = output.Standard.Select(line => - { - var match = UrlAclRegex.Match(line.Content); - - if (match.Success) - { - return new UrlAcl - { - Scheme = match.Groups["scheme"].Value, - Address = match.Groups["address"].Value, - Port = Convert.ToInt32(match.Groups["port"].Value), - UrlBase = match.Groups["urlbase"].Value.Trim() - }; - } - - return null; - - }).Where(r => r != null).ToList(); - } - - private void RegisterUrl(UrlAcl urlAcl) - { - var arguments = string.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl.Url); - _netshProvider.Run(arguments); - } - - private void RemoveSimilar(UrlAcl urlAcl) - { - var similar = RegisteredUrls.Where(c => c.Scheme == urlAcl.Scheme && - InternalUrls.None(x => x.Address == c.Address) && - c.Port == urlAcl.Port && - c.UrlBase == urlAcl.UrlBase); - - foreach (var s in similar) - { - UnregisterUrl(s); - } - } - - private void UnregisterUrl(UrlAcl urlAcl) - { - _logger.Trace("Removing URL ACL {0}", urlAcl.Url); - - var arguments = string.Format("http delete urlacl {0}", urlAcl.Url); - _netshProvider.Run(arguments); - } - - private List BuildUrlAcls(string scheme, string address, int port) - { - var urlAcls = new List(); - var urlBase = _configFileProvider.UrlBase; - - if (urlBase.IsNotNullOrWhiteSpace()) - { - urlAcls.Add(new UrlAcl - { - Scheme = scheme, - Address = address, - Port = port, - UrlBase = urlBase.Trim('/') + "/" - }); - } - - urlAcls.Add(new UrlAcl - { - Scheme = scheme, - Address = address, - Port = port, - UrlBase = string.Empty - }); - - return urlAcls; - } - } -} diff --git a/src/NzbDrone.Host/ApplicationServer.cs b/src/NzbDrone.Host/ApplicationServer.cs index 55717e798..4aa85cb56 100644 --- a/src/NzbDrone.Host/ApplicationServer.cs +++ b/src/NzbDrone.Host/ApplicationServer.cs @@ -1,4 +1,3 @@ -using System; using System.ServiceProcess; using NLog; using NzbDrone.Common.Composition; @@ -7,17 +6,54 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; -using Radarr.Host.Owin; namespace Radarr.Host { public interface INzbDroneServiceFactory { ServiceBase Build(); - void Start(); } - public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory, IHandle + public interface INzbDroneConsoleFactory + { + void Start(); + void Shutdown(); + } + + public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory + { + private readonly INzbDroneConsoleFactory _consoleFactory; + + public NzbDroneServiceFactory(INzbDroneConsoleFactory consoleFactory) + { + _consoleFactory = consoleFactory; + } + + protected override void OnStart(string[] args) + { + _consoleFactory.Start(); + } + + protected override void OnStop() + { + _consoleFactory.Shutdown(); + } + + public ServiceBase Build() + { + return this; + } + } + + public class DummyNzbDroneServiceFactory : INzbDroneServiceFactory + { + public ServiceBase Build() + { + return null; + } + } + + public class NzbDroneConsoleFactory : INzbDroneConsoleFactory, IHandle { private readonly IConfigFileProvider _configFileProvider; private readonly IRuntimeInfo _runtimeInfo; @@ -28,7 +64,7 @@ namespace Radarr.Host private readonly Logger _logger; private CancelHandler _cancelHandler; - public NzbDroneServiceFactory(IConfigFileProvider configFileProvider, + public NzbDroneConsoleFactory(IConfigFileProvider configFileProvider, IHostController hostController, IRuntimeInfo runtimeInfo, IStartupContext startupContext, @@ -45,11 +81,6 @@ namespace Radarr.Host _logger = logger; } - protected override void OnStart(string[] args) - { - Start(); - } - public void Start() { if (OsInfo.IsNotWindows) @@ -72,17 +103,7 @@ namespace Radarr.Host _container.Resolve().PublishEvent(new ApplicationStartedEvent()); } - protected override void OnStop() - { - Shutdown(); - } - - public ServiceBase Build() - { - return this; - } - - private void Shutdown() + public void Shutdown() { _logger.Info("Attempting to stop application."); _hostController.StopServer(); diff --git a/src/NzbDrone.Host/Owin/IHostController.cs b/src/NzbDrone.Host/IHostController.cs similarity index 77% rename from src/NzbDrone.Host/Owin/IHostController.cs rename to src/NzbDrone.Host/IHostController.cs index 74d534b9d..7f2d7ede1 100644 --- a/src/NzbDrone.Host/Owin/IHostController.cs +++ b/src/NzbDrone.Host/IHostController.cs @@ -1,8 +1,8 @@ -namespace Radarr.Host.Owin +namespace Radarr.Host { public interface IHostController { void StartServer(); void StopServer(); } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Host/IRemoteAccessAdapter.cs b/src/NzbDrone.Host/IRemoteAccessAdapter.cs new file mode 100644 index 000000000..63cd16a12 --- /dev/null +++ b/src/NzbDrone.Host/IRemoteAccessAdapter.cs @@ -0,0 +1,7 @@ +namespace Radarr.Host.AccessControl +{ + public interface IRemoteAccessAdapter + { + void MakeAccessible(bool passive); + } +} diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs index 454c254c1..6f859c398 100644 --- a/src/NzbDrone.Host/MainAppContainerBuilder.cs +++ b/src/NzbDrone.Host/MainAppContainerBuilder.cs @@ -27,9 +27,18 @@ namespace Radarr.Host private MainAppContainerBuilder(StartupContext args, List assemblies) : base(args, assemblies) { - AutoRegisterImplementations(); + AutoRegisterImplementations(); Container.Register(); + + if (OsInfo.IsWindows) + { + Container.Register(); + } + else + { + Container.Register(); + } } } } diff --git a/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs deleted file mode 100644 index ee33d0df0..000000000 --- a/src/NzbDrone.Host/Owin/MiddleWare/IOwinMiddleWare.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Owin; - -namespace Radarr.Host.Owin.MiddleWare -{ - public interface IOwinMiddleWare - { - int Order { get; } - void Attach(IAppBuilder appBuilder); - } -} \ No newline at end of file diff --git a/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs deleted file mode 100644 index f10d3ee6c..000000000 --- a/src/NzbDrone.Host/Owin/MiddleWare/NzbDroneVersionMiddleWare.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Owin; -using NLog; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Instrumentation; -using Owin; - -namespace Radarr.Host.Owin.MiddleWare -{ - public class NzbDroneVersionMiddleWare : IOwinMiddleWare - { - public int Order => 0; - - public void Attach(IAppBuilder appBuilder) - { - appBuilder.Use(typeof(AddApplicationVersionHeader)); - } - } - - public class AddApplicationVersionHeader : OwinMiddleware - { - private readonly KeyValuePair _versionHeader; - private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(AddApplicationVersionHeader)); - - public AddApplicationVersionHeader(OwinMiddleware next) - : base(next) - { - _versionHeader = new KeyValuePair("X-Application-Version", new[] { BuildInfo.Version.ToString() }); - } - public override async Task Invoke(IOwinContext context) - { - try - { - context.Response.Headers.Add(_versionHeader); - await Next.Invoke(context); - } - catch (Exception) - { - Logger.Debug("Unable to set version header"); - } - } - } -} diff --git a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs deleted file mode 100644 index f4305fb8e..000000000 --- a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Microsoft.AspNet.SignalR; -using NzbDrone.Common.Composition; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.SignalR; -using Owin; - -namespace Radarr.Host.Owin.MiddleWare -{ - public class SignalRMiddleWare : IOwinMiddleWare - { - public int Order => 1; - - public SignalRMiddleWare(IContainer container) - { - SignalRDependencyResolver.Register(container); - SignalRJsonSerializer.Register(); - - // Note there are some important timeouts involved here: - // nginx has a default 60 sec proxy_read_timeout, this means the connection will be terminated if the server doesn't send anything within that time. - // Previously we lowered the ConnectionTimeout from 110s to 55s to remedy that, however all we should've done is set an appropriate KeepAlive. - // By default KeepAlive is 1/3rd of the DisconnectTimeout, which we set incredibly high 5 years ago, resulting in KeepAlive being 1 minute. - // So when adjusting these values in the future, please keep that all in mind. - GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110); - GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(180); - GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(30); - } - - public void Attach(IAppBuilder appBuilder) - { - appBuilder.MapSignalR("/signalr", typeof(NzbDronePersistentConnection), new ConnectionConfiguration()); - } - } -} diff --git a/src/NzbDrone.Host/Owin/NlogTextWriter.cs b/src/NzbDrone.Host/Owin/NlogTextWriter.cs deleted file mode 100644 index b57e26b92..000000000 --- a/src/NzbDrone.Host/Owin/NlogTextWriter.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.IO; -using System.Text; -using NLog; - -namespace Radarr.Host.Owin -{ - public class NlogTextWriter : TextWriter - { - private readonly Logger _logger; - - public NlogTextWriter(Logger logger) - { - _logger = logger; - } - - public override Encoding Encoding => Encoding.Default; - - public override void Write(char[] buffer, int index, int count) - { - Write(buffer); - } - public override void Write(char[] buffer) - { - Write(new string(buffer)); - } - - public override void Write(string value) - { - _logger.Log(GetLogLevel(value), value); - } - - public override void Write(char value) - { - _logger.Trace(value); - } - - private LogLevel GetLogLevel(string value) - { - var lower = value.ToLowerInvariant(); - - if (!lower.Contains("error")) - { - return LogLevel.Trace; - } - - if (lower.Contains("sqlite")) - { - return LogLevel.Trace; - } - - if (lower.Contains("\"errors\":null")) - { - return LogLevel.Trace; - } - - if (lower.Contains("signalr")) - { - if (lower.Contains("an operation was attempted on a nonexistent network connection")) - { - return LogLevel.Trace; - } - - if (lower.Contains("the network connection was aborted by the local system")) - { - return LogLevel.Trace; - } - - if (lower.Contains("the socket has been shut down")) - { - return LogLevel.Trace; - } - } - - return LogLevel.Error; - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Host/Owin/OwinHostController.cs b/src/NzbDrone.Host/Owin/OwinHostController.cs deleted file mode 100644 index 5beb8c4c8..000000000 --- a/src/NzbDrone.Host/Owin/OwinHostController.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using NLog; -using NzbDrone.Common.EnvironmentInfo; -using Radarr.Host.AccessControl; - -namespace Radarr.Host.Owin -{ - public class OwinHostController : IHostController - { - private readonly IOwinAppFactory _owinAppFactory; - private readonly IRemoteAccessAdapter _removeAccessAdapter; - private readonly IUrlAclAdapter _urlAclAdapter; - private readonly Logger _logger; - private IDisposable _owinApp; - - public OwinHostController( - IOwinAppFactory owinAppFactory, - IRemoteAccessAdapter removeAccessAdapter, - IUrlAclAdapter urlAclAdapter, - Logger logger) - { - _owinAppFactory = owinAppFactory; - _removeAccessAdapter = removeAccessAdapter; - _urlAclAdapter = urlAclAdapter; - _logger = logger; - } - - public void StartServer() - { - _removeAccessAdapter.MakeAccessible(true); - - _logger.Info("Listening on the following URLs:"); - foreach (var url in _urlAclAdapter.Urls) - { - _logger.Info(" {0}", url); - } - - _owinApp = _owinAppFactory.CreateApp(_urlAclAdapter.Urls); - } - - public void StopServer() - { - if (_owinApp == null) return; - - _logger.Info("Attempting to stop OWIN host"); - _owinApp.Dispose(); - _owinApp = null; - _logger.Info("Host has stopped"); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs b/src/NzbDrone.Host/Owin/OwinServiceProvider.cs deleted file mode 100644 index 29baa908e..000000000 --- a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Reflection; -using Microsoft.Owin.Hosting; -using Microsoft.Owin.Hosting.Engine; -using Microsoft.Owin.Hosting.Services; -using Microsoft.Owin.Hosting.Tracing; -using NLog; -using NzbDrone.Core.Configuration; -using Radarr.Host.Owin.MiddleWare; -using Owin; -using NzbDrone.Common.EnvironmentInfo; - -namespace Radarr.Host.Owin -{ - public interface IOwinAppFactory - { - IDisposable CreateApp(List urls); - } - - public class OwinAppFactory : IOwinAppFactory - { - private readonly IEnumerable _owinMiddleWares; - private readonly IConfigFileProvider _configFileProvider; - private readonly Logger _logger; - - public OwinAppFactory(IEnumerable owinMiddleWares, IConfigFileProvider configFileProvider, Logger logger) - { - _owinMiddleWares = owinMiddleWares; - _configFileProvider = configFileProvider; - _logger = logger; - } - - public IDisposable CreateApp(List urls) - { - var services = CreateServiceFactory(); - var engine = services.GetService(); - - var options = new StartOptions() - { - ServerFactory = "Microsoft.Owin.Host.HttpListener" - }; - - urls.ForEach(options.Urls.Add); - - var context = new StartContext(options) { Startup = BuildApp }; - - - try - { - return engine.Start(context); - } - catch (TargetInvocationException ex) - { - if (ex.InnerException == null) - { - throw; - } - - if (ex.InnerException is HttpListenerException) - { - throw new PortInUseException("Port {0} is already in use, please ensure Radarr is not already running.", ex, _configFileProvider.Port); - } - - throw ex.InnerException; - } - } - - - private void BuildApp(IAppBuilder appBuilder) - { - appBuilder.Properties["host.AppName"] = BuildInfo.AppName; - - foreach (var middleWare in _owinMiddleWares.OrderBy(c => c.Order)) - { - _logger.Debug("Attaching {0} to host", middleWare.GetType().Name); - middleWare.Attach(appBuilder); - } - } - - - private IServiceProvider CreateServiceFactory() - { - var provider = (ServiceProvider)ServicesFactory.Create(); - provider.Add(typeof(ITraceOutputFactory), typeof(OwinTraceOutputFactory)); - - return provider; - } - } -} diff --git a/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs b/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs deleted file mode 100644 index b195ba969..000000000 --- a/src/NzbDrone.Host/Owin/OwinTraceOutputFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.IO; -using Microsoft.Owin.Hosting.Tracing; -using NLog; - -namespace Radarr.Host.Owin -{ - public class OwinTraceOutputFactory : ITraceOutputFactory - { - private readonly Logger _logger = LogManager.GetLogger("Owin"); - - public TextWriter Create(string outputFile) - { - return new NlogTextWriter(_logger); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Host/Owin/PortInUseException.cs b/src/NzbDrone.Host/Owin/PortInUseException.cs deleted file mode 100644 index 5946bc61a..000000000 --- a/src/NzbDrone.Host/Owin/PortInUseException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using NzbDrone.Common.Exceptions; - -namespace Radarr.Host.Owin -{ - public class PortInUseException : NzbDroneException - { - public PortInUseException(string message, Exception innerException, params object[] args) : base(message, innerException, args) - { - } - } -} diff --git a/src/NzbDrone.Host/Radarr.Host.csproj b/src/NzbDrone.Host/Radarr.Host.csproj index e81c301ef..056732f1c 100644 --- a/src/NzbDrone.Host/Radarr.Host.csproj +++ b/src/NzbDrone.Host/Radarr.Host.csproj @@ -3,8 +3,11 @@ net462 - - + + + + + diff --git a/src/NzbDrone.Host/Router.cs b/src/NzbDrone.Host/Router.cs index 98f0057d1..f6614d13e 100644 --- a/src/NzbDrone.Host/Router.cs +++ b/src/NzbDrone.Host/Router.cs @@ -11,6 +11,7 @@ namespace Radarr.Host { public class Router { + private readonly INzbDroneConsoleFactory _nzbDroneConsoleFactory; private readonly INzbDroneServiceFactory _nzbDroneServiceFactory; private readonly IServiceProvider _serviceProvider; private readonly IConsoleService _consoleService; @@ -19,7 +20,8 @@ namespace Radarr.Host private readonly IRemoteAccessAdapter _remoteAccessAdapter; private readonly Logger _logger; - public Router(INzbDroneServiceFactory nzbDroneServiceFactory, + public Router(INzbDroneConsoleFactory nzbDroneConsoleFactory, + INzbDroneServiceFactory nzbDroneServiceFactory, IServiceProvider serviceProvider, IConsoleService consoleService, IRuntimeInfo runtimeInfo, @@ -27,6 +29,7 @@ namespace Radarr.Host IRemoteAccessAdapter remoteAccessAdapter, Logger logger) { + _nzbDroneConsoleFactory = nzbDroneConsoleFactory; _nzbDroneServiceFactory = nzbDroneServiceFactory; _serviceProvider = serviceProvider; _consoleService = consoleService; @@ -52,7 +55,7 @@ namespace Radarr.Host case ApplicationModes.Interactive: { _logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected"); - _nzbDroneServiceFactory.Start(); + _nzbDroneConsoleFactory.Start(); break; } case ApplicationModes.InstallService: diff --git a/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs b/src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs similarity index 65% rename from src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs rename to src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs index f9bbd6ac5..7ec6df1ef 100644 --- a/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs +++ b/src/NzbDrone.Host/WebHost/AccessControl/RemoteAccessAdapter.cs @@ -2,27 +2,16 @@ using NzbDrone.Common.EnvironmentInfo; namespace Radarr.Host.AccessControl { - public interface IRemoteAccessAdapter - { - void MakeAccessible(bool passive); - } - public class RemoteAccessAdapter : IRemoteAccessAdapter { private readonly IRuntimeInfo _runtimeInfo; - private readonly IUrlAclAdapter _urlAclAdapter; private readonly IFirewallAdapter _firewallAdapter; - private readonly ISslAdapter _sslAdapter; public RemoteAccessAdapter(IRuntimeInfo runtimeInfo, - IUrlAclAdapter urlAclAdapter, - IFirewallAdapter firewallAdapter, - ISslAdapter sslAdapter) + IFirewallAdapter firewallAdapter) { _runtimeInfo = runtimeInfo; - _urlAclAdapter = urlAclAdapter; _firewallAdapter = firewallAdapter; - _sslAdapter = sslAdapter; } public void MakeAccessible(bool passive) @@ -32,15 +21,12 @@ namespace Radarr.Host.AccessControl if (_runtimeInfo.IsAdmin) { _firewallAdapter.MakeAccessible(); - _sslAdapter.Register(); } else if (!passive) { throw new RemoteAccessException("Failed to register URLs for Radarr. Radarr will not be accessible remotely"); } } - - _urlAclAdapter.ConfigureUrls(); } } } diff --git a/src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs b/src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs new file mode 100644 index 000000000..786381a25 --- /dev/null +++ b/src/NzbDrone.Host/WebHost/Middleware/IAspNetCoreMiddleware.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Builder; + +namespace Radarr.Host.Middleware +{ + public interface IAspNetCoreMiddleware + { + int Order { get; } + void Attach(IApplicationBuilder appBuilder); + } +} diff --git a/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs b/src/NzbDrone.Host/WebHost/Middleware/NancyMiddleware.cs similarity index 56% rename from src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs rename to src/NzbDrone.Host/WebHost/Middleware/NancyMiddleware.cs index 7b826168b..f1b651454 100644 --- a/src/NzbDrone.Host/Owin/MiddleWare/NancyMiddleWare.cs +++ b/src/NzbDrone.Host/WebHost/Middleware/NancyMiddleware.cs @@ -1,21 +1,21 @@ -using Nancy.Bootstrapper; +using Microsoft.AspNetCore.Builder; +using Nancy.Bootstrapper; using Nancy.Owin; -using Owin; -namespace Radarr.Host.Owin.MiddleWare +namespace Radarr.Host.Middleware { - public class NancyMiddleWare : IOwinMiddleWare + public class NancyMiddleware : IAspNetCoreMiddleware { private readonly INancyBootstrapper _nancyBootstrapper; - public NancyMiddleWare(INancyBootstrapper nancyBootstrapper) + public int Order => 2; + + public NancyMiddleware(INancyBootstrapper nancyBootstrapper) { _nancyBootstrapper = nancyBootstrapper; } - public int Order => 2; - - public void Attach(IAppBuilder appBuilder) + public void Attach(IApplicationBuilder appBuilder) { var options = new NancyOptions { @@ -23,7 +23,7 @@ namespace Radarr.Host.Owin.MiddleWare PerformPassThrough = context => context.Request.Path.StartsWith("/signalr") }; - appBuilder.UseNancy(options); + appBuilder.UseOwin(x => x.UseNancy(options)); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs b/src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs new file mode 100644 index 000000000..82d039a03 --- /dev/null +++ b/src/NzbDrone.Host/WebHost/Middleware/SignalRMiddleware.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using NLog; +using NzbDrone.Common.Composition; +using NzbDrone.Core.Configuration; +using NzbDrone.SignalR; + +namespace Radarr.Host.Middleware +{ + public class SignalRMiddleware : IAspNetCoreMiddleware + { + private readonly IContainer _container; + private readonly Logger _logger; + private static string API_KEY; + public int Order => 1; + + public SignalRMiddleware(IContainer container, + IConfigFileProvider configFileProvider, + Logger logger) + { + _container = container; + _logger = logger; + API_KEY = configFileProvider.ApiKey; + } + + public void Attach(IApplicationBuilder appBuilder) + { + appBuilder.UseWebSockets(); + + appBuilder.Use(async (context, next) => + { + if (context.Request.Path.StartsWithSegments("/signalr") && + !context.Request.Path.Value.EndsWith("/negotiate")) + { + if (!context.Request.Query.ContainsKey("access_token") || + context.Request.Query["access_token"] != API_KEY) + { + context.Response.StatusCode = 401; + await context.Response.WriteAsync("Unauthorized"); + return; + } + } + + try + { + await next(); + } + catch (OperationCanceledException e) + { + // Demote the exception to trace logging so users don't worry (as much). + _logger.Trace(e); + } + }); + + appBuilder.UseSignalR(routes => + { + routes.MapHub("/signalr/messages"); + }); + + // This is a side effect of haing multiple IoC containers, TinyIoC and whatever + // Kestrel/SignalR is using. Ideally we'd have one IoC container, but that's non-trivial with TinyIoC + // TODO: Use a single IoC container if supported for TinyIoC or if we switch to another system (ie Autofac). + + var hubContext = appBuilder.ApplicationServices.GetService>(); + _container.Register(hubContext); + } + } +} diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs new file mode 100644 index 000000000..4cdce61ff --- /dev/null +++ b/src/NzbDrone.Host/WebHost/WebHostController.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NLog; +using NLog.Extensions.Logging; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Configuration; +using Radarr.Host.AccessControl; +using Radarr.Host.Middleware; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +namespace Radarr.Host +{ + public class WebHostController : IHostController + { + private readonly IRuntimeInfo _runtimeInfo; + private readonly IConfigFileProvider _configFileProvider; + private readonly IFirewallAdapter _firewallAdapter; + private readonly IEnumerable _middlewares; + private readonly Logger _logger; + private IWebHost _host; + + public WebHostController(IRuntimeInfo runtimeInfo, + IConfigFileProvider configFileProvider, + IFirewallAdapter firewallAdapter, + IEnumerable middlewares, + Logger logger) + { + _runtimeInfo = runtimeInfo; + _configFileProvider = configFileProvider; + _firewallAdapter = firewallAdapter; + _middlewares = middlewares; + _logger = logger; + } + + public void StartServer() + { + if (OsInfo.IsWindows) + { + if (_runtimeInfo.IsAdmin) + { + _firewallAdapter.MakeAccessible(); + } + } + + var bindAddress = _configFileProvider.BindAddress; + var enableSsl = _configFileProvider.EnableSsl; + var sslCertPath = _configFileProvider.SslCertPath; + + var urls = new List(); + + urls.Add(BuildUrl("http", bindAddress, _configFileProvider.Port)); + + if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace()) + { + urls.Add(BuildUrl("https", bindAddress, _configFileProvider.SslPort)); + } + + _host = new WebHostBuilder() + .UseUrls(urls.ToArray()) + .UseKestrel(options => + { + if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace()) + { + options.ConfigureHttpsDefaults(configureOptions => + { + var certificate = new X509Certificate2(); + certificate.Import(_configFileProvider.SslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet); + + configureOptions.ServerCertificate = certificate; + }); + } + }) + .ConfigureKestrel(serverOptions => + { + serverOptions.AllowSynchronousIO = true; + }) + .ConfigureLogging(logging => + { + logging.AddProvider(new NLogLoggerProvider()); + logging.SetMinimumLevel(LogLevel.Warning); + }) + .ConfigureServices(services => + { + services + .AddSignalR() + .AddJsonProtocol(options => + { + options.PayloadSerializerSettings = Json.GetSerializerSettings(); + }); + }) + .Configure(app => + { + app.UsePathBase(_configFileProvider.UrlBase); + app.Properties["host.AppName"] = BuildInfo.AppName; + + foreach (var middleWare in _middlewares.OrderBy(c => c.Order)) + { + _logger.Debug("Attaching {0} to host", middleWare.GetType().Name); + middleWare.Attach(app); + } + }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .Build(); + + _logger.Info("Listening on the following URLs:"); + + foreach (var url in urls) + { + _logger.Info(" {0}", url); + } + + _host.Start(); + } + + public async void StopServer() + { + _logger.Info("Attempting to stop OWIN host"); + + await _host.StopAsync(TimeSpan.FromSeconds(5)); + _host.Dispose(); + _host = null; + + _logger.Info("Host has stopped"); + } + + private string BuildUrl(string scheme, string bindAddress, int port) + { + return $"{scheme}://{bindAddress}:{port}"; + } + } +} diff --git a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs index 3f2ba7967..9dc73fbae 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Integration.Test.ApiTests [Ignore("SignalR on CI seems unstable")] public void should_add_and_delete_root_folders() { - ConnectSignalR(); + ConnectSignalR().Wait(); var rootFolder = new RootFolderResource { @@ -55,4 +55,4 @@ namespace NzbDrone.Integration.Test.ApiTests postResponse.Should().NotBeNull(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index e912fc119..07132b132 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -5,8 +5,6 @@ using System.IO; using System.Linq; using System.Threading; using FluentAssertions; -using Microsoft.AspNet.SignalR.Client; -using Microsoft.AspNet.SignalR.Client.Transports; using NLog; using NLog.Config; using NLog.Targets; @@ -30,6 +28,10 @@ using NzbDrone.Integration.Test.Client; using NzbDrone.SignalR; using NzbDrone.Test.Common.Categories; using RestSharp; +using NzbDrone.Test.Common; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Microsoft.AspNetCore.SignalR.Client; namespace NzbDrone.Integration.Test { @@ -56,7 +58,8 @@ namespace NzbDrone.Integration.Test public ClientBase WantedCutoffUnmet; private List _signalRReceived; - private Connection _signalrConnection; + + private HubConnection _signalrConnection; protected IEnumerable SignalRMessages => _signalRReceived; @@ -132,19 +135,12 @@ namespace NzbDrone.Integration.Test } [TearDown] - public void IntegrationTearDown() + public async Task IntegrationTearDown() { if (_signalrConnection != null) { - switch (_signalrConnection.State) - { - case ConnectionState.Connected: - case ConnectionState.Connecting: - { - _signalrConnection.Stop(); - break; - } - } + + await _signalrConnection.StopAsync(); _signalrConnection = null; _signalRReceived = new List(); @@ -187,33 +183,49 @@ namespace NzbDrone.Integration.Test return path; } - protected void ConnectSignalR() + protected async Task ConnectSignalR() { _signalRReceived = new List(); - _signalrConnection = new Connection("http://localhost:7878/signalr"); - _signalrConnection.Start(new LongPollingTransport()).ContinueWith(task => + _signalrConnection = new HubConnectionBuilder().WithUrl("http://localhost:7878/signalr/messages").Build(); + + + var cts = new CancellationTokenSource(); + + _signalrConnection.Closed += e => { - if (task.IsFaulted) - { - Assert.Fail("SignalrConnection failed. {0}", task.Exception.GetBaseException()); - } + cts.Cancel(); + return Task.CompletedTask; + }; + + _signalrConnection.On("receiveMessage", (message) => + { + _signalRReceived.Add(message); }); + var connected = false; var retryCount = 0; - while (_signalrConnection.State != ConnectionState.Connected) + while (!connected) { - if (retryCount > 25) + try { - Assert.Fail("Couldn't establish signalr connection. State: {0}", _signalrConnection.State); + Console.WriteLine("Connecting to signalR"); + + await _signalrConnection.StartAsync(); + connected = true; + break; + } + catch (Exception e) + { + if (retryCount > 25) + { + Assert.Fail("Couldn't establish signalR connection"); + } } retryCount++; - Console.WriteLine("Connecting to signalR" + _signalrConnection.State); Thread.Sleep(200); } - - _signalrConnection.Received += json => _signalRReceived.Add(Json.Deserialize(json)); ; } public static void WaitForCompletion(Func predicate, int timeout = 10000, int interval = 500) diff --git a/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj b/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj index 3912abe3c..a3b0889cb 100644 --- a/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj +++ b/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj @@ -3,10 +3,10 @@ net462 - + - \ No newline at end of file + diff --git a/src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs b/src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs new file mode 100644 index 000000000..9b16fcf60 --- /dev/null +++ b/src/NzbDrone.SignalR/IBroadcastSignalRMessage.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace NzbDrone.SignalR +{ + public interface IBroadcastSignalRMessage + { + bool IsConnected { get; } + Task BroadcastMessage(SignalRMessage message); + } +} diff --git a/src/NzbDrone.SignalR/MessageHub.cs b/src/NzbDrone.SignalR/MessageHub.cs new file mode 100644 index 000000000..9d0494e34 --- /dev/null +++ b/src/NzbDrone.SignalR/MessageHub.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using NzbDrone.Common.EnvironmentInfo; + +namespace NzbDrone.SignalR +{ + public class SignalRMessageBroadcaster : IBroadcastSignalRMessage + { + private readonly IHubContext _hubContext; + + public SignalRMessageBroadcaster(IHubContext hubContext) + { + _hubContext = hubContext; + } + + public async Task BroadcastMessage(SignalRMessage message) + { + await _hubContext.Clients.All.SendAsync("receiveMessage", message); + } + + public bool IsConnected => MessageHub.IsConnected; + } + + public class MessageHub : Hub + { + private static HashSet _connections = new HashSet(); + + public static bool IsConnected + { + get + { + lock (_connections) + { + return _connections.Count != 0; + } + } + } + + public override async Task OnConnectedAsync() + { + lock (_connections) + { + _connections.Add(Context.ConnectionId); + } + + var message = new SignalRMessage + { + Name = "version", + Body = new + { + Version = BuildInfo.Version.ToString() + } + }; + + await Clients.All.SendAsync("receiveMessage", message); + await base.OnConnectedAsync(); + } + + public override async Task OnDisconnectedAsync(Exception exception) + { + lock (_connections) + { + _connections.Remove(Context.ConnectionId); + } + + await base.OnDisconnectedAsync(exception); + } + } +} diff --git a/src/NzbDrone.SignalR/NoOpPerformanceCounter.cs b/src/NzbDrone.SignalR/NoOpPerformanceCounter.cs deleted file mode 100644 index 3f17b7933..000000000 --- a/src/NzbDrone.SignalR/NoOpPerformanceCounter.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Diagnostics; -using Microsoft.AspNet.SignalR.Infrastructure; - -namespace NzbDrone.SignalR -{ - public class NoOpPerformanceCounter : IPerformanceCounter - { - public string CounterName - { - get - { - return GetType().Name; - } - } - - public long Decrement() - { - return 0; - } - - public long Increment() - { - return 0; - } - - public long IncrementBy(long value) - { - return 0; - } - - public long RawValue - { - get { return 0; } - set { } - } - - public void Close() - { - - } - - public void RemoveInstance() - { - - } - - public CounterSample NextSample() - { - return CounterSample.Empty; - } - } -} diff --git a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs deleted file mode 100644 index d0a57bb79..000000000 --- a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNet.SignalR; -using Microsoft.AspNet.SignalR.Infrastructure; -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Serializer; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Datastore.Events; - -namespace NzbDrone.SignalR -{ - public interface IBroadcastSignalRMessage - { - bool IsConnected { get; } - void BroadcastMessage(SignalRMessage message); - } - - public sealed class NzbDronePersistentConnection : PersistentConnection, IBroadcastSignalRMessage - { - private IPersistentConnectionContext Context => ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); - - private static string API_KEY; - private readonly Dictionary _messageHistory; - private HashSet _connections = new HashSet(); - - public NzbDronePersistentConnection(IConfigFileProvider configFileProvider) - { - API_KEY = configFileProvider.ApiKey; - _messageHistory = new Dictionary(); - } - - public bool IsConnected - { - get - { - lock (_connections) - { - return _connections.Count != 0; - } - } - } - - - public void BroadcastMessage(SignalRMessage message) - { - string lastMessage; - if (_messageHistory.TryGetValue(message.Name, out lastMessage)) - { - if (message.Action == ModelAction.Updated && message.Body.ToJson() == lastMessage) - { - return; - } - } - - _messageHistory[message.Name] = message.Body.ToJson(); - - Context.Connection.Broadcast(message); - } - - protected override bool AuthorizeRequest(IRequest request) - { - var apiKey = request.QueryString["apiKey"]; - - if (apiKey.IsNotNullOrWhiteSpace() && apiKey.Equals(API_KEY)) - { - return true; - } - - return false; - } - - protected override Task OnConnected(IRequest request, string connectionId) - { - lock (_connections) - { - _connections.Add(connectionId); - } - - return SendVersion(connectionId); - } - - protected override Task OnReconnected(IRequest request, string connectionId) - { - lock (_connections) - { - _connections.Add(connectionId); - } - - return SendVersion(connectionId); - } - - protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) - { - lock (_connections) - { - _connections.Remove(connectionId); - } - - return base.OnDisconnected(request, connectionId, stopCalled); - } - - private Task SendVersion(string connectionId) - { - return Context.Connection.Send(connectionId, new SignalRMessage - { - Name = "version", - Body = new - { - Version = BuildInfo.Version.ToString() - } - }); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.SignalR/Radarr.SignalR.csproj b/src/NzbDrone.SignalR/Radarr.SignalR.csproj index 95be67ba8..778d16f99 100644 --- a/src/NzbDrone.SignalR/Radarr.SignalR.csproj +++ b/src/NzbDrone.SignalR/Radarr.SignalR.csproj @@ -3,14 +3,12 @@ net462 - - - - - + + + - \ No newline at end of file + diff --git a/src/NzbDrone.SignalR/RadarrPerformanceCounterManager.cs b/src/NzbDrone.SignalR/RadarrPerformanceCounterManager.cs deleted file mode 100644 index 901aacbb1..000000000 --- a/src/NzbDrone.SignalR/RadarrPerformanceCounterManager.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Threading; -using Microsoft.AspNet.SignalR.Infrastructure; - -namespace NzbDrone.SignalR -{ - public class RadarrPerformanceCounterManager : IPerformanceCounterManager - { - private readonly IPerformanceCounter _counter = new NoOpPerformanceCounter(); - - public void Initialize(string instanceName, CancellationToken hostShutdownToken) - { - - } - - public IPerformanceCounter LoadCounter(string categoryName, string counterName, string instanceName, bool isReadOnly) - { - return _counter; - } - - public IPerformanceCounter ConnectionsConnected => _counter; - public IPerformanceCounter ConnectionsReconnected => _counter; - public IPerformanceCounter ConnectionsDisconnected => _counter; - public IPerformanceCounter ConnectionsCurrent => _counter; - public IPerformanceCounter ConnectionMessagesReceivedTotal => _counter; - public IPerformanceCounter ConnectionMessagesSentTotal => _counter; - public IPerformanceCounter ConnectionMessagesReceivedPerSec => _counter; - public IPerformanceCounter ConnectionMessagesSentPerSec => _counter; - public IPerformanceCounter MessageBusMessagesReceivedTotal => _counter; - public IPerformanceCounter MessageBusMessagesReceivedPerSec => _counter; - public IPerformanceCounter ScaleoutMessageBusMessagesReceivedPerSec => _counter; - public IPerformanceCounter MessageBusMessagesPublishedTotal => _counter; - public IPerformanceCounter MessageBusMessagesPublishedPerSec => _counter; - public IPerformanceCounter MessageBusSubscribersCurrent => _counter; - public IPerformanceCounter MessageBusSubscribersTotal => _counter; - public IPerformanceCounter MessageBusSubscribersPerSec => _counter; - public IPerformanceCounter MessageBusAllocatedWorkers => _counter; - public IPerformanceCounter MessageBusBusyWorkers => _counter; - public IPerformanceCounter MessageBusTopicsCurrent => _counter; - public IPerformanceCounter ErrorsAllTotal => _counter; - public IPerformanceCounter ErrorsAllPerSec => _counter; - public IPerformanceCounter ErrorsHubResolutionTotal => _counter; - public IPerformanceCounter ErrorsHubResolutionPerSec => _counter; - public IPerformanceCounter ErrorsHubInvocationTotal => _counter; - public IPerformanceCounter ErrorsHubInvocationPerSec => _counter; - public IPerformanceCounter ErrorsTransportTotal => _counter; - public IPerformanceCounter ErrorsTransportPerSec => _counter; - public IPerformanceCounter ScaleoutStreamCountTotal => _counter; - public IPerformanceCounter ScaleoutStreamCountOpen => _counter; - public IPerformanceCounter ScaleoutStreamCountBuffering => _counter; - public IPerformanceCounter ScaleoutErrorsTotal => _counter; - public IPerformanceCounter ScaleoutErrorsPerSec => _counter; - public IPerformanceCounter ScaleoutSendQueueLength => _counter; - public IPerformanceCounter ConnectionsCurrentForeverFrame => _counter; - public IPerformanceCounter ConnectionsCurrentLongPolling => _counter; - public IPerformanceCounter ConnectionsCurrentServerSentEvents => _counter; - public IPerformanceCounter ConnectionsCurrentWebSockets => _counter; - } -} \ No newline at end of file diff --git a/src/NzbDrone.SignalR/SignalRContractResolver.cs b/src/NzbDrone.SignalR/SignalRContractResolver.cs deleted file mode 100644 index 67e443db0..000000000 --- a/src/NzbDrone.SignalR/SignalRContractResolver.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Newtonsoft.Json.Serialization; - -namespace NzbDrone.SignalR -{ - public class SignalRContractResolver : IContractResolver - { - private readonly IContractResolver _camelCaseContractResolver; - private readonly IContractResolver _defaultContractSerializer; - - public SignalRContractResolver() - { - _defaultContractSerializer = new DefaultContractResolver(); - _camelCaseContractResolver = new CamelCasePropertyNamesContractResolver(); - } - - public JsonContract ResolveContract(Type type) - { - var fullName = type.FullName; - if (fullName.StartsWith("NzbDrone") || fullName.StartsWith("Radarr")) - { - return _camelCaseContractResolver.ResolveContract(type); - } - - return _defaultContractSerializer.ResolveContract(type); - } - } -} diff --git a/src/NzbDrone.SignalR/SignalRJsonSerializer.cs b/src/NzbDrone.SignalR/SignalRJsonSerializer.cs deleted file mode 100644 index f86795b90..000000000 --- a/src/NzbDrone.SignalR/SignalRJsonSerializer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNet.SignalR; -using Newtonsoft.Json; -using NzbDrone.Common.Serializer; - -namespace NzbDrone.SignalR -{ - public static class SignalRJsonSerializer - { - private static JsonSerializer _serializer; - private static JsonSerializerSettings _serializerSettings; - - public static void Register() - { - _serializerSettings = Json.GetSerializerSettings(); - _serializerSettings.ContractResolver = new SignalRContractResolver(); - _serializerSettings.Formatting = Formatting.None; // ServerSentEvents doesn't like newlines - - _serializer = JsonSerializer.Create(_serializerSettings); - - GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => _serializer); - } - } -} diff --git a/src/NzbDrone.SignalR/SignalrDependencyResolver.cs b/src/NzbDrone.SignalR/SignalrDependencyResolver.cs deleted file mode 100644 index 9007e1d41..000000000 --- a/src/NzbDrone.SignalR/SignalrDependencyResolver.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Microsoft.AspNet.SignalR; -using Microsoft.AspNet.SignalR.Infrastructure; -using NzbDrone.Common.Composition; - -namespace NzbDrone.SignalR -{ - public class SignalRDependencyResolver : DefaultDependencyResolver - { - private readonly IContainer _container; - - public static void Register(IContainer container) - { - GlobalHost.DependencyResolver = new SignalRDependencyResolver(container); - } - - private SignalRDependencyResolver(IContainer container) - { - _container = container; - var performanceCounterManager = new RadarrPerformanceCounterManager(); - Register(typeof(IPerformanceCounterManager), () => performanceCounterManager); - } - - public override object GetService(Type serviceType) - { - // Microsoft.AspNet.SignalR.Infrastructure.AckSubscriber is not registered in our internal contaiiner, - // but it still gets treated like it is (possibly due to being a concrete type). - - var fullName = serviceType.FullName; - - if (fullName == "Microsoft.AspNet.SignalR.Infrastructure.AckSubscriber" || - fullName == "Newtonsoft.Json.JsonSerializer") - { - return base.GetService(serviceType); - } - - if (_container.IsTypeRegistered(serviceType)) - { - return _container.Resolve(serviceType); - } - - return base.GetService(serviceType); - } - } -} \ No newline at end of file diff --git a/src/Radarr.Api.V2/Config/HostConfigModule.cs b/src/Radarr.Api.V2/Config/HostConfigModule.cs index b64805731..85a81f6ae 100644 --- a/src/Radarr.Api.V2/Config/HostConfigModule.cs +++ b/src/Radarr.Api.V2/Config/HostConfigModule.cs @@ -44,7 +44,7 @@ namespace Radarr.Api.V2.Config SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.SslPort).NotEqual(c => c.Port).When(c => c.EnableSsl); - SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows); + SharedValidator.RuleFor(c => c.SslCertPath).NotEmpty().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default"); SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script); diff --git a/src/Radarr.Api.V2/Config/HostConfigResource.cs b/src/Radarr.Api.V2/Config/HostConfigResource.cs index cbf35d69f..3f8575bfa 100644 --- a/src/Radarr.Api.V2/Config/HostConfigResource.cs +++ b/src/Radarr.Api.V2/Config/HostConfigResource.cs @@ -22,7 +22,8 @@ namespace Radarr.Api.V2.Config public string ConsoleLogLevel { get; set; } public string Branch { get; set; } public string ApiKey { get; set; } - public string SslCertHash { get; set; } + public string SslCertPath { get; set; } + public string SslCertPassword { get; set; } public string UrlBase { get; set; } public bool UpdateAutomatically { get; set; } public UpdateMechanism UpdateMechanism { get; set; } @@ -61,7 +62,8 @@ namespace Radarr.Api.V2.Config ConsoleLogLevel = model.ConsoleLogLevel, Branch = model.Branch, ApiKey = model.ApiKey, - SslCertHash = model.SslCertHash, + SslCertPath = model.SslCertPath, + SslCertPassword = model.SslCertPassword, UrlBase = model.UrlBase, UpdateAutomatically = model.UpdateAutomatically, UpdateMechanism = model.UpdateMechanism, diff --git a/yarn.lock b/yarn.lock index 8589787ea..13ae7e989 100644 --- a/yarn.lock +++ b/yarn.lock @@ -923,6 +923,15 @@ normalize-path "^2.0.1" through2 "^2.0.3" +"@microsoft/signalr@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-3.0.0.tgz#df03564f900957db0a62469cad576eb573368c9d" + integrity sha512-M0KMWvJ62yZuizPxFLZakitJb4aOZkJH6epXTLvp5LednJZdzacRDxWT3La7Cexp1cHxVbldBFtc3jrdfwmtxw== + dependencies: + eventsource "^1.0.7" + request "^2.88.0" + ws "^6.0.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1301,7 +1310,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== @@ -1586,6 +1595,18 @@ asn1.js@^4.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + assert@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" @@ -1619,6 +1640,11 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -1626,6 +1652,11 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -1644,6 +1675,16 @@ autoprefixer@9.6.1, autoprefixer@^9.5.1: postcss "^7.0.17" postcss-value-parser "^4.0.0" +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -1785,6 +1826,13 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + beeper@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" @@ -2095,6 +2143,11 @@ caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9" integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw== +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + ccount@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386" @@ -2340,6 +2393,13 @@ color@^0.11.0: color-convert "^1.3.0" color-string "^0.3.0" +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^2.2.0, commander@^2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" @@ -2470,7 +2530,7 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-util-is@~1.0.0: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -2648,6 +2708,13 @@ d@1: es5-ext "^0.10.50" type "^1.0.1" +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -2773,6 +2840,11 @@ del@5.0.0: p-map "^2.0.0" rimraf "^2.6.3" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + delegate@^3.1.2: version "3.2.0" resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" @@ -2984,6 +3056,14 @@ each-props@^1.3.0: is-plain-object "^2.0.1" object.defaults "^1.1.0" +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + electron-to-chromium@^1.3.191: version "1.3.247" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.247.tgz#ff2332376150436599265b2dfd7a539f214f4ade" @@ -3357,6 +3437,13 @@ events@^3.0.0: resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -3448,7 +3535,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -3483,6 +3570,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + fancy-log@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" @@ -3765,6 +3862,20 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -3884,6 +3995,13 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -4268,6 +4386,19 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -4413,6 +4544,15 @@ htmlparser2@^3.10.0: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -4895,6 +5035,11 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.0" +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + is-unc-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" @@ -4972,7 +5117,7 @@ isomorphic-fetch@^2.1.1: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -isstream@^0.1.2: +isstream@^0.1.2, isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= @@ -4982,7 +5127,7 @@ jdu@1.0.0: resolved "https://registry.yarnpkg.com/jdu/-/jdu-1.0.0.tgz#28f1e388501785ae0a1d93e93ed0b14dd41e51ce" integrity sha1-KPHjiFAXha4KHZPpPtCxTdQeUc4= -jquery@3.4.1, jquery@>=1.6.4: +jquery@3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== @@ -5010,6 +5155,11 @@ js-yaml@^3.13.0, js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -5030,11 +5180,21 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -5054,6 +5214,16 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + jsx-ast-utils@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb" @@ -5658,6 +5828,18 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + dependencies: + mime-db "1.40.0" + mime@^2.3.1, mime@^2.4.4: version "2.4.4" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" @@ -6102,6 +6284,11 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6252,6 +6439,13 @@ ordered-read-streams@^1.0.0: dependencies: readable-stream "^2.0.1" +original@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== + dependencies: + url-parse "^1.4.3" + os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -6946,6 +7140,11 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= +psl@^1.1.24: + version "1.4.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" + integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -6988,7 +7187,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4: +punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -7008,6 +7207,11 @@ qs@^6.4.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081" integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w== +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -7026,6 +7230,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -7636,6 +7845,32 @@ replace-homedir@^1.0.0: is-absolute "^1.0.0" remove-trailing-separator "^1.1.0" +request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + 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.0" + 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.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -7651,6 +7886,11 @@ require-nocache@1.0.0: resolved "https://registry.yarnpkg.com/require-nocache/-/require-nocache-1.0.0.tgz#a665d0b60a07e8249875790a4d350219d3c85fa3" integrity sha1-pmXQtgoH6CSYdXkKTTUCGdPIX6M= +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + reselect@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" @@ -7854,7 +8094,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -7998,13 +8238,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= -signalr@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/signalr/-/signalr-2.4.1.tgz#57cde6e0bf43265028e0ca3d954a8577b9e336e2" - integrity sha512-HhIcA9kOE9WBs/DPHd+9jN90GDeSD7RRAETcmxn80laDBQmkQeHblzGBNw4rBzn1behe2WiFYQcbKyx11H3ADw== - dependencies: - jquery ">=1.6.4" - slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -8160,6 +8393,21 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + 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" + ssri@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" @@ -8761,6 +9009,14 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + traverse@~0.6.3: version "0.6.6" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" @@ -8806,6 +9062,18 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -9012,6 +9280,14 @@ url-loader@2.0.1: mime "^2.4.4" schema-utils "^1.0.0" +url-parse@^1.4.3: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -9051,6 +9327,11 @@ util@^0.11.0: dependencies: inherits "2.0.3" +uuid@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + v8flags@^3.0.1: version "3.1.3" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" @@ -9076,6 +9357,15 @@ value-or-function@^3.0.0: resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vfile-location@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.5.tgz#c83eb02f8040228a8d2b3f10e485be3e3433e0a2" @@ -9396,6 +9686,13 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" +ws@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + x-is-string@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"