From fd9963e7eb6ef5c269079a0df97599857f680cee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Fri, 10 Feb 2017 02:19:38 -0300
Subject: [PATCH 01/83] Testing default modules
---
tests/configs/without_modules.js | 23 +++++++++++
tests/e2e/without_modules.js | 68 ++++++++++++++++++++++++++++++++
2 files changed, 91 insertions(+)
create mode 100644 tests/configs/without_modules.js
create mode 100644 tests/e2e/without_modules.js
diff --git a/tests/configs/without_modules.js b/tests/configs/without_modules.js
new file mode 100644
index 00000000..921e71d7
--- /dev/null
+++ b/tests/configs/without_modules.js
@@ -0,0 +1,23 @@
+/* Magic Mirror Test default config for modules
+ *
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8080,
+ ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.10.1"],
+
+ language: "en",
+ timeFormat: 24,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ }
+
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/without_modules.js b/tests/e2e/without_modules.js
new file mode 100644
index 00000000..828891ba
--- /dev/null
+++ b/tests/e2e/without_modules.js
@@ -0,0 +1,68 @@
+const Application = require("spectron").Application;
+const path = require("path");
+const chai = require("chai");
+const chaiAsPromised = require("chai-as-promised");
+
+var electronPath = path.join(__dirname, "../../", "node_modules", ".bin", "electron");
+
+if (process.platform === "win32") {
+ electronPath += ".cmd";
+}
+
+var appPath = path.join(__dirname, "../../js/electron.js");
+
+var app = new Application({
+ path: electronPath,
+ args: [appPath]
+});
+
+global.before(function () {
+ chai.should();
+ chai.use(chaiAsPromised);
+});
+
+
+
+describe("Check configuration without modules", function () {
+ this.timeout(20000);
+
+ before(function() {
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/without_modules.js";
+ });
+
+ beforeEach(function (done) {
+ app.start().then(function() { done(); } );
+ });
+
+ afterEach(function (done) {
+ app.stop().then(function() { done(); });
+ });
+
+ it("Show the message MagicMirror title", function () {
+ return app.client.waitUntilWindowLoaded()
+ .getText("#module_1_helloworld .module-content").should.eventually.equal("Magic Mirror2")
+ });
+
+ it("Show the message create file config", function () {
+ return app.client.waitUntilWindowLoaded()
+ .getText("#module_2_helloworld .module-content").should.eventually.equal("Please create a config file.")
+ });
+
+ it("Show the message See more information in README", function () {
+ return app.client.waitUntilWindowLoaded()
+ .getText("#module_3_helloworld .module-content").should.eventually.equal("See README for more information.")
+ });
+
+ it("Show the message recomended use a linter for Javascript for check configuration", function () {
+ return app.client.waitUntilWindowLoaded()
+ .getText("#module_4_helloworld .module-content").should.eventually.equal("If you get this message while your config file is already\ncreated, your config file probably contains an error.\nUse a JavaScript linter to validate your file.")
+ });
+
+ it("Show the text Michael's website", function () {
+ return app.client.waitUntilWindowLoaded()
+ .getText("#module_5_helloworld .module-content").should.eventually.equal("www.michaelteeuw.nl");
+ });
+
+});
+
From db87f9e15b79827e947316c3ffba5b4ca49854b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Mon, 20 Feb 2017 08:19:36 -0300
Subject: [PATCH 02/83] init sample test case newsfeed_spec
---
tests/configs/data/feed_test_newtimes.xml | 534 ++++++++++++++++++++++
tests/configs/modules/newsfeed/default.js | 38 ++
tests/e2e/modules/newsfeed_spec.js | 31 ++
3 files changed, 603 insertions(+)
create mode 100644 tests/configs/data/feed_test_newtimes.xml
create mode 100644 tests/configs/modules/newsfeed/default.js
create mode 100644 tests/e2e/modules/newsfeed_spec.js
diff --git a/tests/configs/data/feed_test_newtimes.xml b/tests/configs/data/feed_test_newtimes.xml
new file mode 100644
index 00000000..96b7dda6
--- /dev/null
+++ b/tests/configs/data/feed_test_newtimes.xml
@@ -0,0 +1,534 @@
+
+ -Hr#rPSLp<}q;H4)Fs
z(;?wmPeAu##DvWn1p0FN2kP)lIT7EwD|Id;jw#~I6h-|N8!(T8=`u$Aqm3H wSf7x>n6cqsK }`q)ur5MCbr|M8;%JDhunSwtWGUf>yR4
zYT~xr*5el3`%!SR1oGdGT5xJBHV;2|8#g%Cem5m$xcx)*jp9B>Gx$+_Nq!da|17!x
zyC!3AU}R-r_7BP3&h|f~-~Zs?LK3v3fEf@%cHXH5cqx*LDRs#SDZc=AM2NRwwpOQx
zGKyQ0zucVG) x7z(gXfEi@{pI!cIDf}Q
z? X#PpR&
zg94oxHj1>67e9GPuj2Tx2-EC1sn%d=p{{jV{m>$`E_zx0EEQG!MLx+5izTtDk~;UU
zu~Cmtv1wO0L6=(&? La entrada QPanel 0.13.0 aparece primero en Rodrigo Ramírez Norambuena. Para instalar esta nueva versión, la debes descargar de En al README.md puedes encontrar las instrucciones para hacer que funcione en tu sistema. En esta nueva versión cuenta con los siguientes cambios: Si deseas colaborar con el proyecto puedes agregar nuevas sugerencias mediante un issue ó colaborar mediante mediante un Pull Request. Ahora si necesitas soporte comercial para instalaciones, personalizaciones o nuevas características lo puedes solicitar en https://boxtub.com/qpanel/ La entrada QPanel 0.13.0 aparece primero en Rodrigo Ramírez Norambuena. La entrada Problema VirtualBox “starting virtual machine” … aparece primero en Rodrigo Ramírez Norambuena. Ninguna, pero ninguna maquina arrancó, se quedaban en ese mensaje. Fue de esos instantes en que sudas helado … Con un poco de investigación fue a parar al archivo ~/.VirtualBox/VBoxSVC.log que indicaba Fui… algo de donde agarrarse. Mirando un poco mas se trataba de problemas con los permisos al vboxdrvu, mirando indicaba que tenía 0600. El tema es que deben estar en 0666, le cambias los permisos y eso soluciona el problema La entrada Problema VirtualBox “starting virtual machine” … aparece primero en Rodrigo Ramírez Norambuena. La entrada Mejorando la consola interactiva de Python aparece primero en Rodrigo Ramírez Norambuena.W%c6UqgC!6FN#eO5?)
zqLDqg+t{@kfe
!5@}Wx3$Ui}PIT0CjMMBKxA6#!9BKtROoE
zo3Imy0#!gGJ0L37>FNzck5iz^mOz@yk!g~0S*I_d2o1WZ5&=}6DKD}oxIHoZXNySU
zr!7%du%-3$95L$i%i)&%3avwrL7+mI7J#)TY!%wvE=vW8;T-Hzuuwtai}y)b2DSY+
z&d*!GIl3E7rX$RQq?9>RP6xO?y6>w4`q@7p%NGZ?l!n&Ne1n~0MEWvPVG3#F)%wKD
zt)`d^f(ef_gVjzn`KW#bn5#k&fPvSNuw`&y)U!O5=!vnetHz`?=AgSn4>3I45~~<}
zdVT~FhJn-Id_+S4x-`QN2|VzJD9=V8xc0vQCR~~1;xaGK8hJ|ftqS$$0EE|;%3Cv7
zkSL&QB!?E|v4w;`%6t?QS;_Cto(#-b=tKqd-qiDK=N$OPZ~6_R-M
zUa-##^!io
P)_
0Q+UUE=-ixrX{O-max_as6q2Ah2;Q
zZ!IfdE)#gX*T#N!0y?bg`
zQf&+;wA)6~&wxy0@krhB+$3?|db^V*djy8*}VHAO0L43NZi2NQn7gHIhpm3ngrc)=%AD
zLZY{BNH=%?O>`IOu8|`fYw4gaXTF&*Z5xrGKjH(wj#hfJR5Ycqk$}87Pwa!rkt(#O
zyPr3cJfed3kLl#FuHm2c@bYjj^HA~Jc(X72
0aHhx#L
z#hu4d&vTj4xUZDiwN$;o9W%X5?BD5DG`szFo25*7-o
FG>j8-)%doCD?G|*?Tid(uL
zIP{WD?hZ9QX0zg=U`Tq9SalOQ&yrllFfH7fP;4BV-gJ2)%4;;-2kA*depN=R_h!0t
zcqL<>ByGfNkj%Vh|xXxEcgzf8<4QD
z!JMPo`JlTnCYw=|sik5m18Sa|5_LiWWY*xa(%v2BUM-_My$oVDrjknrbQiFD!M+Kn
z9+2M^YnUSO5GAXjj`C0w3?|V^w-58zUrTF{=u}|7^WKyWqTC;%J;{w!iptuw6@nTQ
zF@KuT@sJ_gf{1#M7FD)|^BNh*Utgh#&4#3k#I-Bs_F2$Q*J!u2R*
**Possible values:** Any public accessble .ical calendar.
| `symbol` | The symbol to show in front of an event. This property is optional.
**Possible values:** See [Font Awesome](http://fontawesome.io/icons/) website.
-| `color` | The font color of an event from this calendar. This property should be set if the config is set to colored: true.
**Possible values:** HEX, RGB or RGBA values (#efefef, rgb(242,242,242), rgba(242,242,242,0.5)).
+| `color` | The font color of an event from this calendar. This property should be set if the config is set to colored: true.
**Possible values:** HEX, RGB or RGBA values (#efefef, rgb(242,242,242), rgba(242,242,242,0.5)).
| `repeatingCountTitle` | The count title for yearly repating events in this calendar.
**Example:** `'Birthday'`
-| `user` | The username for HTTP Basic authentication.
-| `pass` | The password for HTTP Basic authentication.
| `maximumEntries` | The maximum number of events shown. Overrides global setting. **Possible values:** `0` - `100`
| `maximumNumberOfDays` | The maximum number of days in the future. Overrides global setting
+| `auth` | The object containing options for authentication against the calendar.
+
+
+#### Calendar authentication options:
+| Option | Description
+| --------------------- | -----------
+| `user` | The username for HTTP authentication.
+| `pass` | The password for HTTP authentication. (If you use Bearer authentication, this should be your BearerToken.)
+| `method` | Which authentication method should be used. HTTP Basic, Digest and Bearer authentication methods are supported. Basic authentication is used by default if this option is omitted. **Possible values:** `digest`, `basic`, `bearer` **Default value:** `basic`
diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js
index 644fc1f6..79e9edca 100644
--- a/modules/default/calendar/calendar.js
+++ b/modules/default/calendar/calendar.js
@@ -72,10 +72,18 @@ Module.register("calendar", {
var calendarConfig = {
maximumEntries: calendar.maximumEntries,
- maximumNumberOfDays: calendar.maximumNumberOfDays,
+ maximumNumberOfDays: calendar.maximumNumberOfDays
};
- this.addCalendar(calendar.url, calendar.user, calendar.pass, calendarConfig);
+ // we check user and password here for backwards compatibility with old configs
+ if(calendar.user && calendar.pass){
+ calendar.auth = {
+ user: calendar.user,
+ pass: calendar.pass
+ }
+ }
+
+ this.addCalendar(calendar.url, calendar.auth, calendarConfig);
}
this.calendarData = {};
@@ -313,14 +321,13 @@ Module.register("calendar", {
*
* argument url string - Url to add.
*/
- addCalendar: function (url, user, pass, calendarConfig) {
+ addCalendar: function (url, auth, calendarConfig) {
this.sendSocketNotification("ADD_CALENDAR", {
url: url,
maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries,
maximumNumberOfDays: calendarConfig.maximumNumberOfDays || this.config.maximumNumberOfDays,
fetchInterval: this.config.fetchInterval,
- user: user,
- pass: pass
+ auth: auth
});
},
diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js
index d5ca075e..4728ffae 100644
--- a/modules/default/calendar/calendarfetcher.js
+++ b/modules/default/calendar/calendarfetcher.js
@@ -8,7 +8,7 @@
var ical = require("./vendor/ical.js");
var moment = require("moment");
-var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumberOfDays, user, pass) {
+var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumberOfDays, auth) {
var self = this;
var reloadTimer = null;
@@ -32,11 +32,23 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
}
};
- if (user && pass) {
- opts.auth = {
- user: user,
- pass: pass,
- sendImmediately: true
+ if (auth) {
+ if(auth.method === 'bearer'){
+ opts.auth = {
+ bearer: auth.pass
+ }
+
+ }else{
+ opts.auth = {
+ user: auth.user,
+ pass: auth.pass
+ };
+
+ if(auth.method === 'digest'){
+ opts.auth.sendImmediately = false;
+ }else{
+ opts.auth.sendImmediately = true;
+ }
}
}
@@ -47,7 +59,7 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
return;
}
- //console.log(data);
+ // console.log(data);
newEvents = [];
var limitFunction = function(date, i) {return i < maximumEntries;};
diff --git a/modules/default/calendar/debug.js b/modules/default/calendar/debug.js
index 9b72d51d..ddf0fb42 100644
--- a/modules/default/calendar/debug.js
+++ b/modules/default/calendar/debug.js
@@ -8,14 +8,22 @@
var CalendarFetcher = require("./calendarfetcher.js");
-var url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics";
+var url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
+// var url = "https://www.googleapis.com/calendar/v3/calendars/primary/events/"; // URL for Bearer auth (must be configured in Google OAuth2 first)
var fetchInterval = 60 * 60 * 1000;
var maximumEntries = 10;
var maximumNumberOfDays = 365;
+var user = "magicmirror";
+var pass = "MyStrongPass";
+
+var auth = {
+ user: user,
+ pass: pass
+};
console.log("Create fetcher ...");
-fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries, maximumNumberOfDays);
+fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries, maximumNumberOfDays, auth);
fetcher.onReceive(function(fetcher) {
console.log(fetcher.events());
@@ -29,4 +37,4 @@ fetcher.onError(function(fetcher, error) {
fetcher.startFetch();
-console.log("Create fetcher done! ");
+console.log("Create fetcher done! ");
\ No newline at end of file
diff --git a/modules/default/calendar/node_helper.js b/modules/default/calendar/node_helper.js
index cc511659..90c286c8 100644
--- a/modules/default/calendar/node_helper.js
+++ b/modules/default/calendar/node_helper.js
@@ -24,7 +24,7 @@ module.exports = NodeHelper.create({
socketNotificationReceived: function(notification, payload) {
if (notification === "ADD_CALENDAR") {
//console.log('ADD_CALENDAR: ');
- this.createFetcher(payload.url, payload.fetchInterval, payload.maximumEntries, payload.maximumNumberOfDays, payload.user, payload.pass);
+ this.createFetcher(payload.url, payload.fetchInterval, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth);
}
},
@@ -36,7 +36,7 @@ module.exports = NodeHelper.create({
* attribute reloadInterval number - Reload interval in milliseconds.
*/
- createFetcher: function(url, fetchInterval, maximumEntries, maximumNumberOfDays, user, pass) {
+ createFetcher: function(url, fetchInterval, maximumEntries, maximumNumberOfDays, auth) {
var self = this;
if (!validUrl.isUri(url)) {
@@ -47,7 +47,7 @@ module.exports = NodeHelper.create({
var fetcher;
if (typeof self.fetchers[url] === "undefined") {
console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
- fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries, maximumNumberOfDays, user, pass);
+ fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries, maximumNumberOfDays, auth);
fetcher.onReceive(function(fetcher) {
//console.log('Broadcast events.');
From f8d80422b27a40acadc80a603884cb3217e99f20 Mon Sep 17 00:00:00 2001
From: Beh
Date: Tue, 7 Mar 2017 00:34:17 +0100
Subject: [PATCH 04/83] Fixed Travis CI errors
Changed indentation from spaces to tabs
changed strings from single quote to double quote
---
modules/default/calendar/calendarfetcher.js | 28 ++++++++++-----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js
index 4728ffae..9655f21e 100644
--- a/modules/default/calendar/calendarfetcher.js
+++ b/modules/default/calendar/calendarfetcher.js
@@ -33,22 +33,22 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
};
if (auth) {
- if(auth.method === 'bearer'){
- opts.auth = {
- bearer: auth.pass
- }
+ if(auth.method === "bearer"){
+ opts.auth = {
+ bearer: auth.pass
+ }
- }else{
- opts.auth = {
- user: auth.user,
- pass: auth.pass
- };
+ }else{
+ opts.auth = {
+ user: auth.user,
+ pass: auth.pass
+ };
- if(auth.method === 'digest'){
- opts.auth.sendImmediately = false;
- }else{
- opts.auth.sendImmediately = true;
- }
+ if(auth.method === "digest"){
+ opts.auth.sendImmediately = false;
+ }else{
+ opts.auth.sendImmediately = true;
+ }
}
}
From a15b8077a37963911161c311942be2eb00e4b93c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Tue, 7 Mar 2017 16:21:15 -0300
Subject: [PATCH 05/83] Add test without access by ipWhistelist
This test set a invalid IP address for not have access to MagicMirror.
Creates a request to localhost and port added in configuration and check if
gets 403 HTTP code.
---
tests/configs/noIpWhiteList.js | 25 +++++++++++++++++++++++++
tests/e2e/ipWhistlist_spec.js | 30 ++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+)
create mode 100644 tests/configs/noIpWhiteList.js
create mode 100644 tests/e2e/ipWhistlist_spec.js
diff --git a/tests/configs/noIpWhiteList.js b/tests/configs/noIpWhiteList.js
new file mode 100644
index 00000000..79366e09
--- /dev/null
+++ b/tests/configs/noIpWhiteList.js
@@ -0,0 +1,25 @@
+/* Magic Mirror Test config sample ipWhitelist
+ *
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8080,
+ ipWhitelist: ["x.x.x.x"],
+
+ language: "en",
+ timeFormat: 24,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ },
+
+ modules: [
+ ]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/ipWhistlist_spec.js b/tests/e2e/ipWhistlist_spec.js
new file mode 100644
index 00000000..01ab6787
--- /dev/null
+++ b/tests/e2e/ipWhistlist_spec.js
@@ -0,0 +1,30 @@
+const globalSetup = require("./global-setup");
+const app = globalSetup.app;
+const request = require("request");
+const chai = require("chai");
+const expect = chai.expect;
+
+describe("Set ipWhitelist without access", function () {
+
+ this.timeout(20000);
+
+ before(function() {
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js";
+ });
+
+ beforeEach(function (done) {
+ app.start().then(function() { done(); } );
+ });
+
+ afterEach(function (done) {
+ app.stop().then(function() { done(); });
+ });
+
+ it("should return 403", function (done) {
+ request.get("http://localhost:8080", function (err, res, body) {
+ expect(res.statusCode).to.equal(403);
+ done();
+ });
+ });
+});
From 11fe6cfbb0674572a7724e4283a3437b00fa8e42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Tue, 7 Mar 2017 21:22:27 -0300
Subject: [PATCH 06/83] Add test ipWhistelist = []
This test ipWhistelist on [] to access all IPs
Creates a request to localhost and port added in configuration and
check if gets 200 HTTP code.
---
tests/configs/empty_ipWhiteList.js | 25 +++++++++++++++++++++
tests/e2e/ipWhistlist_spec.js | 36 +++++++++++++++++++++---------
2 files changed, 51 insertions(+), 10 deletions(-)
create mode 100644 tests/configs/empty_ipWhiteList.js
diff --git a/tests/configs/empty_ipWhiteList.js b/tests/configs/empty_ipWhiteList.js
new file mode 100644
index 00000000..232836c3
--- /dev/null
+++ b/tests/configs/empty_ipWhiteList.js
@@ -0,0 +1,25 @@
+/* Magic Mirror Test config sample ipWhitelist
+ *
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8080,
+ ipWhitelist: [],
+
+ language: "en",
+ timeFormat: 24,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ },
+
+ modules: [
+ ]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/ipWhistlist_spec.js b/tests/e2e/ipWhistlist_spec.js
index 01ab6787..46fc4cff 100644
--- a/tests/e2e/ipWhistlist_spec.js
+++ b/tests/e2e/ipWhistlist_spec.js
@@ -4,15 +4,11 @@ const request = require("request");
const chai = require("chai");
const expect = chai.expect;
-describe("Set ipWhitelist without access", function () {
+
+describe("ipWhitelist directive configuration", function () {
this.timeout(20000);
- before(function() {
- // Set config sample for use in test
- process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js";
- });
-
beforeEach(function (done) {
app.start().then(function() { done(); } );
});
@@ -21,10 +17,30 @@ describe("Set ipWhitelist without access", function () {
app.stop().then(function() { done(); });
});
- it("should return 403", function (done) {
- request.get("http://localhost:8080", function (err, res, body) {
- expect(res.statusCode).to.equal(403);
- done();
+ describe("Set ipWhitelist without access", function () {
+ before(function() {
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js";
+ });
+ it("should return 403", function (done) {
+ request.get("http://localhost:8080", function (err, res, body) {
+ expect(res.statusCode).to.equal(403);
+ done();
+ });
});
});
+
+ describe("Set ipWhitelist []", function () {
+ before(function() {
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/empty_ipWhiteList.js";
+ });
+ it("should return 200", function (done) {
+ request.get("http://localhost:8080", function (err, res, body) {
+ expect(res.statusCode).to.equal(200);
+ done();
+ });
+ });
+ });
+
});
From afe2b934def61a633f29ebaa140364c37fb5391d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Wed, 8 Mar 2017 10:36:08 -0300
Subject: [PATCH 07/83] test env requst http://localhost:8080
This test expect get 200 HTTP code on get request to
http://localhost:8080
---
tests/e2e/env_spec.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/tests/e2e/env_spec.js b/tests/e2e/env_spec.js
index 99a7f657..04bb5542 100644
--- a/tests/e2e/env_spec.js
+++ b/tests/e2e/env_spec.js
@@ -1,5 +1,8 @@
const globalSetup = require("./global-setup");
const app = globalSetup.app;
+const request = require("request");
+const chai = require("chai");
+const expect = chai.expect;
describe("Electron app environment", function () {
this.timeout(20000);
@@ -17,7 +20,6 @@ describe("Electron app environment", function () {
app.stop().then(function() { done(); });
});
-
it("is set to open new app window", function () {
return app.client.waitUntilWindowLoaded()
.getWindowCount().should.eventually.equal(1);
@@ -28,4 +30,11 @@ describe("Electron app environment", function () {
.getTitle().should.eventually.equal("Magic Mirror");
});
+ it("get request from http://localhost:8080 should return 200", function (done) {
+ request.get("http://localhost:8080", function (err, res, body) {
+ expect(res.statusCode).to.equal(200);
+ done();
+ });
+ });
+
});
From 5770b9dc0e07749c6e64596cb0673f4e37b1f970 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Thu, 9 Mar 2017 17:10:32 -0300
Subject: [PATCH 08/83] Test env 404 not found request
http://localhost:8080/nothing
This test expect gets 404 HTTP code on get request to
http://localhost:8080/nothing
---
tests/e2e/env_spec.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/tests/e2e/env_spec.js b/tests/e2e/env_spec.js
index 04bb5542..202bd5e4 100644
--- a/tests/e2e/env_spec.js
+++ b/tests/e2e/env_spec.js
@@ -37,4 +37,11 @@ describe("Electron app environment", function () {
});
});
+ it("get request from http://localhost:8080/nothing should return 404", function (done) {
+ request.get("http://localhost:8080/nothing", function (err, res, body) {
+ expect(res.statusCode).to.equal(404);
+ done();
+ });
+ });
+
});
From 34f04b1946a2f339e24236d9b58e05f9b4bc9d22 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Tue, 7 Mar 2017 14:12:48 -0300
Subject: [PATCH 09/83] Add note to allow all IP addresses. ipWhitelist
configuration directive.
---
README.md | 2 +-
config/config.js.sample | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 9cadb364..e7b7a841 100644
--- a/README.md
+++ b/README.md
@@ -110,7 +110,7 @@ The following properties can be configured:
| --- | --- |
| `port` | The port on which the MagicMirror² server will run on. The default value is `8080`. |
| `address` | The ip address the accept connections. The default open bind `::` is IPv6 is available or `0.0.0.0` IPv4 run on. Example config: `192.168.10.100`. |
-| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`. It is possible to specify IPs with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or define ip ranges (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`).|
+| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`. It is possible to specify IPs with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or define ip ranges (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`). Set `[]` to allow all IP addresses.|
| `zoom` | This allows to scale the mirror contents with a given zoom factor. The default value is `1.0`|
| `language` | The language of the interface. (Note: Not all elements will be localized.) Possible values are `en`, `nl`, `ru`, `fr`, etc., but the default value is `en`. |
| `timeFormat` | The form of time notation that will be used. Possible values are `12` or `24`. The default is `24`. |
diff --git a/config/config.js.sample b/config/config.js.sample
index eab22972..797f4d75 100644
--- a/config/config.js.sample
+++ b/config/config.js.sample
@@ -6,7 +6,7 @@
var config = {
port: 8080,
- ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
+ ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses.
language: "en",
timeFormat: 24,
From 4fdd12bc48d95a562b92fa297c7a11cd41a0066d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Thu, 9 Mar 2017 21:12:19 -0300
Subject: [PATCH 10/83] Test change port configuration
This test change to 8090 port on configuration and check if system
response there.
---
tests/configs/port_8090.js | 25 +++++++++++++++++++++++++
tests/e2e/port_config.js | 32 ++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+)
create mode 100644 tests/configs/port_8090.js
create mode 100644 tests/e2e/port_config.js
diff --git a/tests/configs/port_8090.js b/tests/configs/port_8090.js
new file mode 100644
index 00000000..6646dff7
--- /dev/null
+++ b/tests/configs/port_8090.js
@@ -0,0 +1,25 @@
+/* Magic Mirror Test config sample enviroment set por 8090
+ *
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8090,
+ ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
+
+ language: "en",
+ timeFormat: 24,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ },
+
+ modules: [
+ ]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/port_config.js b/tests/e2e/port_config.js
new file mode 100644
index 00000000..c0806e85
--- /dev/null
+++ b/tests/e2e/port_config.js
@@ -0,0 +1,32 @@
+const globalSetup = require("./global-setup");
+const app = globalSetup.app;
+const request = require("request");
+const chai = require("chai");
+const expect = chai.expect;
+
+
+describe("port directive configuration", function () {
+
+ this.timeout(20000);
+
+ beforeEach(function (done) {
+ app.start().then(function() { done(); } );
+ });
+
+ afterEach(function (done) {
+ app.stop().then(function() { done(); });
+ });
+
+ describe("Set port 8090", function () {
+ before(function() {
+ // Set config sample for use in this test
+ process.env.MM_CONFIG_FILE = "tests/configs/port_8090.js";
+ });
+ it("should return 200", function (done) {
+ request.get("http://localhost:8090", function (err, res, body) {
+ expect(res.statusCode).to.equal(200);
+ done();
+ });
+ });
+ });
+});
From aa1f515fcf5f83750b211aa3a3d28c046645d17d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Fri, 10 Mar 2017 00:27:47 -0300
Subject: [PATCH 11/83] Remote extra space main.js
---
js/main.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/js/main.js b/js/main.js
index e1a13d8a..604dd410 100644
--- a/js/main.js
+++ b/js/main.js
@@ -66,7 +66,7 @@ var MM = (function() {
var classes = position.replace("_"," ");
var parentWrapper = document.getElementsByClassName(classes);
if (parentWrapper.length > 0) {
- var wrapper = parentWrapper[0].getElementsByClassName("container");
+ var wrapper = parentWrapper[0].getElementsByClassName("container");
if (wrapper.length > 0) {
return wrapper[0];
}
From cfc8117c3c067150ad133c24f7b84dc01c8763db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Fri, 10 Mar 2017 00:52:43 -0300
Subject: [PATCH 12/83] Remove tests case from without_modules with much
hardcode
This test retain
* Check title.
* Check footer with Michael website.
---
tests/e2e/without_modules.js | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/tests/e2e/without_modules.js b/tests/e2e/without_modules.js
index 828891ba..73e845f8 100644
--- a/tests/e2e/without_modules.js
+++ b/tests/e2e/without_modules.js
@@ -44,21 +44,6 @@ describe("Check configuration without modules", function () {
.getText("#module_1_helloworld .module-content").should.eventually.equal("Magic Mirror2")
});
- it("Show the message create file config", function () {
- return app.client.waitUntilWindowLoaded()
- .getText("#module_2_helloworld .module-content").should.eventually.equal("Please create a config file.")
- });
-
- it("Show the message See more information in README", function () {
- return app.client.waitUntilWindowLoaded()
- .getText("#module_3_helloworld .module-content").should.eventually.equal("See README for more information.")
- });
-
- it("Show the message recomended use a linter for Javascript for check configuration", function () {
- return app.client.waitUntilWindowLoaded()
- .getText("#module_4_helloworld .module-content").should.eventually.equal("If you get this message while your config file is already\ncreated, your config file probably contains an error.\nUse a JavaScript linter to validate your file.")
- });
-
it("Show the text Michael's website", function () {
return app.client.waitUntilWindowLoaded()
.getText("#module_5_helloworld .module-content").should.eventually.equal("www.michaelteeuw.nl");
From 1c235aa7611dd4f8912bce2b01e2abdba16eef26 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Fri, 10 Mar 2017 03:25:16 -0300
Subject: [PATCH 13/83] Add test default calendar
---
tests/configs/data/calendar_test.ics | 190 ++++++++++++++++++++++
tests/configs/modules/calendar/default.js | 37 +++++
tests/e2e/modules/calendar_spec.js | 29 ++++
3 files changed, 256 insertions(+)
create mode 100644 tests/configs/data/calendar_test.ics
create mode 100644 tests/configs/modules/calendar/default.js
create mode 100644 tests/e2e/modules/calendar_spec.js
diff --git a/tests/configs/data/calendar_test.ics b/tests/configs/data/calendar_test.ics
new file mode 100644
index 00000000..63e001ce
--- /dev/null
+++ b/tests/configs/data/calendar_test.ics
@@ -0,0 +1,190 @@
+BEGIN:VCALENDAR
+PRODID:-//Google Inc//Google Calendar 70.9054//EN
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:PUBLISH
+X-WR-CALNAME:MagicMirrorTest
+X-WR-TIMEZONE:America/Santiago
+X-WR-CALDESC:Testing propose MagicMirror
+BEGIN:VTIMEZONE
+TZID:America/Santiago
+X-LIC-LOCATION:America/Santiago
+BEGIN:STANDARD
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0400
+TZNAME:-04
+DTSTART:19700510T000000
+RDATE:19700510T030000
+RDATE:19710509T030000
+RDATE:19720514T030000
+RDATE:19730513T030000
+RDATE:19740512T030000
+RDATE:19750511T030000
+RDATE:19760509T030000
+RDATE:19770515T030000
+RDATE:19780514T030000
+RDATE:19790513T030000
+RDATE:19800511T030000
+RDATE:19810510T030000
+RDATE:19820509T030000
+RDATE:19830515T030000
+RDATE:19840513T030000
+RDATE:19850512T030000
+RDATE:19860511T030000
+RDATE:19870510T030000
+RDATE:19880515T030000
+RDATE:19890514T030000
+RDATE:19900513T030000
+RDATE:19910512T030000
+RDATE:19920510T030000
+RDATE:19930509T030000
+RDATE:19940515T030000
+RDATE:19950514T030000
+RDATE:19960512T030000
+RDATE:19970511T030000
+RDATE:19980510T030000
+RDATE:19990509T030000
+RDATE:20000514T030000
+RDATE:20010513T030000
+RDATE:20020512T030000
+RDATE:20030511T030000
+RDATE:20040509T030000
+RDATE:20050515T030000
+RDATE:20060514T030000
+RDATE:20070513T030000
+RDATE:20080511T030000
+RDATE:20090510T030000
+RDATE:20100509T030000
+RDATE:20110515T030000
+RDATE:20120513T030000
+RDATE:20130512T030000
+RDATE:20140511T030000
+RDATE:20150510T030000
+RDATE:20160515T030000
+RDATE:20170514T030000
+RDATE:20180513T030000
+RDATE:20190512T030000
+RDATE:20200510T030000
+RDATE:20210509T030000
+RDATE:20220515T030000
+RDATE:20230514T030000
+RDATE:20240512T030000
+RDATE:20250511T030000
+RDATE:20260510T030000
+RDATE:20270509T030000
+RDATE:20280514T030000
+RDATE:20290513T030000
+RDATE:20300512T030000
+RDATE:20310511T030000
+RDATE:20320509T030000
+RDATE:20330515T030000
+RDATE:20340514T030000
+RDATE:20350513T030000
+RDATE:20360511T030000
+RDATE:20370510T030000
+END:STANDARD
+BEGIN:STANDARD
+TZOFFSETFROM:-0300
+TZOFFSETTO:-0400
+TZNAME:-04
+DTSTART:20380509T000000
+RRULE:FREQ=YEARLY;BYMONTH=5;BYDAY=2SU
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0300
+TZNAME:-03
+DTSTART:19700809T000000
+RDATE:19700809T040000
+RDATE:19710815T040000
+RDATE:19720813T040000
+RDATE:19730812T040000
+RDATE:19740811T040000
+RDATE:19750810T040000
+RDATE:19760815T040000
+RDATE:19770814T040000
+RDATE:19780813T040000
+RDATE:19790812T040000
+RDATE:19800810T040000
+RDATE:19810809T040000
+RDATE:19820815T040000
+RDATE:19830814T040000
+RDATE:19840812T040000
+RDATE:19850811T040000
+RDATE:19860810T040000
+RDATE:19870809T040000
+RDATE:19880814T040000
+RDATE:19890813T040000
+RDATE:19900812T040000
+RDATE:19910811T040000
+RDATE:19920809T040000
+RDATE:19930815T040000
+RDATE:19940814T040000
+RDATE:19950813T040000
+RDATE:19960811T040000
+RDATE:19970810T040000
+RDATE:19980809T040000
+RDATE:19990815T040000
+RDATE:20000813T040000
+RDATE:20010812T040000
+RDATE:20020811T040000
+RDATE:20030810T040000
+RDATE:20040815T040000
+RDATE:20050814T040000
+RDATE:20060813T040000
+RDATE:20070812T040000
+RDATE:20080810T040000
+RDATE:20090809T040000
+RDATE:20100815T040000
+RDATE:20110814T040000
+RDATE:20120812T040000
+RDATE:20130811T040000
+RDATE:20140810T040000
+RDATE:20150809T040000
+RDATE:20160814T040000
+RDATE:20170813T040000
+RDATE:20180812T040000
+RDATE:20190811T040000
+RDATE:20200809T040000
+RDATE:20210815T040000
+RDATE:20220814T040000
+RDATE:20230813T040000
+RDATE:20240811T040000
+RDATE:20250810T040000
+RDATE:20260809T040000
+RDATE:20270815T040000
+RDATE:20280813T040000
+RDATE:20290812T040000
+RDATE:20300811T040000
+RDATE:20310810T040000
+RDATE:20320815T040000
+RDATE:20330814T040000
+RDATE:20340813T040000
+RDATE:20350812T040000
+RDATE:20360810T040000
+RDATE:20370809T040000
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0300
+TZNAME:-03
+DTSTART:20380815T000000
+RRULE:FREQ=YEARLY;BYMONTH=8;BYDAY=2SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTART;TZID=America/Santiago:20170309T100000
+DTEND;TZID=America/Santiago:20170309T110000
+RRULE:FREQ=MONTHLY;INTERVAL=30;BYMONTHDAY=9
+DTSTAMP:20170310T172720Z
+UID:80rl9kuu5bq49gme99eklov27k@google.com
+CREATED:20170310T172400Z
+DESCRIPTION:
+LAST-MODIFIED:20170310T172400Z
+LOCATION:
+SEQUENCE:0
+STATUS:CONFIRMED
+SUMMARY:TestEvent
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/configs/modules/calendar/default.js b/tests/configs/modules/calendar/default.js
new file mode 100644
index 00000000..3f70d930
--- /dev/null
+++ b/tests/configs/modules/calendar/default.js
@@ -0,0 +1,37 @@
+/* Magic Mirror Test config default calendar
+ *
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8080,
+ ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
+
+ language: "en",
+ timeFormat: 12,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ },
+
+ modules: [
+ {
+ module: "calendar",
+ position: "bottom_bar",
+ config: {
+ calendars: [
+ {
+ maximumNumberOfDays: 10000,
+ url: "http://localhost:8080/tests/configs/data/calendar_test.ics"
+ }
+ ]
+ }
+ }
+ ]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/modules/calendar_spec.js b/tests/e2e/modules/calendar_spec.js
new file mode 100644
index 00000000..f8535231
--- /dev/null
+++ b/tests/e2e/modules/calendar_spec.js
@@ -0,0 +1,29 @@
+const globalSetup = require("../global-setup");
+const app = globalSetup.app;
+const chai = require("chai");
+const expect = chai.expect;
+
+describe("Calendar module", function () {
+
+ this.timeout(20000);
+
+ beforeEach(function (done) {
+ app.start().then(function() { done(); } );
+ });
+
+ afterEach(function (done) {
+ app.stop().then(function() { done(); });
+ });
+
+ describe("Default configuration", function() {
+ before(function() {
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/default.js";
+ });
+
+ it("Should return TestEvents", function () {
+ return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
+ });
+ });
+
+});
From ceb4ef2642b81423bae99d42a410d280c7b88d36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Fri, 10 Mar 2017 04:33:27 -0300
Subject: [PATCH 14/83] Add test basic-auth
---
package.json | 1 +
tests/configs/modules/calendar/basic-auth.js | 42 ++++++++++++++++++++
tests/e2e/modules/calendar_spec.js | 16 ++++++++
tests/servers/basic-auth.js | 31 +++++++++++++++
4 files changed, 90 insertions(+)
create mode 100644 tests/configs/modules/calendar/basic-auth.js
create mode 100644 tests/servers/basic-auth.js
diff --git a/package.json b/package.json
index 704f8394..66886cad 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"grunt-markdownlint": "^1.0.13",
"grunt-stylelint": "latest",
"grunt-yamllint": "latest",
+ "http-auth": "^3.1.3",
"mocha": "^3.2.0",
"spectron": "^3.4.1",
"stylelint-config-standard": "latest",
diff --git a/tests/configs/modules/calendar/basic-auth.js b/tests/configs/modules/calendar/basic-auth.js
new file mode 100644
index 00000000..1b210102
--- /dev/null
+++ b/tests/configs/modules/calendar/basic-auth.js
@@ -0,0 +1,42 @@
+/* Magic Mirror Test config default calendar
+ *
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8080,
+ ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
+
+ language: "en",
+ timeFormat: 12,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ },
+
+ modules: [
+ {
+ module: "calendar",
+ position: "bottom_bar",
+ config: {
+ calendars: [
+ {
+ maximumNumberOfDays: 10000,
+ url: "http://localhost:8010/tests/configs/data/calendar_test.ics",
+ auth: {
+ user: "MagicMirror",
+ pass: "CallMeADog",
+ method: "basic"
+ }
+ }
+ ]
+ }
+ }
+ ]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/modules/calendar_spec.js b/tests/e2e/modules/calendar_spec.js
index f8535231..3b4ac736 100644
--- a/tests/e2e/modules/calendar_spec.js
+++ b/tests/e2e/modules/calendar_spec.js
@@ -1,4 +1,5 @@
const globalSetup = require("../global-setup");
+const serverBasicAuth = require("../../servers/basic-auth.js");
const app = globalSetup.app;
const chai = require("chai");
const expect = chai.expect;
@@ -26,4 +27,19 @@ describe("Calendar module", function () {
});
});
+
+ describe("Basic auth", function() {
+ before(function() {
+ serverBasicAuth.listen(8010);
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/basic-auth.js";
+ });
+
+ it("Should return TestEvents", function () {
+ return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
+ });
+ });
+
+
+
});
diff --git a/tests/servers/basic-auth.js b/tests/servers/basic-auth.js
new file mode 100644
index 00000000..6077cf8c
--- /dev/null
+++ b/tests/servers/basic-auth.js
@@ -0,0 +1,31 @@
+var http = require("http");
+var path = require("path");
+var auth = require("http-auth");
+var express = require("express")
+
+var basic = auth.basic({
+ realm: "MagicMirror Area restricted."
+ }, (username, password, callback) => {
+ callback(username === "MagicMirror" && password === "CallMeADog");
+ }
+);
+
+this.server = express();
+this.server.use(auth.connect(basic));
+
+// Set directories availables
+var directories = ["/tests/configs"];
+var directory;
+root_path = path.resolve(__dirname + "/../../");
+for (i in directories) {
+ directory = directories[i];
+ this.server.use(directory, express.static(path.resolve(root_path + directory)));
+}
+
+exports.listen = function () {
+ this.server.listen.apply(this.server, arguments);
+};
+
+exports.close = function (callback) {
+ this.server.close(callback);
+};
From f5c57e84c77d14da5c450b748d3f4e11f8fd7eaf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Fri, 10 Mar 2017 04:36:09 -0300
Subject: [PATCH 15/83] Add test calendar without auth method. Should be set by
default basic.
---
.../configs/modules/calendar/auth-default.js | 41 +++++++++++++++++++
tests/e2e/modules/calendar_spec.js | 12 ++++++
2 files changed, 53 insertions(+)
create mode 100644 tests/configs/modules/calendar/auth-default.js
diff --git a/tests/configs/modules/calendar/auth-default.js b/tests/configs/modules/calendar/auth-default.js
new file mode 100644
index 00000000..3fee5015
--- /dev/null
+++ b/tests/configs/modules/calendar/auth-default.js
@@ -0,0 +1,41 @@
+/* Magic Mirror Test config default calendar with auth by default
+ *
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8080,
+ ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
+
+ language: "en",
+ timeFormat: 12,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ },
+
+ modules: [
+ {
+ module: "calendar",
+ position: "bottom_bar",
+ config: {
+ calendars: [
+ {
+ maximumNumberOfDays: 10000,
+ url: "http://localhost:8011/tests/configs/data/calendar_test.ics",
+ auth: {
+ user: "MagicMirror",
+ pass: "CallMeADog"
+ }
+ }
+ ]
+ }
+ }
+ ]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/modules/calendar_spec.js b/tests/e2e/modules/calendar_spec.js
index 3b4ac736..9bb68baa 100644
--- a/tests/e2e/modules/calendar_spec.js
+++ b/tests/e2e/modules/calendar_spec.js
@@ -41,5 +41,17 @@ describe("Calendar module", function () {
});
+ describe("Basic auth by default", function() {
+ before(function() {
+ serverBasicAuth.listen(8011);
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/auth-default.js";
+ });
+
+ it("Should return TestEvents", function () {
+ return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
+ });
+ });
+
});
From b129fe908c6ade3adf9e06ddd50372caa830b2bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Ram=C3=ADrez=20Norambuena?=
Date: Fri, 10 Mar 2017 04:40:59 -0300
Subject: [PATCH 16/83] Test check backward backward compatibility
authentication method basic on calendar module
Fix travis basic-auth server
---
.../modules/calendar/old-basic-auth.js | 39 +++++++++++++++++++
tests/e2e/modules/calendar_spec.js | 13 +++++++
tests/servers/basic-auth.js | 15 ++++---
3 files changed, 59 insertions(+), 8 deletions(-)
create mode 100644 tests/configs/modules/calendar/old-basic-auth.js
diff --git a/tests/configs/modules/calendar/old-basic-auth.js b/tests/configs/modules/calendar/old-basic-auth.js
new file mode 100644
index 00000000..76e2df3a
--- /dev/null
+++ b/tests/configs/modules/calendar/old-basic-auth.js
@@ -0,0 +1,39 @@
+/* Magic Mirror Test config default calendar
+ * with authenticacion old config
+ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
+ * MIT Licensed.
+ */
+
+var config = {
+ port: 8080,
+ ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
+
+ language: "en",
+ timeFormat: 12,
+ units: "metric",
+ electronOptions: {
+ webPreferences: {
+ nodeIntegration: true,
+ },
+ },
+
+ modules: [
+ {
+ module: "calendar",
+ position: "bottom_bar",
+ config: {
+ calendars: [
+ {
+ maximumNumberOfDays: 10000,
+ url: "http://localhost:8012/tests/configs/data/calendar_test.ics",
+ user: "MagicMirror",
+ pass: "CallMeADog"
+ }
+ ]
+ }
+ }
+ ]
+};
+
+/*************** DO NOT EDIT THE LINE BELOW ***************/
+if (typeof module !== "undefined") {module.exports = config;}
diff --git a/tests/e2e/modules/calendar_spec.js b/tests/e2e/modules/calendar_spec.js
index 9bb68baa..21939f06 100644
--- a/tests/e2e/modules/calendar_spec.js
+++ b/tests/e2e/modules/calendar_spec.js
@@ -53,5 +53,18 @@ describe("Calendar module", function () {
});
});
+ describe("Basic auth backward compatibilty configuration", function() {
+ before(function() {
+ serverBasicAuth.listen(8012);
+ // Set config sample for use in test
+ process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/old-basic-auth.js";
+ });
+
+ it("Should return TestEvents", function () {
+ return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
+ });
+ });
+
+
});
diff --git a/tests/servers/basic-auth.js b/tests/servers/basic-auth.js
index 6077cf8c..238bdc26 100644
--- a/tests/servers/basic-auth.js
+++ b/tests/servers/basic-auth.js
@@ -5,10 +5,9 @@ var express = require("express")
var basic = auth.basic({
realm: "MagicMirror Area restricted."
- }, (username, password, callback) => {
- callback(username === "MagicMirror" && password === "CallMeADog");
- }
-);
+}, (username, password, callback) => {
+ callback(username === "MagicMirror" && password === "CallMeADog");
+});
this.server = express();
this.server.use(auth.connect(basic));
@@ -16,16 +15,16 @@ this.server.use(auth.connect(basic));
// Set directories availables
var directories = ["/tests/configs"];
var directory;
-root_path = path.resolve(__dirname + "/../../");
+rootPath = path.resolve(__dirname + "/../../");
for (i in directories) {
directory = directories[i];
- this.server.use(directory, express.static(path.resolve(root_path + directory)));
+ this.server.use(directory, express.static(path.resolve(rootPath + directory)));
}
exports.listen = function () {
- this.server.listen.apply(this.server, arguments);
+ this.server.listen.apply(this.server, arguments);
};
exports.close = function (callback) {
- this.server.close(callback);
+ this.server.close(callback);
};
From 50f2dded64e0ea8c4a69c48ce87a005debf6e60f Mon Sep 17 00:00:00 2001
From: BeatIdo Ya está disponible la versión 0.13.0 de QPanel
+
+$ tail -f ~/.VirtualBox/VBoxSVC.log
+ 00:08:32.932717 nspr-7 Failed to open "/dev/vboxdrvu", errno=13, rc=VERR_VM_DRIVER_NOT_ACCESSIBLE
+ 00:08:33.555836 nspr-6 Failed to open "/dev/vboxdrvu", errno=13, rc=VERR_VM_DRIVER_NOT_ACCESSIBLE
+$ ls -lh /dev/vboxdrvu
+ crw------- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu
+
+$ sudo chmod 0666 /dev/vboxdrvu
+$ ls -lh /dev/vboxdrvu
+ crw-rw-rw- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu
+
La consola de Python funciona y cumple su cometido. Solo al tipear python te permite entrar en modo interactivo e ir probando cosas.
+El punto es que a veces uno necesita ir un poco más allá. Como autocomentado de código o resaltado de sintaxis, para eso tengo dos truco que utilizo generalmente.
+Este permite añadirle algunos esteriodes a la consolta, en realidad uno, el autocompletado. Esto es de gran ayuda para ir conociendo los metodo que puede tener un objecto, funciones u operaciones.
+Para esto se ocupo rlcompleter y readline.
++
Lo que hace que hacer luego de tipear python es agregar lo siguiente dentro de la consola interativa
+import rlcompleter, readline
+readline.parse_and_bind(‘tab:complete’)
Ya con esto te permite autocomentar código
+
Esto es mejorar un poco más. Es utilizar embed de IPython, ya en la consola digita (copias o pegas) lo siguiente
+from IPython import embed
+embed()
Y el resultado será lo que se ve a continuación… bueno, no?
++ +
+
Si no quieres estar escribiendo cada vez que entras, agregas estas instrucciones en tu archivo ~/.pythonrc.py y lo hará cada vez que entras en el modo interactivo de la consola de Python. Lo que si, tu archivo pythonrc.py debe estar seteado en variable de entorno PYTHONSTARTUP
+ejemplo
+export PYTHONSTARTUP=~/.pythonrc.py
+O lo agregas a un bashrc, zshrc o la shell que ocupes.
+La entrada Mejorando la consola interactiva de Python aparece primero en Rodrigo Ramírez Norambuena.
+]]> +La entrada QPanel 0.12.0 con estadísticas aparece primero en Rodrigo Ramírez Norambuena.
+]]>Para instalar esta nueva versión, debes visitar la siguiente URL
+ +En esta nueva versión las funcionalidades agregadas son:
+Si deseas colaborar con el proyecto puedes agregar nuevas sugerencias mediante un issue ó colaborar mediante mediante un Pull Request
+La entrada QPanel 0.12.0 con estadísticas aparece primero en Rodrigo Ramírez Norambuena.
+]]>La entrada QPanel 0.11.0 con Spy, Whisper y mas aparece primero en Rodrigo Ramírez Norambuena.
+]]>Para instalar esta nueva versión, debes visitar la siguiente URL
+ +Esta versión hemos agregado algunas funcionalidades que los usuarios han ido solicitando.
+Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que están en una cola.
+También el sistema de plantillas se hecho una refactorización para eliminar exceso de codigo HTML usando uno de base.
+Se han agregado una suite de tests unitarios que al contar del avance del proyecto deberían ir incrementando.
+Se ha solucionado un bug con la actualización del color del estado del agente cuando es uno nuevo agregado a la cola.
++
El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un issue.
+La entrada QPanel 0.11.0 con Spy, Whisper y mas aparece primero en Rodrigo Ramírez Norambuena.
+]]>La entrada Añadir Swap a un sistema aparece primero en Rodrigo Ramírez Norambuena.
+]]>La memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM.
+El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto un espacio para la Swap, lo que te lleva a que el sistema pueda tener crash durante la ejecución.
+Para comprobar la asignación de memoria, al ejecutar el comando free nos debería mostrar como algo similar a lo siguiente
++
$ free -m + total used free shared buffers cached +Mem: 494 488 6 1 54 75 +-/+ buffers/cache: 357 136 +Swap: 0 0 0+
En la zona de swap indica que no asignada, valor 0.
+Para asignar swap al sistema se debe un archivo en disco para que sea utilizado como espacio de intercambio, en este caso lo vamos crear uno de 3GB en la raíz del sistema
+fallocate -l 3G /swapfile
+Comprobamos que ha sido creado
+$ ls -lh /swapfile +-rw-r--r-- 1 root root 3.0G Jul 11 13:10 /swapfile ++
Ahora nos toca habilitar el archivo creado. Para eso le asignaremos los permisos
+chmod 600 /swapfile+
Lo siguiente es para convertir el archivo para swap
+mkswap /swapfile+
Para habilitar y asignarla eso como memoria swap al sistema usamos
+swapon /swapfile+
Ya con esto podrémos ver en nuestro sistema la memoria asignada para swap
+$ free -m + total used free shared buffers cached +Mem: 494 486 7 1 51 77 +-/+ buffers/cache: 358 136 +Swap: 3071 0 3071+
+
Para que al reiniciar el sistema esto se mantenga, debemos agregar la siguiente línea al archivo /etc/fstab
+/swapfile none swap sw 0 0
++
Podemos editar /etc/fstab con algún editor como vim, nano o podemos agregar la linea directamente en la desde la cli de la siguiente manera
+echo "/swapfile none swap sw 0 0" >> /etc/fstab+
+
+
La entrada Añadir Swap a un sistema aparece primero en Rodrigo Ramírez Norambuena.
+]]>La entrada QPanel 0.10.0 con vista consolidada aparece primero en Rodrigo Ramírez Norambuena.
+]]>Para instalar esta nueva versión, debes visitar la siguiente URL
+ +Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad.
+La nueva funcionalidad incluida es que ahora es posible contar con una vista consolidada para la información de todas las colas. Que hace tener un mejor control y visualización de lo que está pasando en las colas.
+El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un issue.
+La entrada QPanel 0.10.0 con vista consolidada aparece primero en Rodrigo Ramírez Norambuena.
+]]>La entrada Nerdearla 2016, WebRTC Glue aparece primero en Rodrigo Ramírez Norambuena.
+]]>Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te lo perdiste te recomiendo que estés pendiente para el proximo año.
++
Te podias encontrar con una nuestra como esta
Puedes dar un vistaso a lo registrado por algunos usuarios en Twitter
+El primer día hice un workshop denominado WebRTC Glue, donde muestra como hacer como unificar la experiencia de atención del centro de contacto directamente en la web. Es una presentación práctica donde puedes ver los ejemplos y usarlos como gustes. Están en el repositorio en Gitlab. La presentación la puedes ver aquí
++ +
Haber si nos vemos el próximo año.
++
Update: Puedes ver una parte sin la demostración del workshop
+
+
La entrada Nerdearla 2016, WebRTC Glue aparece primero en Rodrigo Ramírez Norambuena.
+]]>La entrada QPanel 0.9.0 aparece primero en Rodrigo Ramírez Norambuena.
+]]>Para instalar esta nueva versión, debes visitar la siguiente URL
+ +Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de Asterisk.
+Dentro de las cosas que podamos mencionar:
+El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un issue.
+La entrada QPanel 0.9.0 aparece primero en Rodrigo Ramírez Norambuena.
+]]>La entrada Mandar un email desde la shell aparece primero en Rodrigo Ramírez Norambuena.
+]]>Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un echo le pasas por pipe a mail
+echo "Cuerpo del mensaje" | mail -s Asunto a@rodrigoramirez.com+
La entrada Mandar un email desde la shell aparece primero en Rodrigo Ramírez Norambuena.
+]]>3>i_TXkMWH5-NvAh~g;>`Zo&n zN>E_vUv(zXVo_-(P>H+(;i3Mj;b;H2)}i(Ob;Cc62nq^@Q=0{5DlJOqc#%itOrRQn z{<9ABU1U}a&>wtA;5ES2BP|l247YKH$fQ_s>Jq@GN{$Vf5fBv*l;9ES=)(k>DAC~j zbyOs#K<|G$9sjuH70_SLc;5KOty82^fd(1i>!oBEL7WUJ@iD;9X_+yA3(z6PgsfE1 zOKAopm<`^N1JzSvK^yX#9TX^-e&CC=t z$14H@J2fTL1UqN@*pdPM_{qV8keC!OtB(vEkJcG8)4}ls_slVYh@hF%ou p|*5ka8<8X5#;01XAuPyh`D&` =!>U2 zdRYw6fdCx{(18FQ2+)B59SG2Y038Uu20^bu02c&sK>!!zoWK%i4@&|Z9WCJ>LW3Y@ z_e-?S+7RajdJBe@!7xHFj1UYX1oL&!AlTVpDGZ{8LA2117C^NCss&IjfNB9$3!qw< zObEb*09**bg#cWLb27{LY5)zH5gQQ8R|Dt_sG9+xGXQi3)Xjh{XF!)TLI5TjuF&NS zfSU<$GXV~077R2C2ATx}&4Phu!9cTMpjq%tfC~k xwOz&3*^!QxwJqoEs#qK
}(C@m053xv`Fp|l|U+8}@n0=OW6 z1Nvx#04^Bdf&nfV;D9VzAd42rq6M;Ofh<}ei#8bGv;YS*(E?4hKoc#{L<=<00!_3) z6D`n03pCLJO|(E0Ezm>@G|`3tTnNAcL9`(N7Xol0p|OD>)8PcT8GGI3S1?2%?<< za5Df72%-goXn`PFAcz(Sq6LCzfgoBSh!zN<1%haSAX*@Z76_sRf@pytS|Erv6yQLD zv>-uRkRWa7%-BFs!60mbGabwt2)%{?Swes;Ax=38oax}yK z2OWk8sF?DmhEt2Fa%vCNNHtUK)PCw9b(%U)Jx5)o`l%18PpQu#*7=Kz0OFi77cZCL zF8(fKTqd}z6u1c71p$H(!CXOvAXbnh*d<646bs4)HG+CUpWp?-tAckSc6mqev*3Y% z2>x^xxaLBfvI3%%9T2BH>Uz@kyz6tW{X%cyEa5!iBH=RO(-4zP6kZp;C;UkGnecmh z97G#u(DUfU5M{hb_tUS_@6sR9UqUo-vuHa+4=tikMPG;~h_&Kb5FuPFUM>z3uMvlf zH;K22d&JL)Ul8AMle_u3jdh#i7T`9^ZK>N@w`bh8x<$F^-ICmnx}9_zfH>f7x3AoO zgh-&x5exKWhCwV)!%Sc%Gqafm%yNkRWib^@J9C0L%e={a0CB$utQ+ge`miI|@$7VV z3#(&yv90WW_Aq;#J;Q#;{>c6zagorHVG@mGkz|?VX~`Bzyd+s-lw?TqC8d&H$py)E z$$;dx m zA)1xxo(EAZK8Dri-sOG)qE>IXf9Z}rq!5*w;t}dG&ts{_lOC%))_LeX@;n+m4tt#N zxaRS$$A=!DdHm1gj>qqwLQfx0jpumJ>7JpU^E?-OuJByvxxq8WGr=>@bB||}=RVIK z&-0$Icz)pdo#!u}_dIQ$LtauZ&TEXN zd!u)Rx6V7++vuI;UF==qZSmgkec1cF_jBIQd%x=ahW9((x4b{`{@nXVZ!D+eBDuHR zSMDz#BcCD hom2Z_t$`j-Ud7ivfUMaW8TjZVc1M9IF_#Br*>zqK6hg z*M3DhXR{1T=dALZZ*fHaBb~y8UE=KWAF+floa8nziOhLUG>&1h9PeYWT#(3M8S^7O zjq@8^aFnC%G+s)&@kTOCP2h*Xjh$9bqO vqBjKYtb}95mYdN+r`G}s?GdKhEfSS$9Yv))|9#D?Q zDc>|JueB4JiaJ|cJJncDceVnqvD|e#$F>ngYetQ_q_e2Apj~X~diIx2WldFWMUA?l z!2d?Ms;n|TJ}D+8#%j-sNfBFX5sq6I(atIGYu$_g|Ul~om$%Is&yS~AoHIYn6| zS>lXJqR7it5`R%HR^)Y6sv9$#vYT_n#Tk)fvz3+A6}6S>(&h(Yb*h#RerapcG*!XT z)KuLh8J*ko!MGMx-GiGA&6?Vp>Y7?*XXG!%UFzocrjEva;;tI3Xl+xnQyD*7VJPEA z7fyk8t4)R)^8qxw+YzFj00}Zio6N-O<+8p&=GL5C-Tua-@$v!F|T`aO^9bJ=PwJ zT{)}cuyew)ddu!cm8B8;bJworbL<3$gimDHXMlJk8Fn)ePcfQdAAvPm0E|uH)GQu> zMfEWH1cnV~un+wbgZ;K(&$&OTaRY7?8X0@IAMu~eD0m{ONw|~2N%#QA7BYJ{o #5a{@vUA&Z z%&E=#|7|*;HZQ&ML6zG4VcD~b)Nc_6yPmw*)o`ur#QrWZnZJ)Le-mF;etz_wtNrSd zu3aCWzb}5H3y&LbR(4!})AEMujW>(4uWQVB+|M^N)X$FCHMb-~e0BZNWiyoI3VGrw zGCxy&*x0je@n|zU6C~PoCc`SGGHlS|#hB&Z|5{@k5S75BvVj{nFn(fmo*U1F0ek9? zB(qaF3{u0=ksSLy2oRnQD7%W?YWFnO*H`=Ecj>w+OIhviE5yymYD!qkk=N;^c((9r z#_DtHRb+r(zdCwzhGr?5EhMiW z|jW)QEr9D9R-9kHii7C&-CghhD79ftjaQ`^I>;Vs-RjK&^r56@!; z DL# z1Py^l!0bR|fwKpJvlAJfvGFqEj;E1D=hcVKw_dr5 b&Y)4GCB@mIRVTN?gsT2M6faIE`!ugU^d_48sO9#4?# c_K0b-vv~paP^!T!!1;k7oQ<7sMzJ0A>Mb%f?hG+$gJP7V|`w2eqDi@bjE<{ zjV0kqdj?rROk_OqR2v)8nlsd7718DwR%PtX^Zk6%-n{Ormc71yqBVZ>dbWruDl`}E z;@GU#yiWDac^oUOuis;7RI-NNXtTk d?90YB@lx)MJh*{0j`#Cm(vxhGLlf`N9Xfm37LB3eR zV4BdYIQ#`1P 3-~g(1BCah@3F@ p>gkH?+Kdn zhT=WN)#Bpfin3A-;fT7huqv}D- pVWG@TnnyQ22`XCHT#(a{tzz%P zez<)=@hxZ^o< b9-rrip|ai zv%$W*_LZfekSlOMt6mXHxGLQ$aj_`7CaXGI{p+utzpC&k?4Py=kJOy37S@2}vM4($ zIx5@MbNd47=bsg)-D>{qBPFZY4?|*>vBz3teXt+1X_L=A#>&|_AN(A1kMX^>2{W7( z9tv5}42Q!c;MgGUN7hkqovBTMZTfsGw$8QElm- zvidUH=Aq3#y@oDJpOWpsX+Wa^JOH9y%ZqlpIUOWk0X~fh=>0K+;UZe-unEwKiDuit zPvV_b_V(@DGp{r3+Z?gLj0y@$bF!5ng=$>PUEr{soy7fH9a!gphY7B03&YN0us2xb z(+)(hW!ONVaXjENjlsMb#fwObFtfR `7-lIF4^0LY_>0!dGvdp|}mHi%_ zRn*#9uVE+7`Q$Dh!C4wgi|4%Y)F=Fv fWp_5jQ`0ZxHj6B($9 z0qlU&xx1`PvuV>3;5h3 qNeuzk*=R20IXp+d)(S|G_MNicwpG zMSZNWGt1CqP#FwGSy>vkfb*TnlJnpUDN+EP?qh!??rcYCdqFcRtY~bkY*g*tTU%$* zbTl=$*Y3lXXvSwxZ&6Qro0xZEEmOhZtOiod |?_V!HS9}G|AZ@$5^Ip~_j;dm93 z2oJ$ku!4%`wYx^P2$0xM2f@;20kAeK5M+~ySE6R5eTFcFCLZI)5f9J>qd`jiT)zM# z0fy6z>quQRL+Wsy_X0nmE33jv4qs-lncczDQZWg*qZ7;~IKJ!&tb%u005ysMoZQe= z-B#P|TiYevLzh*TRhOunx4uu3g+me+2Yohecdf=!Se;W+uzs#jc}a0OxW84!drBL` zX7&m!H~R5XPIfTofT>AsY)UE5%49|0jf)PgezN&V6`@GnMlu4dYurW*$yhk Fads!PB{A0Pe+CDK62~}R28rTu+ZWcQczsNT-&q?$kqW& z%?=fq2Q7j@3+rbsOavzI8${rD@zMmybBNOrNM%{s?y`zfU$!N`%iPl9%Ld_7T+m{+ zx7U2m& r>O>WbL)+i85;aLc|%cH zSzJ=6T1FS|Zm`s|fugRg=3S}E$7b|U^TDo8_5Vbyrm3K?AxA|nttY>bbTZ??Bpj@< zJ)~Li8)cxl!BT~DV6x_MvR;EZ8k`0LTzU;Gn2>%JpJaf*d+I@$EDa472Oce0?lglf z3|zfG0_q$%a7g_tedvHGRl}N@eITIt5?7Bqxw3+04PS16aw%UHW@Qx_3`!7*&MY+x z(q>}G5^xGelO-%~IlxM>p93ce4;*McaZ*@>oe3t_~u;0uc;F>HgmzN$f^wN~Ii@dIH?OG`(mQZ}H&Y(RjHu!|O*A?!Eb zoWbmXn%$L_wrf|~{)2cn=HQrpaDUn^=oZvy3+PbZ9uSjV&WbiWG;kXi=HP>Zf}Aqk z$JI9%z^hG!xt`2BVd_z{N?5hx9bjZZgUH#l d4b>T5j zP2P6>{aY7be)rS2f NTcQK!^JUfDNhvg58&x6pP2kt!%H`Y14K4zN^lpoIQ zWJP?7?6RDK0`M}j@?e$CiX47ZqouaKo+W^7wt-WySWe_@BI63mC>q=_5!+b5w?VVF z!_wK@A^zY62;G397cG5d?YpotgUN~F52c<|V?>`kbm$~-=Pu4>$p>xTq{0D34BO4? zy_;ALQ70@P&R*aZ0I{&J;Lx+ti~=xW0$ Yb32qN)u5?GxBZdiy* zjp7#+@K)eT*h~uW5f#MUD#Y$I>nAGYX}X Pk> zJOB&jbquHzRz-XWg2P9{s(~C}mqnEzX%jYF?2FPIjW_LMaV;YgHaEAk{F(>`q5D|K zU`7aj{So9lqY{Lw5F|aLR)h{x=oq4Q|LL;(Pr+`zOFE=!1L$PP5=W2 Nw8^&;vJqP|Bie#m7Sa=D3I?jnI23DS|^5)up{R~2%NL9VBfD?!3k zBs_ Rjd2B@<5y;~(@~lLj&B%*FUV7vmgyddG-hvc6(6AY3STq_|j)t8@ z!(K+ien!JJX!trbyc!Mv4*A3(p99F}BJvGJzW0&xKS;G5sh&lu8%Xs9;uFn-kl!=N zZ#(kajr_Wh-#O&>72^C6w;pj-h&zeYPa<_a(#VizCDQzi{GUYrxyb)E8ZjS@IEqHf z(8we-@_jV&D>U*R8l^|0hojN|L1TuYF{jbkXVJJ>XxwjT{6#cjBbsm)O{CDog=k_P znlu$nI*2B{h$fFklVj23A~g9WG-V5#l7Ob%MpMhswC~Y$cQl=#zzHaDE(+{LfnTDa zO(^JP6wIRF (TPp(DEVlL@;`y0zD~5PYy#* z`k*J3=*hQHm=J}nKw;fz z#ib#=3(`xFJ{!d^Me${5=SZ}3G}<{9?c9ZSUPU_xP{JydI2|Qcqoma+X(vj0A0;n9 z$*-dnIZDYusdBVy7}^zvc2%KWw~=8wGUOrSCS=@?Oo7N$j7(K1Efl5oqjWEnz6_;j zp!BmS{SL~Qk21 z^F?JdP?;5#UqTi4k$E06-$vCHsAejvIfH6dsP;=#`zP8n9o5Z7^>a~u1FHWPH3XoB zT-5LyYV<;lpCHR{)Z~F$7oxT()IJKe|A;y!qK>nu<38$~hW1TD`_7|%zo4#8)V&8C z@I?pqqMk*lrvdexLOuUOy~9!O7}Wa$I=CJk+=mX{L5Ds?hp(U`v(eF==;$Zt*wg6v z2z0y=9sdKJ_y(Q4i24?xzT4=OJ33`Sr^lhQ^U*myI)4jYIEgNfMi;N6=Q#A-Ep$nV zF4d#Uv(Oa}bmeVy^*p*(i~duF{_{3^ejIwf9Q8+{{)6bHHRz>E^s)`TvIo8Djb6=0 zubxJ)wW8}0==$^M^ 5p^wz)qy6aPH_#`$(5KVUrw7qza`c%E{Wl8TzKTAN zMqeyPU)(}pUO`_SLtkG--_1nd+t5$j(9h4IUmMV`pP=8iqu-j)@AJ?fv(O(epnG%B zy{{2`S95 B;S&h|fUK_}n-L8@f$Sd0{sS7?f*!6y5Bn(UFh%`Ixtyc~ zCn(oQik4Dz2PMj;M9)%UFG_rxa%-TNBNTg-l1!!~Hz=uwl5VG@Z&K1BN@k+m=TROb zDUS~* DW6XoSgc`c{Bc2QnN$}5fXI!bvjq~rmVyp~e9QHnuo*g|SpJ2mWO*ej-n zo2lV#l-i9_CsUe*lx8obIYDV&pfn#)nlCB;Aj-d&8c|7&{0}wq4{FpZYV;OrG@-^U zqsIJ3jpeAZPg7&}P-CB^#!=LGh8q7qHE}jINli_Tqo%}CQ*Tn!%BktYsOh_@>EBQR zE2$tc74#MrTtsPIDD7Qp#v*FQ5H<5TDm0s#RZ7izjhelYn)@v^Z-AP=nwlR=&A&p; ze?TozQVSBO1z%7L&D6p-sYQX*qEu?p%hcj6)Zzka$zp2hBx>ntYUu-N+3VEuVbt>7 z)Ds)2C%UO8zoVYoM?H0cdg@*3sb8ru2^CgDtth8fK1Hp(POTb6t*W6`?V(oHQ>z-O zRZY~YW@=R{wW^(3)k&@D8u}B02d>s|>?(jYgR8<%=x+^&P^vv#1a69}!WJCO3 xCa$v!egh+h*OfX%=G zhP);M$SJG(=A`Ra`Gye}{i^Oc*Y&=5ADIe}Q&;KJ=k%@ey-8g9R(+Cwy$>GXDMIoZ z&gGyFfSPh$|4Db>b>ExVrSE$BC#(8=Wf#G{ww;G~lRX$*y>{4~Y2)m{_F!j#2DbVj zcw~k9)3BDF&sYISw1Sfj6*wFiTR1+U*q_yzrY5CyT2@w?hLnoZI 0t#Hda_vdz gnm}5V!3= z-};>Dvw= IsMM7C)GdnW5ob|S}E)P z$MM&D`H=-v81Sm*GyGEsd;`+y9zWqS#nqLTST%%{=-8FTo0AVDcV%ec`xuFE$O4Xg z4L2yKpMw>p%|hGYyBT3u*MTEPlrpOYpWtH$(Lg?b1;4daR9sb242~Xz)uQuLVk4Cg zx7_D!n?x{Fk
>m8c^l`@z@_V)=;FgQJjR(?tGhpT7 z0+BLTtRZ6YiVwb@_lK$)KCb%X`;TsXpurj}hIN-KEOT6XyAJH{?hy}JM1!YXLqdt* zsf}u)97&WTi0=sTj`fqKEmc*~RrJzH-(6U*A-?yC68`Ur1KxZ3XG|-XGHM*bohgej zz%J$K+x#qO*lh~ZJRHC}Mz$5UXNVkva!e}&gi9sc8cQ`cSNqSfWNR;LSHb)Aae_W> zP^vhZ&}`hJwpR_1H0u`OzT)itCRH3`eTEbYOPgEETUAGzdk*3N&e6ipb(i&s;(dFl zU5dBZWn{~MHI%%HuUTikj@Mef@LIc
R%dm|Z+HHm-4IYKa+2s_#k=z!gAbQgzc$iSedVgvUYrAl_aktz%WY5xD^+ ~e6An%{)uL+K!D>o4r+hwv_}U?6VK19or@zz$fc zLeqbD)WG3-K2ZIrF$7W}#{hbLn)$z e-1z{^9R2C_W+IxsDiQnHIAbPsQ>4{j|b&t>NpStTjXCr48NWu ze7yTnY6Tw+7dcZb_=q^76Hf{%#f4j|w0xv-J1`CBX}%qon0ddIrvSuaJ3@vsRWkO?G22|H7Lgx@s5As^rgc-T+s zm!56@Y{yHHFVEil^bRrEK*oYOFp=F#yoP(cGo>B^_EPZMB1_k k e;{BByAhK%%2 zZcA!TZ1PRPD})`DIW1W#Vt^P$LC$VX&g!zKN>?QoCFUpR67>V0`VzijJW-LcJB=DX zYG14@-cwRvsy@B_)b^8Ga+30siW11G6rbFRf-J~iFo+;)p`${L!%~~#TX%LO_{MLI zjfmeSE-$Tswk0(sb*1WO*S@>5e~TDmT)QnQ3j{qxF(v zZ%bu&{$7%TpYpLzPUOf25vjmCBKww`BlW&R(e@wn3oFz2=KH?Bu&%6ecVm^WrK+*E zq0aV%^_GwA3HvQ!d3{4=V|BH;TGd$HxTmiE^; v`DEfnbhc zP(EmS3oQAMict)naLrHTCm8(EdRmcFom-HrA{BIQZgYFJW{;=`cAAcGcoja#^pFAJ zZjsGwk5im0JePY)ZPyMS5%w0P?oU;b?*O2UgbTB(^Yef~X3<&st)11HHrRkT!jbRj z)YLRXk;bllctn^RR~Q|wOm2)gqqc?9*$@w^)^q`f!!W(jOBdMCtm-`2w}Gc;8ZRCJ zA#LD}aG=q`FLU-7npa7}xY#Wj8&w;(be)RT5Eb?l+I7$KtUsS2AA?OgP-JS~r6Lw< zIE}|Znh3Fou!XUP+rxeSfQvPJUuWInq8?uvF5?F8S?|5c;d_Aed!T^--_x4#OorUE z4uo)g!{2S=6?M%OEyYbBjRQWg4_jNJDk*VnLcdSm7nYUs0jqHk6%Qx(24DyrE^29R zY_8Lg->uyt9EfKl@YH0$8(4tJ7%uP7HFeeXHFa2TE%mW&q_N&!T2o$KR#WbaHzg@f z753+yRFh)sRpE)e=!1F{#Fq5YyLRSjNU{B@FfYDveVo#sjyG_xg1^&klPeAsbZ6{U z+t1^H%w~f*qtG{_pdj6(1odKSPVXvEmpARL*voG+)i-H?JqCnbdz-rtD6xFlD44fp zcr>Iuaer{INu=<>z7Tz1_X*jgvd< CK!jVx0^8k*;|ucv7E(EMR$Do`~TPLX%v%N(DsCImJZiPz4< z8&nWMd=8uOgD*6&iC ;?ISO;30QAy$%+WZlPPqslIX%$q8LwVFyZpI+6P3)@#) z=(Z+HYn`U!T=SWpvtm5?dmQ>5UZ#QpJU0jZpy4N-=a@4e`34OETqzfTWw _J4_y~a8yA^GvNjY=3D?{0=e|ew%(|-Cbb`tIr0nrTz?mx zJ I~F))dVf;4Zm854V7sDeJ ~paLe}$Jx{h z>omJ3{>8ol4o~uneHx* MI#LWIls^g+k=C}y6KYxa)BO1K& zMAuc}xu3i4V@fG|SYWnU1mMfs{{-VMhmute`uLYsKTSEU-cwRsU82$_r^IRIV{tc; z!XfUSuZ%mA(${FI-qWbQbLRTpU*MhiwvP|_7yrLHfh~O;L1cI>S@Q`#z@@Zj9w^Wp z%IhuY$rAs(_!T08tv(`KOGM)rtBqNPg48^*sURmaMH$1LYP8hUHmWf+g!VW>$@ldd z3B$)oAC^LA$!$43d79Re_VTup!?}mE4`zyCNhQ5Y){z}bvST7#;a0sXGbKAEH@76G zJg-D-%*)M5R?cE9&c6AMphNr`Z}=*C*Y>YtI9MqKZs+JhxhsFM0Wuu-4=oS96;^L> zhP_4L7dI*Z5O!j$@?p-xkepZ)L%{YnNG&OH_Q88G3IfXt+FJtQYnvQ4+xZ(Gh|=2u zl=k-H(+y~EJI(|*haR%v66R4KHVf#oM`dt`ZX7#);~4fj_QUHx96Jujof~&<@PEf{ z{BQ?uZw$XN%$nd#bxMc)2uB*&PDlgm?lSLJ-KbU+a)m|a!d+am#k^N_CzspX #yigCz&>*~?4{%~2Oc$Gpe59J JG*>$ zwx+t4c%LdET;xjM9Fs9)cV 08b6$7vbOx zJOsW}*x-i*^T7=q%x6GZ!ro DVu-5E8Rj3*1v=oTGauNn%5sL1&+ z(a7N7ks5pOR#;0A2#E|rNCeh`{Hj8RCo+TKwtJ8-?aZ1ckVs7+k@|5$HHLbqfn{j8 zHCO?p+X$Y^n})eBELGVp^wPP{JZaEu1n&l(z`il$+2QewI+3B*a2kjmT;PTv-?ebE zW2rgZ7C)#{co)7$##|@Uo+p#VRhgMpnX2*QYpcg=?9UI4QrvjGcJB>MO>0+uD})W) zGO$=xZK<|clwIb1Rr}OWH ByNs-+iNzj uYTuV_5J(Gro$J^M^%tmr7R&6zt@~t-}ux@C8S`DpH4EjY-!mSq0Y1AE4;g_ z4w(6)zq-F)GRC|A*OFg P=8ScTlov0fE8B`8Ml^1#l=?F$7$?m9!eDrco}}O z9WVd->$0y^_!52-cKYt(STYxI@bm>(Vcf)iZ{g)i+(K$FBI#=H?(N&Ex2s4IDbC(a ziZy#?3cdSJ?8f6t+mgjSTPq)~NmD*iH%h-oy>i!6Rbe@mQ+yys21z8tiI;2L*I%{$ zPvu?s_B&No?`W!Cel_#u{oS?uRgEX=Pt=@P*|f4@RclqJ&+cch;w?>A`f<}u+ i!qrJ~XiRou0-c4M+DJ6_|Q8YpY&dl(pdZN%|j()A^t;QFVd<~-7*(CX4 zeE#xMePOIAd3EBllCTVMc8HK)K(baJyw}zYyS#q%JD`SV4_ZE@FSZ`;OVxOPLA-EC zYF~so6@rakBt)oBZB4nT`t+e?HXV_dqCcd8MA(o+`itg65KaVGVc(&=!x1V!TalmZ zpSYY8zqZGWw_Z6R?mqIM QxssN2=8sj2M2bC|Pz_-RI{OEp=dA%H_KUQ)dnH!-k+ zVXd9GUhtWpumFaAt*#nxyL?RC-h&JFKl^ytw(L?;5S3e(o2LwFuqPST!GIx^IHU^C zP;U@b;mOWXeOtlmjV#6Ps-`Q|6^)v`{Ccpsy0RCBKXVF(&(BKFORM&^kWSKFMmk}9 zKi6hI;;Y!!?w_T3XrVjXA8gYTU&5xRs&fkpvon+#d$ZdL)ai%7PiFg6mvSC-Y6dNI zR*wIrKJ1Ix?nCOf=Di(V%C5qW+-7xmDuGJ9N|ptd6OZFVH=z)>nZjpJ!rdU;MMIAA zqi2w@Tn#x8k6<6lLzzL?UxziRMPYKn3_+bu&VyJju8`#XxI5?F Q*+U&{Uc z;|MkgU iB7eZ~}s4F-0r*X|CTofA~n0e9)xW$l+t0nw%%+g=J+G#U;v; z+T!{$HJ|FhC-he|s^&rfjf8JxwleshEnMMDT!mZ+VSQOKESMlhV;@Lh9BGY?T1Q%= zpRn6*+Q{_s%NT_-|BK(8FM%EPviteVl!)5Qjp3&rtX{(2HXsvNo2(vqaW$_Hn|i zys?CkULWtB+vE@&Sq_m92*=rjA&zaaTSzbtCIhgbCE dK;c8DT5c=R~Z2OSIWpTqpYOL;(l`z B(e?I286f^4=BL6 zlLDMC>@n_ajaS9*%rlxaWEKhK!|!+&9ALjesql1%0D}#zSrRb8FuaocFSDv)#ldj3 zl^SXjMs!_Dy`s8u>CnY)jTPD2ga==2xN%*{#{i(wj|%uM=HUQDr@ )6YHTVmtkrZs(Z9SUL)@IvXf!Cj7x2E_ zuSs_{eyZO1DcOm8X5j yo3{ChUe7JC ahEVLn~U^5^K zpTa8`2)#xCyZ#6(Pq|=Fg6}ET{W$3*8q5`}y~t!C{>b{P!ctt7TU< @BEM7u4lf78Qks`2aJPlqf;9O=aLqhPJlK z-EAP`&9xTG)vG@6p;A-j-pZy@OYt6YM{IOsG_0goFp~y@6+^*8!9oIzD6$3Hf`xcG zo~}53+WdO#Y2Fv`i9Nm292+}rnh($5-+d?K7d>(~;%qO#D?r5OqN1hPIgz~G#l< +5Q2>NIB#JlAy%HacqewzMldb6d03kj({Ilai67aRq~m zY`Ya$!a=%;k2CSQq`0!8L}Mz-G3`>4aBy6P{AZBsq?>F7w<4{fNK;bZRNkc8v!}Xx zkEW&G(puS?h_id}#LvDL|L T+)_~9Md=Us+ z$%lLQ!)yHh2%j~(cUSM`gBqo!ny@fl#CfbX{1dBHwY62Xsz&~^ovT-!nDza?P>XYH zR}{yOo3AAthYb!FpM>L^%n1oYsv(sRX~*r(TVKd}bV*1sZ#tgfYh7etbUfjj`S@{P z+l#gr!HU7+!}2ZYN?2}#XXFd%uh`=Fo`g?l&V!lr$zZUscmW(eyZj_K-T8?@F;~k$ z^0EdpZuqbPWbl;24-HWRzQQR@g26aGkE`9WG>3#!KI97S@%uQIvu%b9)ON>eoH4=Tks2$NBOJ+Cpc7HV+8t-(aF37_PV8R~+jwA5QG>8+^x>?vvPI$kp>% z;39o*?y(LvL}>X *I MW-*hP3mJLloy8#@>Sr6L}``F=!$0vr$ z8pZ|?LeN 0~Oh&*^qS?5CHk`*`<)i*#K#r@M*}C1P-XnDTO4iY`!w!8`%3Q z?dVW;G_*H2tFZ$H159&iJ73uWmEU{=8Co5WlUw;tcrZTS6*AB|_=MMQ0^mD}hU{!* zb}66j$B)8;@s*H`5Bb6AF#S86?E*f`+0WU}Ii{7Z%5h8qHenvZcxC)}_566>euKg4 zrIU2)Wa}g-e#`e>54}UWcF1nv9l%`hqi+<$;fu#`dKJU(GGU7i)*q8ct)0I^y)`v? zvq@JV#DvOVFa`Xvga4}N0vQc8p-@BP?{E!#!$fb!3E=0? zB?(u>cocLvN< 1-nOw+&a2 z7h!o+3E#3%V@|xJRolvKWx^Ljj*lgj@?j2nUa{`Woxfp$62AbS-c;g#Eckur+Aq~s zy?u+qIv2hG`-%S+4895b$v)RU555WeiT@_-C+j@5>?Q;c1+e|~_V(8ntA`*8c?n+< z_Vn;s*^uyT>`^bc1#U~iBe%t<8PW`?g(kd&+j+pGwrPg^aV7J?hr%959yj0OfSO$T ze;9icz^1Bo4HTsrPCSR>F%70kvv(Ci1Vxbof-)%rQf9(D&y> mR=F`^g(3 zpH}?-;q&kE12ATGV9e}5NW3kR{oKp@_@oca4@!ZwPB3*q8*a(Pz_yUd9dvUk}!a73512K+A2yZcq z>)w96%;L&~#f3xX1C2!WH4 _Keb}4!3{OWfZ^YPvDhu?=QFClNReX@1x z`Kwn&b@in!f8GgfUhHHtez<4U>0s312IAE3qB_thONWZ;ba%}@T|7SpCmT4AzN;tF zVF!KZ5yq@QOi|nVhf4aPhlAkZ5?rrg7(~jG-ta#;AJKW9U-yU}p~WDugx{kPXJ|0z z*14m~5~T*GGq+S-?J2J+yF>^9Aju_I8!9lf3$g@Dk`>)P>0rrl1S6W0c01FX*yl zzFJ>*u4aHYRP-ebNTDki%X_-B0xSG~@d$w}rjK9%N>4?QTAY$zhSc!_No84CfGbi_ znx4X&$OuJ-IbDym(QI*Yy1SH5_1z~zlFnJg*&QyMOHE?ME?0rk!8`M9xw)zVq>z9O zABre*=xt^ 26!t;IlJv6nwB5w=QLEE+?df2(An#J|#aOTnmc%QbEFDVc8O z(T)}ghTB*OZXsPP2H$A3$m_1JY*TkI@UZY#>YA_d&k~adfB1nsNFIFr2m0WQ**lhR z1lJYp&0sQ4bZq17<{9cf!VH ~E0(X3W@TpNrXU8rYj35q)Z^tzF-arIWGMMf&E__{$ZyHsd)Ljg4KoRLdbw zuk_EjPS;JB(tS@+2Th_^R9kIZ9g$p7Qsd(6`E>dCL8?S&o-37G)l{|p;=j(5wa3*5 zZ_i3Q%5VSj`?T9?@+294@Li(j5$@&4O0v3>bV|LQ&P$!D01AkP1B4Aw!(HkdER$`R zaeKNI >cqkE%s|8{c;uklIZD6WEYil=E5SgDC)x2uiqoZ zg0@YN1Cb+W$z#X{pj#t?KZ)+5v*-tE`T-i7MX-P;i)GY8R)Hb9Pj)@=qYda=&pb~C z(ucmKk4t+u!X;t63@U!=DUA7}G``Q_$Tj33vBi-$(bgAa9VKc?=ofSyM!#8nR$`70 zjFMs`xH$SIc7h)WQ4>PGAZy8KGK=@r6xRW?(063QNwP{or(VbSsvd+;6OLxkWh9fN zV>!|(+_|^&5#aBR(tty 9wjxTMlMQIWjPEbd0bY_xAQBJeRGsJ_+y`wEyCZ( zCgJbwJp`#MyH&N%zfjR!&}?kxXsh2WB2(T5jdVPjqY|_
z%%VX`BAW2+kPp!DpP@!-0mlAN+!U%*V!*G%Y?DBi@&^tdIJW16)LZMUX;k6q#56sc zby1djOUby~Lrcs4twHCc3nV5%rpPp>8}9bc5-*=BtP(kFB~Cl*pN_lzGmrI8qb(;- z6(#IYv?r`>T&Z5UcIWy8zSjlgF=1ojin@&<8|z++Kdjy$6CZx9{y-gHccAcS!T~A5 zfEfH!G5D{=|B8rC8XhrxI1Q)a%HF5QD1$94KPNYYWeak13-pEAAy$JU+hr8&v_(lh zcrES_6`Bex1!}}1_~FBlc+JLIg+L<|8(|8~S0eXfcjOf3mzYBeEy$50+Q8qw4R!^x zpCOCMV&cK8#cyXDxJ)5t8)N@Eoz@~d;8C|ritXM4PY4b7&B6mR`u6Qk5+2bRa?ke* zZs}}+3mA^20F89EAd{V}QIKd&=RguA>jm3afw8I`h%zkbVp sIDB62jOeZQ;|nM06Be7fG>8|`Z=N`}Ux(OU)G|9j>CLrG+% z%r~Ovpn^O)gg*QUOSI}hn$wBN@J^N%JIf#Zrv2;FpGtdrdJigG*nxx&k3jJ3Z}a&L zPpu!J9WA9^5V9inlH+QOw-ARo2D3A$Z_SSosH~xG+MkbK5H~+&p_KMpIO`c2^7~Fc zg6>ESag+Xh{h5ZdyG~0 $!G+P%wD^eM!HA@=5`4d`JY5o`j{|Zm`|>eW5Sc% z9hc9wMnmSp2L*n^4Et6H{`2<%HD82^XTh#0ChHWS0Jg_9z{yGJ^N&)L;L)vbh=Xf0 z?yif(ydt >R+
GtU4}j=u|TvljkknX$~^2KY?~7L{hD6ep{bQ*tuk z@^txwMFzbwJ)Mk$pWqJ{!xWD*ZrsN){ k>{r8Hh=iE? @CFQ)jH>dUlZkqL!kD=9a$QGT>r3i9pw^l-;E!G4JxQ22sOM$r6}VON1MW z!%N*;xI10qQB<~Q`h1>#?hg~CrKHElt5%=TzsfWqJ|nVI@1Et!=R%mMyt1smUPW&s zi4Y<4w^zz?3K0yoJIELK@%9b!^cC_CwXaKzyfki9f1j4^5ttKru>U29amPhu|4ZP4 zrJ`5Jz_OC``%;GWf+eSLx}dDzi)9cwJbwViSY``Ed_VJ!5u|nDGgfzr*~$tc0irOl z3<*~DJEEV|g~SrRz}arc^V9RJV#nGoo7cxk3$rcR*(wMXY{dgIkUjud@wpM4sl38m zq5l4hw$tzPPg^Hmi2X!r@_J2Pbyca&Uda!ynt1Cc6`)8Kt|h(Iu-7bIus^c1OSRv< zuW}Ff3v8#SpCd12OY_Uj73C@-{pK4YJ^yCh`u*Hd*Wt?c`n2WS)~(t3y5pdGe}xn$ zv^WgVo_~;>Pi4ZcBQi3NC^dayL)=7W{-FmC4QOW&t0qS_2l+f59k>iv$bUsaf;T|T z2SKANifdTn8;B(Wf _qLxh+? z1z~V&;go9o`?_%TAZ!q`!|B<{d3HK?#Hg6g!RSvJTXk1+ZdN0g)tKA7OGS!gobRh% ztzs8#A>~krLd3f$N3DDMY2D6m#@8hBnB-uVh(78D|BfhCB!&(r!)P?UarYICZ^KCp zQIg<}kGeVEsNPXPLZ0sIJRLw&@-&vj-o7nLthw=2qq^>^uj+V=0}Olia1S1VXbr@V zT#A<$%%A%5{4-a`7) ! zdZvobmSmJ
@wRWU>s))>zL^M?F_v)GCVJCS`} zpVgpm%n7LjUOvNGsxMWOIpR`JNh!ji$Lw#~Pc#)blr(sWWTY}bu;DHssRuMw^LD${ z=~NXLI51>J?T_BK }-B8>}o~~DVttDk%6%xfiDyAZ0R2VdsVQ_($$0P76eV3&8 zL&ypl*TzJx0C|W-vgw;&5Q-Sfg6+Y9*LnCB8}aEC5 NYb#3Gtsa%NOIDO>Col@Gw9{S#3JoBVoEbnf<5pCGTufo%( zTt^;N%YXh_{HmCY&@>$K9zU+CE2z(_ z;dK37is^Ia&fsXbnEvJV7%D@4P&b)sxQI)w }raWd_f%rkVuQ< z^jEJ|o#OX{AurxbWNJ9xxP1LTScdbJ|D0?Bx`2C4SGQo5O8!%PUVK&}*A`W^VWlc6 zV`XAIC;#~kbMwf}0JNDE^6s;#XQGaBj#^uNok}ho&paM?h_jX!mX)eH6FWB^=j;+P zw-CVbesrdq28rni`j1V^=wrMgW_ _PM$bOU)yEvS z)bEnMb&|XQtMmy3_sN2Jv<=hb!2cPvPo9?rOje}CZY%a}G{zxq2hcy0m(ntMDZtJT zXTICKR0C_EFR6BS3EvYgw7XdX)&Lh(jz1&*4E^zSfWkMEr1j2}l=XxuURkziJZCXW zCM-Mk_PZxfUcY{9$@D^AxOpc`AxI?OY`gee#HO|DQ@Ol!b7GRpucT=fiOu(yf=d<^ zNgHAkAKm@tW%3~NQpH1QB=3JzLJU2rVH!Z<5K8PX=n%EU7|#jX9U2VtK>zu|X*Z%P z^g;3rRdRw$QmA2;1Ri2BgHbbUg7?E>RniCH5Ir{@-E^q O zl{+CMiG(R2nXf^fwJ(#?O@_doXPFKyr1g5WvykvaHj;(r@2VF~rBi#udc*Fi7x}_` zVFb&Ffl(5I5xoQg(~Ybr9Dw0yO1#=TeaGelcEDEJp{)h71>!!bG6w%=zm&;fA3<@D zWt4<21~(?b!MB9fzDE>vSv!)&q5oFhCODaqEp)(ZPjfvNNweY!WS~Rovi00+U$p=t zHWC1_;o`us!@*Br;(`9IBN})N0ggbMhAx15(q?igojbU6Uvy=Lv|~l-vItc~6dbPM z5yLr%iut-GLLu<>@Xq1GqdGe~OOJOP >_WNES zdma`zGD}fZWOC&>a*;;QHyZP;*-VANWiGNy _wJhqf {FxqpGjmj@5%(ds) zjZRZhkrbY9kizOf=H8ZL%`Pw@>FUUJnWXZyAQaiG4(Uas!kJ^wHgnmz#=Kmov80ff zhq;_~2Y gQ0;3+RPv!W;;!FNk-YavD_KpR;?5nq+VXCb zwC&R`_I|CF7hv`ftsoB^IN&; +Z8}9>`TT-vPjpLKBe(7J_OnrE4Alw85}i`S3@y3pEib2~Z{wFIMlFn6 zEsdI&Ja>j_#Qw2+#&ETBn&!1>yXi#4k=BukkG>gsF7`r#bo;G;Zu(mN&iSgAbNu0k z(=9c}q-Cw;@4c#G!3q)_7$kbSfwt}I$boU9OAIeR98V7N?6N)x7~k6kYy#`n0HyV* zbz;*bcrimH{^x~Z&)#}=jQ@H6Fi~jG^&Xc52%Q933j@2~R_x4LYKZ4viKY*&n5LdF zr>Sl>Pj`Gwvq|>zWQSDuR_uj$RF{vEhu+@JmDHc`G^*_mi^ YN%{TsXlOtJbXx9SC^o#;+xaRkSUH7snux7Ghzhs zRfcK84uy(`!k5+d_!aZxW~I!`edpkbi}hD$Z+CGzU-cD6&+#V?~;Fu8Ibo8TzmJ++yppvbFV6v&Cdq(UGJ-9Y}^obGflG`7!Fm zBinZ--OxzzFhafi#@ l`WFRao$7pF?{#vgEU Tz` ;4&W{SQi2PSCL90N8qRs@r4WVBwh72jjWKGa1YdJX?c1*pOKZD z3VfDYvaKPu6%?GaF~mrXyXaZzHd|9%tD30Al@$m~^B%9G!dxj8TqZ>d`59r-n?S@B z(n4aTH_7Al=Sr#BY%#M|v$FV1sEVQ0L6U8aJK9W~@KL0&!M~x^|B*D(+Pt$3vTguQ zIe|IwvQ{gkY{>KFlJMKGUV@l6*T7IPivyq*{2N5W0i35`ND>B|hs%r$( b;AmcNYL z%QFAZqMW!KU95_B0o8+6#euAOFuR|@3L%)KWx@fn=%PA*u-dQNjM#@6Ub*;Xn(!%| zc9u^2Y7B8(-y#DZ`DEmc_lRV|$L%0zK5&ry5Ces$H8_BwQba7!QmgFBf-_T*H1PkZ zprvFbbr2DCHcLsJggBdt1Myrel@6$Bfywak$GCs(81*mrJcd&sYh~|gxZV+@T|uUk z)&C+*L{Nc?FaXMmWq1t$x?4eof6*o~ov!A{%z<<$0>P8P^hr`78#@Yk#>xW__my#M z`VUA;^8cf>WI!?oY#I5F5Yv)SNeVNv+QCpkJeCFDID!a{vnj73B)1`{I?h4o9aVOa zInIXMrsT?yl&V-~TrQonSh<4C%L}X!veUUUHzgTVFkXpZI^VZSNL*f0QeNB&I!{UG zbi_Ghu}VmCN-j3 $h;NG#dJ zV3m-{ Pnw*tY2>EkLWCl1 By$$y@UdbDJGI<~#bl0>%sblPsIGQ4gg64HCS?F6W={FSNpKDGXyoKL?$3AV zeOfpab7k`HfR^b}Oq(}v8b|*r?(^fA7x&kRD`^l?OLP+Y=}Udi+pkx?=<4L#kL*9v z+TpG7)Ou>Ab?y^H+N{2HHEK8>lHoIFse~?gZ+@A$tljzgAr+F)iht2joV=T=*3wBM z=JU}jBiC 8r0;5Ys9>vj^PhObt$&wJ4Jnv_g{-XlO& ze<5KA5#W@L6FHpG=|r)i@7&lq%5AvY7+K_Q`VyUpa66js|Kc$s $DRZHm`HTCS&r_DQe`P~cW-{FNWvD|iX_$3mox_!0fR?~4R z(s?+K&Z8?$XkEO2Rr@OGi=9_S{;F<)nND1t@39NW1fk!7KeA%_r>!ykmiEZ@=r^Qa z&tFR>QI(njs>A7oHO{qV>v>!nJQFfl9~@{XQp(N==Vx2z-B7bxD&K2DHZemLQ4?`+ z87Fs}jUu|xOjLsyVdehrmVH0*a$D_%@Aq9)!wP4C9r7sVTVTI^c7?nP_bV7ls+Uwz zy>LB$9eL%&P7VjWe{pM~H^GzOk?P!3dZJl%tL5r#PJWmKUmAW9QBTP`=P$g=$v?b& zZr(JW{!>y@|3+zx`i+ChkuAtJ;ex5 n*8m^Br1ZdW`|TY?GHP4 zzKJ`q3 Z#@KSK$ zY5(OfQeghNl9LN_js*9(g3r`2)5jc+(qo;3+;qcH$u8fwB zm`^9Ir79I2^%>CgFTD1Kqx=B7CZLny`e39A!&5FLVeIZqkQ^vFQrcF9i()D*GncA6 zJ8KRe=k3U(Ej0f^RAd%&WQux95rv+Nsd=WxZx$n0G#}7z0BL~80mLy*^UO0MGDgGD zH_v?eu?&z;d?FIrUC_>5BoEt4Ff%RlC9*yYk_+t?f!ZcQ*svLmP_U@cFwtuI5JLeN z(R?yr &LfN}lhGYrjxgCIqN874>(Xe9tjx(8J1+gh?-CYU9Z9RV8e zg9il~$8q9lB{Ig}zSoxBoX;AQqcL_s?A%gh?A!u-2ArtxL^jI&W(g=q^ZQOTa2WQ& ze3-UJ$;++RZ)VrN(cI`dT3viHv6@I-@~a&%ivD*&VbbKURJfhU0_3|3T=|*V(!6}g zJepi4Z+?DuV|rvts#RKSj!Q38WgD|BxzI9nC#M(XW$K)U40l#Ss=mySZ`T|0il8Bx zn{O@j7;I*#(UhNWRNFK48QD3899>R c7wt|ZMoZ>9Vv>E^|c4U_9NKJQ23ybniHohP$*O`|uh2v<* zGkW7P;tOM?N#3nhdd_9sZE_WrI!tc6$L@7F$_ih%Zrf^1DL|golqEGm;nl7#ve|7a zhoQQ_;c;e_W?M6)8CGw4vO1%DhtbNrlPYtnOQbmk33?M}FeTR%xy|_n1qCL9C)JVd zG1+3<9oCGz9VMC0M0dfio%tyR#@xhWX@)(kB*U5J+TqHp&MUW8WtT}y@=~fZ)gIk$ zTLG{4ZrEE~ZM7v7=NA?k3ibAEJ5r?%gFB@x!IY4hUsRP}oa;@t7)zyAkcIJA5m#E| z$#7e045iuLbVsJkXxnncYfCa3-mrP37L%n=RpiNitGrNeE@-mZtwm+&g(gIWiy<>u ztas)*3alF%&E_Jf*_mgTCa0I|GS~nawdEI`{p!Ytw>zYTj$%u(y3$mV?#;H^^NS1Y zwqlRfX>}EPt;P9yuH53>Vlx!(%_hAoG0TuI&B@O-<`fu_&&@BC%D?Z}UB0KZ)s5h6 zt}8dsZqZrurSA0lk}~t*yzIX|<}_H+fe3bGmN;D2qDs31tmrIf0W!oJZB=#7;)eWU zqg4LS$?iOlKHcCnw`LnNr4DGS7OP!mYhsbHG_L-5ktxO528@Zbz#X57onD*lHWVf# zLHIkZDl=V*hdbMl4-j%jMsi`Q)oD)9nerLC*OX&2SoKCjh26D1*IZ+(uvF)=txJtH zc)W@nRRs={C$BKmrY}nO##I#CthFUM4rz`vyRx{X*j;6_mr9H4q6#WhC0?7^! >-w5Z5xD^wL`IMOPtkhe}Y6>xdk>DFvxfjK_WR*+eiXi3Gx z1%6g;PJRx~svzH(Vl akXoc|2~9+g4Xl zYOJ?3 #BH@Zku`7v^ o;%RRMEMzaE4fME}kA*Bso92!SAVl&q zd@S}J%+g0BxVKADhb%zQ-GAVGg^JQbgFd>;J%`T*(?%v3{05~zRdQEm8O#J ;ZqNPU1p-$$}0Q)KKH5?BlCC2H(4{G()B1Qw!=0GInIji8l) zE#B8AGFx}mctDI3zJMRG Xof>cSLTdY2($>&MQR zwu=B+8pRD8X#hAWanKm5R sNXo`3k}D3Ay= zSon1rLgnRfp|qjmwRggycK<9{$dr%wG57Q}nJ6_SE}N-1yD9;aSx+{IQYsA)LfP%9 ztEuL;wKi`&p`N9Yhn?8iysee1sjhSHQa3ahQUHJ3NH; -vmzc!0 zZrgESshT-yOAqX5-Nq*-=?pP bOc_qA_w zY&FrTMN@W9Qok@IYN3`N^_l4On=MzbtFFa#Ejq;oj#F-bbJ2^tC#omC7_(>*Pamg4 zM2i-0pE5}`ao0<4YPrDSEjsl<^fmSM4_Z#Y$$vIVv``y0`303cY|^e5Pc7o0n!iD$ zJ=GQcAp(Ebx^|s9#goT@T|L#Y=Ywl7D~F-hi=LrdW>Pug4igV@jR(u#IDyg%5%YB$ z*KlO+MDUj%rrQFg719f#UGU`8M-Z|B*Y1zf3SmAFHbX_^Glk#cYZD2BIwgl?AW2#^ zo&G^$zooZLL?4*kF^oK-E|iJMBOTw72PX5q)xJnY^&Zhai7*I!fI*VYlIrcG!9PXR zyHY%$rjU$6sC86fXJ^30*5ZjQksX$ijtGTNBs^N_b=8_`LJDd#tCCA-aHrCqZLk{D zdVPU0o9~_Gf0*9?cD@y_!VuS |K{_Yaw~P&%eJL$Ptr)TFs;`7^85d+K-cb54UG+V5TI zfeY_dUr--u&x&v4FD%?Ng!Y>*CDR0-0wi*)wa{h@DRw%Fi;Ei@mDg8h LX$1KSc#aAh=>>o3C=-8ug>2k61vfC{nyFIcBo+3 zib5df%Lx_!Cj%5ljJsqsQZTr6t)b!?pcee$;*HBvbsKqdLn7~5tikuiQ?_s3%=@3j zHk&tZpR!oh8zA_~TZMMo-bdC&xChZWe>;s-$lK{jdQw8a@l8?$J_$L<^Y(4X;1dk? z0;w}09E6f^iM-Vpe5YH%xWmA~$S?K%AVEC12&1 ?_P>td6W}= z_Wu%S)upaEeY&sJpAh_d|HUWT2de?#cpeD%twUaW;>t_Bud=s7H2bYvn?QYqb}nAe z0DsNqAU63x@+Nqo08b5!ek2od4Hj#9A!F|krvbqWTnnZM*Tn#?lIjC#8KpdFqirH@ zX2tqs)tz98-_?V#9o6 }Df&6eXa%@1J>6jlI)T8V z1+5VZ0eAmF${pb`7T65BD_Ff9cOO_)a#RK#`=Aan>NEjmh=BZbc(woSC!e-j^XF9& zZTqzf XRrQ70Vx-OXU*VM EKS@&XRA_dcyZ*8I8 8a&SOBSkDrLEn*fs5X-K4xjEG-cV!>~-putDR*V`1Pe*t77V7>SOoB zAJA8(p4fbAut~W_l9a#Ix{NCtZd#I~%9QAfa*Oi0lAOBi19}p+L7Di$+om_wpS e*N2Ya=-e!TkhH}zN_~9 z?qmCo?%&_uc&K7`X?w{DynZs0;Zk`=hA<$f(xNgqn5w<1Du>tQ;_UV&`y0+(h0XRR zrxcbn3Y)l075NQZigi}*%)IHg5W22JG*)aHU0U#ibys0yR;@0iG~F3(TU#_wDX%G` ziC!b-%0ao4GM00Ot*IM#ZpcbXNZhX59=|PZt6rKsFCu%odj7n!mbv`;+NeFc1KUrf zy_0_ 0{Rjd zt50kCBSR7%gRvyYiJ2R5pT1gYT>u%QWDI0&`f8 wlQ!8M=u_L?gbJ>x}2pY)@ z PDy@|eO@dn8q447=(bTQQ%7pA7e icHzBJXEXH3e^$cfC)G$w%*=ahyWLT#M@XblkiGNB*XF^f t zJU8L+iJjbP`f_%pMU^&h+q{@n(!}@P*I!eAp#i-TJcpqma=Za;G1sRu5+TleGyAvN zuh@ZpLb|#5W0v=Eph1`ZdE;{HlG)SN4AKqf#^XS*tI1>E0dKqmb;D4p1$v^Xa5pAV z?TqSGyK$gt)?8yUJJ5nmV{+g?AHjh_oKHozlbJwy_mbCP&Tqyco&c0Ko)(Ooc<4kd zw}!sN4m5GxqVd~SN_~+48Cu9VGMMBDK>#(~B;)$`JmG^t{SMg7&(NJX)k&;U$RzOI zB7gqZrrGasgNBHjW0vii0xkD2NV%)1>NmKhO7i3xa^LA BkZ=2mQe9DvKcYNN?(7)QRBYceO&Jhi di#x|-ThHfIA+rJy%#AT~1S+c`^Ml&>HQMdYstXODZI0wmDke}V#6=zqfh1i`!T z1e6Ecpu9Pj_YuH&=q9oWS7j4koAGHAP##z!X1L<*vMXl-&$vOx$Unh~+xXUQlBC6@ z>*EtAeyqfu)QE`KuO-0&@Cm%+nK-dbw6__jm`P{SnK;c%w7D6lnMq~>s)IQIA}7F| ziXn6l Rn;x`IA?@%?a!(@qwLEgt*CGe1(%l@&aS)%7DI@dqg_>FP`5{- +za`R#GQ_mm;{p3zO`9xd;M6E*J@tyhQ z52y%Ow<%MGJ$YpkoL{R&OeF7L`_{~d2HAR}732&ZLms8k9Gx~{==8O`6#;{rVoTA8 z{5u0V3%MyAm+ZFfDz4?u)t|cXrJ6i&6!3oD-$ZUo1|XvDdRVRpX;6S*9Jqzg(`9tw z3xF)4HrYU;urul;r!-<9Js2uJ4Ya{+CT|2$nE%`*%Wc}878}b`7sd- 6h{3|3QyiGvb1Q+iY<|RO0ofz`BlvcyQ4W* zHUt9HnOVkc6npxS{wT^uv^i6Sl6$UfZg+G-^D6aD9R!K^Es-m>EV^5kMu0m3*zKuT zkG|T*S5}oEkaA>4ZS-y~+vPEO)n1R?1 hD4pP;z3x@}zI_}IaZ&VH Pq<455krK@dk zO )yjzFm+}l VO{~ogG289t zB6VeXNm(&3> u3TI+sQGz-lF+Vqv z_kSpvF=x(<8FMbYGYJv}lt +(e@*p9g;BsGP#g*7#`=9<4g#?b8;* kQVpO~9<%J^)2L)nL zO{1n44qfMd_!{u%BETW<4Oftp5(x916p=`5)#u!?{-<|Cx~n(@emOLUyWru${l2S} z6%bqq2C7F6uA_W>fHNic%D?vM7?P$xefZdj8L~22$8WLI%4AHXI(%Tn&Kc{LbIAz_ zh>A@eGxu==6-jBb*g!;cf0%kJsV<==xmLRF*v@x4Rp2zPL3yoLIJp;i>oxSRbR}XU z0}=l70Ga8)%Y#R_;jy-7FHhp+UtgZ|Wms{TG$u7AYMUzFr7KV6(o(Z^@v3bV(akAb zPKGfxO(plvJhA;OH^1@hridI17M 5dYGIA9Qu{nVTQe@}n dpFd3Y#_Lxq4oqOc!7F6%toL(>?RzB z@FhfQw0P23*;o>(k$3gD<|xRD4dfk~eUjTXJ$J&3s_Uzdjd?~z1_m&@0oh}GW|em& z2(BeGaq{n889QiGm^y6G{ }o)<=sxq;1GhlTfK5@ z$IDk^IX_RP!Pb?K=q3n?g_A`p7%Y=Cc}BC@$Q?*n*R(>dlbu;`rLNZLuHyYeu(mKn zqOYpWtyA{_fL5%F-I&13zb-UF%@q}f0;uFguwlbVbSy`e5|kufMJHDW>M3#pFOhd& zZR&XMpGZf5lY?8R0T>nJ99SzDk%L)=ptpNq1et*norroQX=pH#05D$q^|&Cz*wYRc zQUtzrg4I;>Coqxk48TPH8%pSRkkJ2JfA$_o=nr265Fzpe(j@^P974V$Q8W~p8u726 zj(&s(e61IjAPhREhDq3U!Ueorff>@I`fypVjudJzn%HD5of1}0m&s4EBnbS-zA*}8 z&~*(eZ)3Rn)5qRm$@r5mUW?^=t5Md#S1q~Lbh7J)ifKfGq~;QnCp3I-I}_+*s(O9J zu-akG{~?Ba#|3m1gF5#iyNAhqI;vCjYQZjMOQ
upC{nBZMefk2*il1zTTzJ)Ln7}PugTMsZuZv&15v>_v& z0(-}Zr^CU8-7k|5!&4QA=*jE!f~y7B9PC!Hd{IAyRZ%*Ew!>|OVc)Osu;4)S^)Ga_ zWG1@P*Xc0d$jjg?&qmfta4B|63LSQox@69>Qoh|@ {4ksP82oTMunoW=y5yHIx?*4QcI-`l3Hy_`!{pMj)E~N?{L-D^fl=sqEn-|L z*7L&A^TJur>ynV}K+lW9x0ZW+LjA)mA=0I=SXyX%Cr7(msIF5K0jUa%)0fEs*>G?z z#Su}Rh+jh&Ib2M&L?;?9j*5ajSxXcN&XG`Uyr>P`EMAmDgQKEwNG(yK+hPp0IuXPx z!?7_2Iq_{YHjN2$bc^s^*GaltM5Mjb(qaK-m0flWABY2{Z?Q~NPhD{43J<|4+#4W% z0Uw1xnile`V2t|OC7sx>R&+-zj-tULzmC0iMnI*S4O4;2>74slNT yW;#B2lK?&= zw1;VWvkvzx@9&d9LIzc5@79vf)L@QWg|0a8zb%7Z;l&2RSGdy2Yz-U}nTJ7B@d^tj zy@F|To(yx{EBGzw71@A0|2ZsNNBccG1&Y(JlKr_s{OHAxfws GWt7pS|?{DAYMgWDCVING#tL zx^DLuKyOp9l8wX-Gh-v$*zZD76KsszA2=kc^?lCptQ__TEL! xt#lG3 z#a?~RzjM85eZhL(oR*)Grb_mtRhK%QB~Gra#@*tslOibd9+aEI7^xY?K{&JEv2i#P z`9tJXL7qhlWgz~A4NcF{)|Iq?qc4ad5Y+kxIY*xQ8VK33Z+B*8U*dC4o-F84cbv2p zpX6O{U-Z0zRS^PhRhg2AK+`Bd9vgw z^`Jre_#yl^^shrM(`VI@(^Dr;RxNPNuheqGY0w6M7l){5e>Z*L#1IaVNsXX@Vy+r> zaR1W?;X@^V{3kb}iT&9)#mJR-5|ISb^~tj!a}y|I-lHX>*>hipJIK0@_J5{uvj*@e zL*57!Eb*_xPm1*H%xpae$({d*) !1r$@)lX*tVfB z(I5n3hQ9yXHeTPhrD~f#QlGrl9Vw-wj{|1kk4#XL3COhfBcqS={%m3TXh>SIrpS2( z8NCd+{C;!-Frf%f_M@Yh@%qTBt!?^N{i{{(HtBDz-@nfQ!9m}@6xPbSBVSE!bGN#y z_UhZDXzJ5IQ*ipUYw)Zi$L@o+C^Ut%SPtR>ypawBa(rEamX#UT!V!^o#34tAc?jc2 z@LbGHaB5I=7nCIpE(Ro$K)M#XA~0>Q)j(0QRmL>}g6e{k2&qc$PO$i<>~|T+fkbxf z`WHM@eqQ*3$d;3VYA8c8B6DDJVm5<|ed;r6*nB1bAQn>YK_prRqdXY&x@Z~Ii7}21 zm#`E&9G?9eOk`~NG#REfnAqsf{d&9qw2rTPKTh@TI(Jw8S|*>-Z<_3nZx8kR8ZF>L z^fkrI1hf=XgWdLhyl*XOLVY9}1W}eh?;Y5K$1w8hF{G};1scdG ?Z8rVuJlbEqF|rtX{5wiT9bu23pd-{!<_)1QESZC%pu%nN)L{-?Bnp7( ziFE1W-}r>rj~O~hgU0WdAT;UQt=KB#cFF2xWTotL8PQ{=5n2`WK?8kk6_qZMlGzd> zIYS;lMh3X0xO2joAo`$@KDva;=19qW3Az6Q8G4R91ke`>S9SR}{C-@K(GGVkPv%K7 zi!$w*4(S=F%RG3B4$sR2uZqM;tQF;jhJ(VH)Y2T4@x$*6a}JS?TN zB*LViXO_}Y6RB#ylq{9xH8h% Llh=GM3-gAG dO+zL z+{-rwRJL@XqL~=Uk@R1#@IN9vLjI-+5grj96 zq@?V)j1)mKE|qXgZ$0@V4Qit^VrZi|-IY;hltMDoVpDtE4!50mdJ(WaM>eui95uB< zgX)B9isdUkubc-a>k4^c-X2lU5@-+6tGnmEZ6LxD5n19<9C|(DwYe@--BJe1ZZ&13 zmZqRwZAwZ;Y$i_={44zC7w2YYtb2WJh|rJzHrihxT9aG8bC23xT2x%d3yX+eU?sY8 z$^gh!? 2*)>;RT6B{ZmB;cYPPPO6f6kyA@m8w@7kU|`wy2Jg(wh<3(=#5fz0 zb~DY7E^%~q+^$@Xh28Ax`dV+ToA&`L098>802qy3{e@F7TwqasCGvkI=?y*{_$(1l z^@h*aP@V59F|iP{#~Y~BoasrjWM)Ay%cAW43LH$>i#j|{FTkVg?It^AzHapQ5_}`N z?tmWdZ46QtnC`a`!jmtB$*ahNe-o64U1P{$ZY|K@Y!2yooxGueyw~l3f6rmvk+P`= zW}n%{o9L!T*3dcgmZvXDT^c8q9~SZ?XIc-ubwwp#dN&pjSdr_!09^foZ5wAJJvU5q zW#d~5TDeBacPHz~sN)=&h|~PoMtY@v+Xg^n *3W#-0wD~S@3;?t(TOta# zyc$9qu{bBiljKdVNDYz4l7G?9?t-&EeE)Z7)|;nxzyG1i-|O27#rUl1%&OE1`Y-aC zvJVM1@ulC9VA@5|FHKZU-2KuiAi+MTpI4+-WmadQ-Aa-Rl2H>tX!RwH>c;v2lC2So zawzm5H`XW?&$)%vtX7)~afXNI=L7t9P2t1Lq35<^&LJW6r-gCn5T7PA##JMs`l z>rfFmZi6woKv~DNLU!jr{}$%41af;|sqRV5zh(SxI9q!QckJ4##x>izBW7zM-`wjG zZ7tj#1L4w2+3sC?vBc*R4G0x2M2A5hQ$D# kXv-{m^o3~$V5$B<$iU?8(Wv~7>8b!C0jE%TdYWMqhh0I(Y-e1`c8~o zXqPoAMx*j&zqk1qYcuEfHn&Hj&DP2TeQlz}l)`kbsINtY- 06NF{ zUQrxLT(xVN8cR2CP1_NVDdUc7+oqhw)vH2Q)f|aAuEu)%_Leu-qbNX+t95^IN74~! zQ3(ko;$h4uGZo$QzoQb4%C*C$skd>=I7}d-4vwrn>3A>Ak^OOPuFyMN%s&=j0(P zCEiIT>ZwzjZvtaR`h!ERBKM6xH~5iZs7Hb}zSB-qZB5u7uj692ugP1d9yiYI9nTxA z^Pt5ih52q~p=!=+sw}9&Ur6SSoBEsT*A6*$?cwX|YWM8DJnT&(R}sl~XS;v?bRNBr zqXTIFD0;tY+y^_Z{PIh6$ gq?T4|X5_m?QVMfD|)8P3}AQ>CZfK zPfjB+gmhdN;zC~Nw5T+Ua!D+(I1q V?k~*{|m9KLC<*-}aNrUTS)U`|(Fn&-cRjY)%<>Q;B&bl#amv%5K@s zaY}C{s>XYXlQ@;{t^GLWR<`slm>+$cYc*3|u4 ~r+L=K^d3ztpXzo!dME7I-3^x?3v-1D=>ty~%_Wu+3ey}{5i zWU5nr0W+7xKTi0RRyF83IKn0uz<0%eeXPMPd`hZ>ahO3|GT2LlSO%^AHewny_%>=P zxR9@0hexeN0g0v@2a|EJ=BiC5i@AXJYln+63iO%jD*C3_Rc(i667zqeN@B|5Q1XDT zn)x_gY&4`rc{lAzlNwzmrV@~b+4ycPgxcy3vQ>>+C8h&6JcT gZ76c-JMxl07l_((eKJ?py10piXBC+5*#+t+-rfHt3f6=j}7bXipsRo6eQ>t zK2NRG#x0mX@0Gbz{eMqV8q5a7x~a||tjx`_rj_VBBb0qI^O#Z#Z9Lg^i(%2m&|L+3 zTUK#)2*MVh$Q*zZLWDh-=v)p*NnuGywea^V=ifee_Dl#gEV91>?BN!Rw1$d5rq{`J zxjt0 )6o}wR )Qs}uh@c|C+C$|<2*Awe)@@wgH^NKzdW$TeTT7uh1if(u)%V$q5 25W`{IA zcX}o%ddG;%AYJTJikJD@DIF>@=S%3F9}>tO5qU}M1vzi&VzR8GmL!Y(f_S9>Lq;kN zrAG1wLTRBr>xC RiUEM0jTIu6uQwypr8wKbb2yW9@eA9`~v|Mwlg Nfc ziF_$V;W2~(!=8o*)T `szdoaHfc z=At6xB$@Ky*I$c3^%_a`(!Y_n)Z{I~N-$vo|21%-&SJ7fCg0_;7bDe;p${I8784;x ziFO3g5i{=+zomH^VgKoxKH@of2{r@@fC(&fc^Admii&xx_Tw^)Py?^Ae*z@}e)qW` zy^m#3(K{H>(!)DJ67yGb?~N_R8iR4G0R(MVEYh$;_~Q-ri%Tp*Fe`n=enfH6yZ>PS zuAX@E8k@UW)b^LB6ouLO*?FWu=B+UE=@3~MN5;{0Wzy8bOp-5ixQbk6axoP(&CC__ zqEvuRPa$$yx!9nTZ6J(-yy%zyQEY<@Y~Xa-O3Xq vC^a1#EZoIIHhuuQ%>`KpBsxOx?}5txAIvg6|{%a?`7YwqxOxMj;M z 1+gUwpuyH=G@!He2E!HuIVu-<$8h8h4Q5~|LiBb|t_KAi zCu)4%=ui!{rFCw|9`!Ze0y8cvD@SkSjrzij;%p`#sO+omNxIOURved*o}dRm#THwh z%rpd*eNsnEJy2O*Rn(-flh*1J%Hj}s@% GbVqdlaD4WD<1 zvMqFrQrIGF`O{ElAhlu03gNjvjfbbAzc5sotXQ%{#7a;|P9mpWCLB=Y7H61HeBuy2 zgyIvX46v|rO06yzIYbVj_=L4I#|hc2-f0TjjueQ{4#OAEim0 2xT_fC3M!yotk`#lwQ}@A2>X)A+qd*n@@1 zmB!|h`jEPky7ES~pv#o`;bpr7SKvH)x}lNOV*vZ9PqrTAQf#?=d`TMcDo|~tb03MG zHiv(4TrwTDo90 AE|FTL&EN=tYa|O9~Nn?3keF;}r z(rm0uW@%qGYe^wS@G09RCTfgMF8s9zVvdLpF(Dg-c|^#SY)~K(;1c-{0N6=fNJNl9 z5Grp$$STG6lL9x;8?WvS7RM)K;%4g;tvgHE&DJVYJynJpbxl=C1!8lEIh9sf8}&6I zMBO_-qHl9ytioMmYRq5{r&d{!m0XgjPE0nW24pO?8Of%d?!=G;cWq|9+7~SDb4MWr z;9;BhbRj-lW2j2?aDFYSVwUc-Ch9}}AHv=Px{0f87j=Rq&F3Vf*aNnNXXu39OXv^? zy*r_r>W+Kwk}O-6CE1p3N$w5zPBCC=AQ%X}1V|x;l0Zl!2}$ #TL} zU9vpV% qj+4`|f~o(jK_>^@yyB#woHZl1=Z0qatuWAuw6rYOL89{MYp07d-%(xL)uQ5IiC z95c-smb5+%zyV74{v{jDz}?tR_ZClotOxi!{@$v!vy^ 5YpOy;l2aM#@Y4CtDsXCqJg zD&4V|68Kq5Wz>5QP@-e#N;%CYkrT<%MNqW(kT`7{EvJ$}8%IJXc_5J+ zGdNL73Yt(o&ZR@4K9Qm{##@x8H1JpklH>q}zAu9)Z3$ZAR{Fk-__ac~e=6B2@7vvN z=<3&}t6$e?9JP5;)hE#>_O>%5X|DSpYjJbM@8Q;a_{lJ6G5p{?{{U~FILc|*P sz5=DbT6ld1GWMw4Ssw aF@+~>?m;0YMEJq%YBXiwf$Y8e$ z*vDQexh8yWvKYXMbhD(Xb5shtL5iY;5?HGZ_8GA|qISjabW1ppa46z9-7Cy-wWg_Z zl?sTat5l-+wN`CVn$+a3{hlivZX(==z?Y+a+Bfb)#UcF``5H1`Dq4q_NrG?&vT BqlK|f8Ta-OLB5er>SmeO+0U~j|8()}N#esIo=#Nr|$fDnlz z>C*GrLtOMM{0<&MCeBNjwNCa-6cK)uA{N9Fh=m9BL9mi%YzFwna(#~#^L%o~l}v_` zpbJZ*q pfb^MGMXXVvs y+Pm9( z0ez^9itqUtjX3dzbxp_m?Fj-JNfe8BVq@=xe0PDIe>3;LgLcY2nOg)DJ`nIrHe&b=*DZfc!^Y`Z$KO-oQp{SK1` zP<8J#FPhTn)G$C&qwNJ@BZQbD+Rk~fzQG}Wz9DsOt#$Qn5C}mG0yjL`4z!R9QiJVd zv7r@sg1?yjcnC)uq T^fezH6QBzT36OWQGQWn97CqOhzZiWvit#~JO|gv(QItqO$CKTVt$1v_R&(U^ zfsT?-)9zJQl{H)TTMroysi1;bbUfn}Q<&bMYT{ZBR&<^bOO}0b7*QQ>3$DYqhxq!{ z1-C)r1lOpU1$2qFUlS{mVc7g+?1rqXn~Y?wP1y~F^2}3-$Ab=8_hZqA)ca$b!>e-c z#eeGEvGeq@n#o2cJ;@NKm?w>w(jio_&4)VhE2Gv#`{@~feNYJ8o>*BxT?oXx_UnG8 zuN__2=Gn5dG4P<5bxr;{>)QNPM|Lq4xM||3eDO`t`LK_9Ri)aZX<|r&^u?pcFO2+| z==1&udSYiXewK9(*gwrMA!a(4 zyl(5x^{GrEb*BI udVG(ub*XM3Ml6; R6wNis;bnYb zY;G`iy`K#3Q?Ij!WI>_fiQ$UqM005>pPrvlf-b|c>$F %2U84=_u3JNV1 zMLcP{ZkNi0rKEG9do2lkPF`R1{Nh*fKNNqdd6M&8!jr;V+9z3$pO8Mzcj~)#kPh}} z^1iUd#MHz@j-Hhz78RuyDQXHTJ9zSh_^=&``+|ZJBe!#&KgeEgAtkK++6bBR-A%%^ z!OzImvDiZUHTJA*x37Q9UPW+pezk_z7U}l2$_2&ub_ZFsL1ol&NtwCmah8@9)|K-@ z-|y`yGV&vNm-WmDNR8%vRpGIIioJoQ`-AwclWe2EmjS~@gJf;>jit?s*6LUvE1w&a z8Q>{L>61LMu)&6yj1(>-MiZw>VEiI{;^1ZBSJD#3$5yEio{^IUvNPrbWz}3uO>4<9 zMMrDAe+^$6Zub62J`}>2GS8yO#>#^HDidck8Oz(`Pm-nfzq*F8p2-nG!AVueIN~Ea zR=BUep&-(eqdqd*o^B0G7$gJ2h$9#G)$;Rm%}hB$=G6we3br^H#=ux>PRfdPv9p ZnJqmBFb&Wd{0Y4P?ejXJ1|Sg@%d7Ww_cvRw+P`T5YI+)yWrmSI(Dx#Z(hVBSbD_ z@|W=g9PR89TD*u9O9n|RR#mR7T-7wLe6blmS5?nRm5jV043Uru`j9=7axfPA817f% zso3rp?;p;`hNpOaBu5PVk=d&(jPtK=qPP}8W~$nll$(q=xXD!y(O$iR=GZ+_la0waYBzO`6^2GcBW*1~Uk@RR zu) 14>HKH1oE`|6)xIZ7SSd)Ewq{}xqObr zP?%akzZa;h-e5E;gzB!Dxdj+!aYKS3qU#=sK!U-R&l*ybfrEI;#iu02hKj~TDvc=* z35`6xCC37);BT(jNCStoPN3GJx0=x5GnupVfozc06~-l4L<6EQgB}~{D5S9_jX|wd z+sAhMx?;mn)F`lr=BP1F XGp;Gr`y{yTVy;-jUkV1O0UggQ4 zGTd=3Q(2> {DIf5udH3{dRS-6S$mmaWKOOP2ggVHCB)DzqWk#b> zFEPr5WYLrB-T=q%S-ZH1Cv=~XLOLZv3hiXUggH-srU!&1@|nb*WP<{u_zjbA 5N z4)bA!F1zIq@}|8@ut)&&$pd_Hv=G*f?Do${TYuPno3*u*$=&Uu%;-F%c;^7HFb9nN zg;?o&@+#{e8tTuzJS+7Nt!?3L1IQp*^6u2_+vU_u0_Yx63?@wg7=7lB)Q;qKbU6*u z?g28oUua;_ndU$+oQD(xhfP6B6;BU2WWwh`Gf__8 K)j4fQMV| zfyTxIc+@P!hd^(hzK1O#UT{B|^gVR|BwG(+p~Iv}h%)e5MjgHf$CVr47>%0k_umr- zk!e~h%q9czu623f>ux{ZjdI(YPQs`C@E?IQ{Ah>cNkuP6gyB-Ca!0a5$P|gaOge;4 zk&KlJJ8VmY9o 27Y0uYo$>_q)CGk87&$ z7}BSLEuwwLz fNx~D$6j_G_^&Kg@ AI5H7q`}n<0`ZWUvnrliyJ!xR|VY_Hpf{ zHtUrGB={f+Zh@FDj%)#WexFRePktu_eQe*`#<6*87H-?vxUS0Zw6m5uQGd&8z5ISL zoBRi5Zj9ZvCUp0TtYz7gzJ333-s8NR4^@vDGG9=zf$bmWZOx^MO3E3jB4T^$W=+VR zV>_>=UeFMSWb%sspTY~d*K>|`Fo(O7+2}?2W9ILo>>@f*LkHK)FPL1l`*z$-)otZ1 zQ@(3{4hf`#$r%G&2?x8isKa+hAeKOVI-E`pr4vKY$a6=ScMMf5p%Yc~-Qp3}p|vwF zc{dnNX%2^zk-=m{92sT4a;NTW1@l?W7X@GBl4-?cRvMY)*%^N^^1#-TC~a_h1Vc)m zi+JA~U2FSvEh2l^z{Iu5JM;{d>%xAyB^QdwBlJlh3bSQ2HQiv~(;>X2=aXo^)ijox zCZKyrW~29pq=7pvA^$6fBYiW;fYS{fjM$fR486xj`VaORo59c~?O;%3hscF#U?1Y_ z|LE3nvdH=ylWR0(Who3 N5z6avbdHGOMVr934K^$Xjj8P_m{o? zEjdUt=WA8DsPjY)aC`ua$^d9IM#_>^J(+D~W^$gI5ag~=odmCk%y&2EW#?ey{uEsD zESV@1x&7A)Ca0^8HRWd%Xe^WqE{aXC0m?NteRnAguS*MIz#z!vOstQ&_dRodq;L8`UvusgN+1Rb4 z=kxPNc|!?mtenm`MQ8m&=W}%aZ@*3XQ_+0Xy6+Rd^6UxgAw~OXb<_nu`r $uAKoaiQjiLRLZLX{z|Jhp-B>WF1uj_MvKU43%P zr`%nsk;X_c)49%Ln2E&TQ4rJHsQ3Fl*~OD5Kjmp+*E#k!>7R}LPtA5q%}U8gPkkRA zakZ PS1|@8$7qK=X$rYcLVz70y}1CB1513^1b=Q-bPEi|ADIyYd>x&Zav>t33}f2 zlp^wWh4bb)D% o5!OjJTfLNf2{Aa&Th_84{2u3((q z930X&BzR~x1R1j`%#Doms}2t4+Ja_dld(Zps>x0@sZtWc{5=*#e4ts9vLe_2fT|!S zK0~L|7N%HZ7-xqw4#gowJG^6zzwgZnxP;X^J8p5fl54&ZSRYYMxj+04#g|5!Q!1iP zB;Kq&iI6WnJr2#J4pD<|Of5QUZBU=hIm$RQ^BpXo-y|a%^R_+y8|Ub7J9+BPV^Y<=2}mrpAJ%{MNEoah9p7>YR)+ zwF$<1hE!Ez*rLR!)Zmmr&92N9nMDnrHOaZz M&y%6P8&R1?HhKK1Que)W%%SYRGIVK6gmprmxwqpTi`dFDsAc3X@hLZsV+N zsjbSw=~_+Ux~SC9lz2;Vjip9?%_Dt-F7Mr GC{Pi@KQ(tfz199)ByZ*jQz x2vAQrfJC>mz5Pw#uPS2EQ6ln@kHNj#s({l>YORG@oBbc*D zb7)nOS=x-))U>#iTy dT+~t&p^g}td*0c4+^_xlqa=fqT zQ2BLmc%EMVdy}aGqQnWm#{IR5yf^zh{IU+JWw&;J8ulpauJU&F&78+u$eVM{Z)uxb z{(+wH@<~e!;VH8+VPfXc%+Xo&RqK%CtxG2C+^}waFzw?#DrQqagl?HW*0{B#@W#gv zs4Jj2+p!;2UXLI{FeZ9a>U(Xo%NLa_wC=Hn=c>h$RBhV uNv0(pisojKiJ%WX@&! z=I3G*