From 6811158e1a4b577e7e9fc21837c0b4f87f240439 Mon Sep 17 00:00:00 2001 From: luccioman Date: Sun, 31 Jul 2016 19:24:52 +0200 Subject: [PATCH 01/33] Expose HTTPS default port on docker images --- docker/Dockerfile | 4 ++-- docker/Dockerfile.alpine | 4 ++-- docker/docker-cloud.yml | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 35102e4d6..a090c75e8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -36,8 +36,8 @@ RUN adduser --system --group --no-create-home --disabled-password yacy # Set ownership of yacy install directory to yacy user/group RUN chown yacy:yacy -R /opt/yacy_search_server -# Expose port 8090 -EXPOSE 8090 +# Expose HTTP and HTTPS default ports +EXPOSE 8090 8443 # Set data volume : yacy data and configuration will persist aven after container stop or destruction VOLUME ["/opt/yacy_search_server/DATA"] diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 497dd70ed..eb913af31 100755 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -68,8 +68,8 @@ RUN addgroup yacy && adduser -S -G yacy -H -D yacy # Set ownership of yacy install directory to yacy user/group RUN chown yacy:yacy -R /opt/yacy_search_server -# Expose port 8090 -EXPOSE 8090 +# Expose HTTP and HTTPS default ports +EXPOSE 8090 8443 # Set data volume : yacy data and configuration will persist aven after container stop or destruction VOLUME ["/opt/yacy_search_server/DATA"] diff --git a/docker/docker-cloud.yml b/docker/docker-cloud.yml index 24a09a69b..97e965bd7 100644 --- a/docker/docker-cloud.yml +++ b/docker/docker-cloud.yml @@ -2,5 +2,6 @@ yacy: image: 'luccioman/yacy:latest' ports: - '8090:8090' + - '8443:8443' restart: on-failure autoredeploy: true \ No newline at end of file From 16dfc49bfd84a44ae4ba3749607e9ac6f3bb7507 Mon Sep 17 00:00:00 2001 From: luccioman Date: Fri, 5 Aug 2016 11:57:38 +0200 Subject: [PATCH 02/33] Enabled HTTPS as default, and added HTPS related documentation --- docker/Dockerfile | 7 ++++++ docker/Dockerfile.alpine | 7 ++++++ docker/Readme.md | 49 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a090c75e8..401d1f1d0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,6 +18,10 @@ WORKDIR /opt # - Compile with ant # - remove unnecessary and size consuming .git directory # - remove ant and git packages + +# Possible alternative : copy directly your current sources an remove git clone command from the following RUN +# COPY . /opt/yacy_search_server/ + RUN apt-get update && \ apt-get install -yq ant git && \ git clone https://github.com/yacy/yacy_search_server.git && \ @@ -30,6 +34,9 @@ RUN apt-get update && \ # Set initial admin password : "docker" (encoded with custom yacy md5 function net.yacy.cora.order.Digest.encodeMD5Hex()) RUN sed -i "/adminAccountBase64MD5=/c\adminAccountBase64MD5=MD5:e672161ffdce91be4678605f4f4e6786" /opt/yacy_search_server/defaults/yacy.init +# Intially enable HTTPS : this is the most secure option for remote administrator authentication +RUN sed -i "/server.https=false/c\server.https=true" /opt/yacy_search_server/defaults/yacy.init + # Create user and group yacy : this user will be used to run YaCy main process RUN adduser --system --group --no-create-home --disabled-password yacy diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index eb913af31..77134f222 100755 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -51,6 +51,10 @@ WORKDIR /opt # - compile with apache ant # - remove unnecessary and size consuming .git directory # - delete git package and ant binary install + +# Possible alternative : copy directly your current sources an remove git clone command from the following RUN +# COPY . /opt/yacy_search_server/ + RUN apk update && \ apk add --no-cache git && \ git clone https://github.com/yacy/yacy_search_server.git && \ @@ -62,6 +66,9 @@ RUN apk update && \ # Set initial admin password : "docker" (encoded with custom yacy md5 function net.yacy.cora.order.Digest.encodeMD5Hex()) RUN sed -i "/adminAccountBase64MD5=/c\adminAccountBase64MD5=MD5:e672161ffdce91be4678605f4f4e6786" /opt/yacy_search_server/defaults/yacy.init +# Intially enable HTTPS : this is the most secure option for remote administrator authentication +RUN sed -i "/server.https=false/c\server.https=true" /opt/yacy_search_server/defaults/yacy.init + # Create user and group yacy : this user will be used to run YaCy main process RUN addgroup yacy && adduser -S -G yacy -H -D yacy diff --git a/docker/Readme.md b/docker/Readme.md index 7471d05ae..53228ae44 100755 --- a/docker/Readme.md +++ b/docker/Readme.md @@ -20,6 +20,11 @@ Using yacy_search_server/docker/Dockerfile : cd yacy_search_server/docker docker build . +To build the Alpine variant : + + cd yacy_search_server/docker + docker build -f Dockerfile.alpine . + ## Image variants `luccioman/yacy:latest` @@ -51,12 +56,12 @@ You can retrieve the container IP address with `docker inspect`. #### Easier to handle - docker run --name yacy -p 8090:8090 --log-opt max-size=100m --log-opt max-file=2 luccioman/yacy + docker run --name yacy -p 8090:8090 -p 8443:8443 --log-opt max-size=200m --log-opt max-file=2 luccioman/yacy ##### Options detail * --name : allow easier management of your container (without it, docker automatically generate a new name at each startup). -* -p : map host port and container port, allowing web interface access through the usual http://localhost:8090. +* -p 8090:8090 -p 8443:8443 : map host ports to YaCy container ports, allowing web interface access through the usual http://localhost:8090 and https://localhost:8443 (you can set a different mapping, for example -p 443:8443 if you prefer to use the default HTTPS port on your host) * --log-opt max-size : limit maximum docker log file size for this container * --log-opt max-file : limit number of docker rotated log files for this container @@ -81,6 +86,44 @@ Note that you can list all docker volumes with : #### As background process docker run -d luccioman/yacy + +### HTTPS support + +This images are default configured with HTTPS enabled, and use a default certificate stored in defaults/freeworldKeystore. You should use your own certificated. In order to do it, you can proceed as follow. + +#### Self-signed certificate + +A self-signed certificate will provide encrypted communications with your YaCy server, but browsers will still complain about an invalid security certificate with the error "SEC_ERROR_UNKNOWN_ISSUER". If it is sufficient for you, you can add permanently add exception to your browser. + +This kind of certificate can be generated and added to your YaCy Docker container with the following : + + keytool -keystore /var/lib/docker/volumes/[your_yacy_volume]/_data/SETTINGS/yacykeystore -genkey -keyalg RSA -alias yacycert + +Then edit YaCy config file. For example with the nano text editor : + + nano /var/lib/docker/volumes/[your_yacy_volume]/_data/SETTINGS/yacy.conf + +And configure the keyStoreXXXX properties accordingly : + + keyStore=/opt/yacy_search_server/DATA/SETTINGS/yacykeystore + keyStorePassword=yourpassword + +#### Import an existing certificate: + +Importing a certificated validated by a certification authority (CA) will ensure you full HTTPS support with no security errors when accessing your YaCy peer. You can import an existing certificate in pkcs12 format. + +First copy it to the YaCy Docker container volume : + + cp [yourStore].pkcs12 /var/lib/docker/volumes/[your_yacy_volume]/_data/SETTINGS/[yourStore].pkcs12 + +Then edit YaCy config file. For example with the nano text editor : + + nano /var/lib/docker/volumes/[your_yacy_volume]/_data/SETTINGS/yacy.conf + +And configure the pkcs12XXX properties accordingly : + + pkcs12ImportFile=/opt/yacy_search_server/DATA/SETTINGS/[yourStore].pkcs12 + pkcs12ImportPwd=yourpassword ### Next starts @@ -111,7 +154,7 @@ OR Create new container based on pulled image, using volume data from old container : - docker create --name [tmp-container_name] -p 8090:8090 --volumes-from=[container_name] luccioman/yacy:latest + docker create --name [tmp-container_name] -p 8090:8090 -p 8443:8443 --volumes-from=[container_name] luccioman/yacy:latest Stop old container : From 75254ac9b60c5f9425318eab3b9c212f4541ede7 Mon Sep 17 00:00:00 2001 From: luccioman Date: Fri, 5 Aug 2016 12:16:11 +0200 Subject: [PATCH 03/33] Fixed syntax errors. --- docker/Readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/Readme.md b/docker/Readme.md index 53228ae44..9da94ccf8 100755 --- a/docker/Readme.md +++ b/docker/Readme.md @@ -83,17 +83,17 @@ Note that you can list all docker volumes with : docker volume ls -#### As background process +#### Start as background process docker run -d luccioman/yacy ### HTTPS support -This images are default configured with HTTPS enabled, and use a default certificate stored in defaults/freeworldKeystore. You should use your own certificated. In order to do it, you can proceed as follow. +This images are default configured with HTTPS enabled, and use a default certificate stored in defaults/freeworldKeystore. You should use your own certificate. In order to do it, you can proceed as follow. #### Self-signed certificate -A self-signed certificate will provide encrypted communications with your YaCy server, but browsers will still complain about an invalid security certificate with the error "SEC_ERROR_UNKNOWN_ISSUER". If it is sufficient for you, you can add permanently add exception to your browser. +A self-signed certificate will provide encrypted communications with your YaCy server, but browsers will still complain about an invalid security certificate with the error "SEC_ERROR_UNKNOWN_ISSUER". If it is sufficient for you, you can permanently add and exception to your browser. This kind of certificate can be generated and added to your YaCy Docker container with the following : @@ -110,7 +110,7 @@ And configure the keyStoreXXXX properties accordingly : #### Import an existing certificate: -Importing a certificated validated by a certification authority (CA) will ensure you full HTTPS support with no security errors when accessing your YaCy peer. You can import an existing certificate in pkcs12 format. +Importing a certificate validated by a certification authority (CA) will ensure you have full HTTPS support with no security errors when accessing your YaCy peer. You can import an existing certificate in pkcs12 format. First copy it to the YaCy Docker container volume : From 5ab8afd4d860209ad09ff6f7411c457430bb5ac7 Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 28 Aug 2016 02:19:29 +0200 Subject: [PATCH 04/33] rem some more untranslated text in language files (to proper display in translation editor) --- locales/cn.lng | 40 ++++++++++++++++++++-------------------- locales/gr.lng | 4 ++-- locales/it.lng | 4 ++-- locales/ja.lng | 18 +++++++++--------- locales/ru.lng | 16 ++++++++-------- locales/uk.lng | 10 +++++----- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/locales/cn.lng b/locales/cn.lng index a200bd6e2..b26d906a9 100644 --- a/locales/cn.lng +++ b/locales/cn.lng @@ -97,9 +97,9 @@ Add new pattern:==添加新规则: "Add URL pattern"=="添加URL规则" The right '*', after the '/', can be replaced by a regex.== 在 '/' 后边的 '*' ,可用正则表达式表示. domain.net/fullpath<==domain.net/绝对路径< ->domain.net/*<==>domain.net/*< -*.domain.net/*<==*.domain.net/*< -*.sub.domain.net/*<==*.sub.domain.net/*< +#>domain.net/*<==>domain.net/*< +#*.domain.net/*<==*.domain.net/*< +#*.sub.domain.net/*<==*.sub.domain.net/*< #sub.domain.*/*<==sub.domain.*/*< #domain.*/*<==domain.*/*< a complete regex (slow)==一个完整的正则表达式 (慢) @@ -641,7 +641,7 @@ Show Information Links for each Search Result Entry==显示搜索结果的链接 >Date&==>日期& >Size&==>大小& >Metadata&==>元数据& ->Parser&==>Parser& +#>Parser&==>Parser& >Pictures==>图像 Default Pop-Up Page<==默认弹出页面< >Status Page==>状态页面 @@ -1563,7 +1563,7 @@ Delete==删除 #File: IndexImport_p.html #--------------------------- -YaCy '#[clientname]#': Index Import==YaCy '#[clientname]#': Index Import +#YaCy '#[clientname]#': Index Import==YaCy '#[clientname]#': Index Import #Crawling Queue Import==Crawling Puffer Import Index DB Import==导入索引数据 The local index currently consists of (at least) #[wcount]# reverse word indexes and #[ucount]# URL references.==本地索引当前至少有 #[wcount]# 个关键字索引和 #[ucount]# 个URL关联. @@ -1580,7 +1580,7 @@ Always do a backup of your source and destination database before starting to us Currently running jobs==当前运行任务 Job Type==任务类型 >Path==>路径 -Status==Status +#Status==Status Elapsed
Time==已用
时间 Time
Left==剩余
时间 Abort Import==停止 @@ -1607,7 +1607,7 @@ The assortment directory containing parts of the word index.==分类目录中含 The words directory containing parts of the word index.==关键字目录中含有部分关键字索引. The assortment file that should be imported.==需要导入分类文件. The assortment file must have the postfix==分类文件一定要有后缀名 -.db".==.db". +#.db".==.db". If you would like to import an assortment file from the PLASMADBACLUSTERABKP==如果您想从 PLASMADBACLUSTERABKP 中导入分类文件, you have to rename it first.==则须先重命名. >Notes:==>注意: @@ -1869,7 +1869,7 @@ Search for a peername (RegExp allowed)==搜索peer名称(允许正则表达式) "Search"=="搜索" Name==名称 Address==地址 -Hash==Hash +#Hash==Hash Type==类型 Release/
SVN==YaCy版本/
SVN Last
Seen==最后
上线 @@ -2068,7 +2068,7 @@ Table RAM Index:==Table使用内存: >Key==>关键字 >Value==>值 #FlexTable RAM Index:==FlexTabelle RAM Index: -Table==Table +#Table==Table Chunk Size<==块大小< #Count==Anzahl Used Memory<==已用内存< @@ -2755,7 +2755,7 @@ Please go to the User Administration page an You don't have the correct access right to perform this task.==无执行此任务权限. Please log in.==请登录. You can now go back to the Settings page if you want to make more changes.==您现在可以返回设置页面进行详细设置. -See you soon!==See you soon! +#See you soon!==See you soon! Just a moment, please!==请稍候. Application will terminate after working off all scheduled tasks.==程序在所有任务完成后将停止, Then YaCy will restart.==然后YaCy会重新启动. @@ -3136,7 +3136,7 @@ This table contains a short description of the tags that can be used in the Wiki of YaCy. For a more detailed description visit the==详情请见 #YaCy Wiki==YaCy Wiki Description==描述 -=headline===headline +#=headline===headline These tags create headlines. If a page has three or more headlines, a table of content will be created automatically.==此标记标识标题内容. 如果页面有多于三个标题, 则会自动创建一个表格. Headlines of level 1 will be ignored in the table of content.==一级标题. #text==Text @@ -3144,19 +3144,19 @@ These tags create stressed texts. The first pair emphasizes the text (most brows the second one emphazises it more strongly (i.e. bold) and the last tags create a combination of both.==第二对用粗体表示, 第三对为两者的联合. Text will be displayed stricken through.==文本内容以删除线表示. Lines will be indented. This tag is supposed to mark citations, but may as well be used for styling purposes.==缩进内容, 此标记主要用于引用, 也能用于标识样式. -point==point +#point==point These tags create a numbered list.==此标记用于有序列表. -something<==something< -another thing==another thing -and yet another==and yet another -something else==something else +#something<==something< +#another thing==another thing +#and yet another==and yet another +#something else==something else These tags create an unnumbered list.==用于创建无序列表. -word==word -:definition==:definition +#word==word +#:definition==:definition These tags create a definition list.==用于创建定义列表. This tag creates a horizontal line.==创建水平线. -pagename==pagename -description]]==description]] +#pagename==pagename +#description]]==description]] This tag creates links to other pages of the wiki.==创建到其他wiki页面的链接. This tag displays an image, it can be aligned left, right or center.==显示图片, 可设置左对齐, 右对齐和居中. These tags create a table, whereas the first marks the beginning of the table, the second starts==用于创建表格, 第一个标记为表格开头, 第二个为换行, diff --git a/locales/gr.lng b/locales/gr.lng index bccae501d..bddbaefbc 100644 --- a/locales/gr.lng +++ b/locales/gr.lng @@ -67,8 +67,8 @@ File:==Αρχείο: #File: Bookmarks.html #--------------------------- -YaCy '#[clientname]#': Bookmarks==YaCy '#[clientname]#': Bookmarks -

Bookmarks==

Bookmarks +#YaCy '#[clientname]#': Bookmarks==YaCy '#[clientname]#': Bookmarks +#

Bookmarks==

Bookmarks Add Bookmark==Προσθήκη Σελειδοδείκτη (Bookmark) Edit Bookmark==Τροποποίηση Σελειδοδείκτη (Bookmark) #URL:==URL: diff --git a/locales/it.lng b/locales/it.lng index 4fb128405..843cce334 100644 --- a/locales/it.lng +++ b/locales/it.lng @@ -118,8 +118,8 @@ Waiting for new request nr.==In attesa della nuova richiesta Nr. #File: CookieMonitorIncoming_p.html #--------------------------- -Incoming Cookies Monitor==Incoming Cookies Monitor -Cookie Monitor: Incoming Cookies==Cookie Monitor: Incoming Cookies +#Incoming Cookies Monitor==Incoming Cookies Monitor +#Cookie Monitor: Incoming Cookies==Cookie Monitor: Incoming Cookies This is a list of Cookies that a web server has sent to clients of the YaCy Proxy:==Questa è una lista di Cookies che il server ha mnadato hai client dello YaCy Proxy: Showing==Mostra entries from a total of==le entrate per un totale di diff --git a/locales/ja.lng b/locales/ja.lng index f0b45c517..9f309b040 100644 --- a/locales/ja.lng +++ b/locales/ja.lng @@ -153,9 +153,9 @@ Add new pattern:==新規のパターンを追加する: The right '*', after the '/', can be replaced by a=='/' の後の '*' は次のもので置き換える事ができます >regular expression<==>正規表現< domain.net/fullpath<==domain.net/フルパス< ->domain.net/*<==>domain.net/*< -*.domain.net/*<==*.domain.net/*< -*.sub.domain.net/*<==*.sub.domain.net/*< +#>domain.net/*<==>domain.net/*< +#*.domain.net/*<==*.domain.net/*< +#*.sub.domain.net/*<==*.sub.domain.net/*< #sub.domain.*/*<==sub.domain.*/*< #domain.*/*<==domain.*/*< a complete <==完全な < @@ -665,7 +665,7 @@ Manual System Update==手動でのシステムの更新 Current installed Release==現在のインストールされているリリース Available Releases==利用可能なリリース >changelog<==>変更ログ< -> and <==> and < +#> and <==> and < > RSS feed<==> RSS フィード< (unsigned)==(署名無し) (signed)==(署名有り) @@ -979,12 +979,12 @@ Configuration of a RSS Search==RSS検索の構成 #--------------------------- >Messages==>メッセージ Date==日時 -From==From -To==To +#From==From +#To==To >Subject==>件名 Action==動作 -From:==From: -To:==To: +#From:==From: +#To:==To: Date:==日時: #Subject:==件名: >view==>見る @@ -1442,7 +1442,7 @@ e="Search"==e="検索" Search Page==検索ページ This search result can also be retrieved as RSS/opensearch output.==この検索結果はRSS/opensearchの出力として取得する事もできます. The query format is similar to==クエリーの形式は次に似ています -SRU==SRU +#SRU==SRU Click the API icon to see an example call to the search rss API.==rss APIを検索する為のエグザンプル コールを見る為にはAPI アイコンをクリックして下さい. To see a list of all APIs, please visit the==全てのAPIの一覧を見る為には, どうぞ次を訪問して下さい API wiki page==API ウィキページ diff --git a/locales/ru.lng b/locales/ru.lng index 48189adf4..0c1a635ff 100644 --- a/locales/ru.lng +++ b/locales/ru.lng @@ -170,9 +170,9 @@ Add new pattern:==Добавить новый шаблон: The right '*', after the '/', can be replaced by a==Символом '*' (звездочка), после '/' может быть заменено >regular expression<==>регулярным выражением< domain.net/fullpath<==domain.net/полный путь< ->domain.net/*<==>domain.net/*< -*.domain.net/*<==*.domain.net/*< -*.sub.domain.net/*<==*.sub.domain.net/*< +#>domain.net/*<==>domain.net/*< +#*.domain.net/*<==*.domain.net/*< +#*.sub.domain.net/*<==*.sub.domain.net/*< #sub.domain.*/*<==sub.domain.*/*< #domain.*/*<==domain.*/*< a complete <==полное < @@ -572,9 +572,9 @@ If checked, only non-zero values and non-empty strings are written to Solr field Use deep-embedded local Solr ==Использовать встроенную локальную базу Solr  This will write the YaCy-embedded Solr index which stored within the YaCy DATA directory.==Для записи данных будет использоваться встроенная база Solr, которая хранится в папке DATA. The Solr native search interface is accessible at
==Интерфейс поиска Solr доступен по ссылке -/solr/select?q=*:*&start=0&rows=3&core=collection1==/solr/select?q=*:*&start=0&rows=3&core=collection1 +#/solr/select?q=*:*&start=0&rows=3&core=collection1==/solr/select?q=*:*&start=0&rows=3&core=collection1 for the default search index (core: collection1) and at
==для поискового индекса по-умолчанию (ядро: collection1) и
-/solr/select?q=*:*&start=0&rows=3&core=webgraph==/solr/select?q=*:*&start=0&rows=3&core=webgraph +#/solr/select?q=*:*&start=0&rows=3&core=webgraph==/solr/select?q=*:*&start=0&rows=3&core=webgraph for the webgraph core.
==для ядра вэб-контента.
If you switch off this index, a remote Solr must be activated.==Если вы выключите использование встроенной базы, то будет активирована удалённая база Solr. Use remote Solr server(s)==Использовать удалённую базу Solr @@ -1820,7 +1820,7 @@ Delete local search index (embedded Solr and old Metadata)==Удалить ло Delete remote solr index==Удалить удалённый индекс базы Solr Delete RWI Index (DHT transmission words)==Удалить индекс RWI (передача слов в виде DHT) Delete Citation Index (linking between URLs)==Удалить индекс цитат (перекрестные ссылки) -Delete First-Seen Date Table==Delete First-Seen Date Table +#Delete First-Seen Date Table==Delete First-Seen Date Table Delete HTTP & FTP Cache==Удалить HTTP & FTP кэш Stop Crawler and delete Crawl Queues==Остановить индексатор и удалить очередь запросов Delete robots.txt Cache==Удалить кэш robots.txt @@ -2055,7 +2055,7 @@ This is the most generic option: select a set of documents using a solr query.== #File: IndexImport_p.html #--------------------------- -YaCy '#[clientname]#': Index Import==YaCy '#[clientname]#': Index Import +#YaCy '#[clientname]#': Index Import==YaCy '#[clientname]#': Index Import #Crawling Queue Import==Crawling Puffer Import #Index DB Import==Index Datenbank Import #The local index currently consists of (at least) #[wcount]# reverse word indexes and #[ucount]# URL references.==Der lokale Index besteht zur Zeit aus (mindestens) #[wcount]# Wörtern und #[ucount]# URLs. @@ -4064,7 +4064,7 @@ e="Search"==e="Поиск" Search Page==Страница поиска This search result can also be retrieved as RSS/opensearch output.==Результат поиска может быть отправлен в RSS-ленту/OpenSearch. The query format is similar to==Формат запроса аналогичен -SRU==SRU +#SRU==SRU Click the API icon to see an example call to the search rss API.==Нажмите для просмотра примера вызова API поиска RSS-ленты. To see a list of all APIs, please visit the==Список всех API, смотрите на API wiki page==Wiki-странице API diff --git a/locales/uk.lng b/locales/uk.lng index 7ab57e9a0..078ffc47e 100644 --- a/locales/uk.lng +++ b/locales/uk.lng @@ -86,9 +86,9 @@ Add new pattern:==Додати новий шаблон: "Add URL pattern"=="Додати шаблон URL" The right '*', after the '/', can be replaced by a regex.==Права "*", після "/", може бути замінена на регулярний вираз. domain.net/fullpath<==domain.de/повний шлях< ->domain.net/*<==>domain.de/*< -*.domain.net/*<==*.domain.de/*< -*.sub.domain.net/*<==*.sub.domain.de/*< +#>domain.net/*<==>domain.de/*< +#*.domain.net/*<==*.domain.de/*< +#*.sub.domain.net/*<==*.sub.domain.de/*< #sub.domain.*/*<==sub.domain.*/*< #domain.*/*<==domain.*/*< a complete regex (slow)==повний регулярний вираз (повільний) @@ -1747,7 +1747,7 @@ The assortment directory containing parts of the word index.==Каталог н The words directory containing parts of the word index.==Каталог слів містить частини індексу слів. The assortment file that should be imported.==Файл набору, що повинен бути імпортований. The assortment file must have the postfix==Файл набору повинен мати закінчення -.db".==.db". +#.db".==.db". If you would like to import an assortment file from the PLASMADBACLUSTERABKP==Якщо ви хочете імпортувати файл набору з PLASMADBACLUSTERABKP, you have to rename it first.==ви повинні спочатку його перейменувати. >Notes:==>Примітки: @@ -2505,7 +2505,7 @@ Crawl results will appear in the==Показ результатів знаход Peers offering remote crawl URLs==Вузли, що пропонують URL для віддаленого сканування If the remote crawl option is switched on, then this peer will load URLs from the following remote peers:==Якщо віддалене сканування увімкнене, то цей вузол скануватиме URL-адреси з таких віддалених вузлів: >Name<==>Ім’я< ->Remote
Crawl<==>Remote
Crawl< +#>Remote
Crawl<==>Remote
Crawl< >Release/
SVN<==>Випуск/SVN< >PPM<==>Сторінок За Хвилину (PPM)< >QPH<==>Запитів За Годину (QPH)< From 19b4509d54a0e452d9f755a7c21d848f9b23b169 Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 28 Aug 2016 02:55:42 +0200 Subject: [PATCH 05/33] speed-up reading of xlif language file, by using xmlparser (stax) instead of jaxb making xliff-core-1.2-1.1.jar obsolete --- build.xml | 1 - lib/xliff-core-1.2-1.1.jar | Bin 79263 -> 0 bytes pom.xml | 5 - .../utils/translation/TranslatorXliff.java | 108 ++++++++++-------- 4 files changed, 59 insertions(+), 55 deletions(-) delete mode 100644 lib/xliff-core-1.2-1.1.jar diff --git a/build.xml b/build.xml index faeb82bc0..879296ef4 100644 --- a/build.xml +++ b/build.xml @@ -242,7 +242,6 @@ - diff --git a/lib/xliff-core-1.2-1.1.jar b/lib/xliff-core-1.2-1.1.jar deleted file mode 100644 index 6d5f6b2039e52edcca07b6d52693e7560582ad2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79263 zcmag_19W8X(l(68n%K6TiEZ1qZQD*dnb@{7@x;bN9ov|R?fi4T=REIv&-s7r_wCia zyHZ+=%>Q<5khkyh5*8#Nfmi&K&f1hB#KIO&Lgc+n2B$$-`tp)=!|D_gE zkA7wU^>f(Q8~y)QlNVNymJnA}W0aS;m7kiFlVxC>Ly%>lot~O$R$*RX-8pojmzf@; zmt_!vfj%hFNWrA*BlqgakzsX}am%HxMMu3=iBUm|_3p?CR+F}Y)0%B0X5l7N@8@i! zXIJdkn$em;Eus)#rlY_!iw(~c{*_6?gvEpf^1sUi`G1!8wW9xXKzvD#E|&l22LAsw z{&n~N(r`3(wRZgnJ>>t;^RlzHu=odU?Ee>SQ%4tbLzeIV=c@ntGZ(>GPkg_Ysrn5B zgybKtl6SFXG_^B!bxqaKS4Gpr4&Xdkhk>Eh8D5HkD{KmHU86*Y#YU$PgDoYtKOa8= z)SOBBJk+2uzJjm4egr+}SWa*O6}qnnGPmxC++XrG?~z4TR$|Zc{HJewUVjlCZ@#@> z64it3pv}$$f@>l86ht9fRh+=fD^bQ8d|XG%(2a;oQEyk7x$|PxUCY< zm&66Fy7c}gbg#-2Q69(}3C`iDM7clc$~xD%4IMGp(*ky z&ta^i3j;6)(Zz%EsvREY*h+gTVmrwh-~w=Bhe;(kOw zp<|t#v6^kB)U;bN-h`Sbb-ajN9A4!O@M@sd8}0$|UxufH`p;yQngT`4{!-kXi{6D> zaz|=jQ$fz^qE9&CY2xG~-8xB6IZ^k4eGjW|>06e1@QmsX4sgSg^o;h8f<54Nxc6p! z6^;vpf^}-}bI4v#>N_tQRgVaPx!AElNdx;$y9KH3B`=UJvQPMAn%$Ox?u~UZ9z8f76ED0j0k!L z5YFxqo_z4op^y8@w%>|BLH`$L{|C8@<9l5rzu@zh3?6qr20; zu`9#d4_}?2FR(fLH{mM2yyMzhnt}A1o3EA>(pm&tw~KW0mI?1_u37o!rnZGec34qS zNUc|4ML|e`S3w1ONvn!DdP|g1QcQ?=A09p29_Zfn?xQa&FYoJ3^6E!pulMD9z~$p> z&$jPnTl4GH=VccWNZQ~hX#^;A7L0P3U!-AfWjUuYA^A%EUIF% zSIkhoh*Mgg;*TfxXiF(R**$L5UXp8F)Lyb{U(`V2Yf02V(rdnGtbr>_F3DOf={E|9 zz|vz`as$e1PSkq96K`}N;E5_aaBfO7^hyW$gF%?B&RouXY@@`sqXvTg{Pi%@h2FBN zQme}*qnZNdu>u@tb?P(*LO#bwzN-vrh;3CVenm;<{4#NvU4OO9O}l3J(BodmjkSe0 zon`T7lRo=GPs)7GxK){5mCgvjNWhgB9Z#m*#NUPz8Mn*3x|@W#5H zcbN^dk$Y37Zf;gRt!_aIq~~j5nGAV})zZg5E+Z>x%D)-kRoF!lU@=ot%`Dj%7qg_;YW3)6*1@fYep$8=G+Ui#UZ?J@1ePPSwQ>LqS7hAu$@tY4P%|naXO` z*~N5BF%ps$ity0A>2&T*8Z2z#_v;zWEn?GT#F24btTJTO6SL#exDt+5QRS)4!>8@JdE9xlw0P_t?e~xEddwBm$&Pr@ zRRAkiyOHmC7|_ca8o-?8fh=~~f(CLwWAG#rxpPSA>^N{Wc#Q-@Cjgsp&7n!jr)3P- z_jkInWOl^kb+b)QJK6|yd(Oe@lA2M~CgTRZQ*fD(TvPg5jrzHtor_tSYHr6~^HJY= zM9X5ETD{ETZLxB}ak}DedUbxyhUXm)`3p`gS7b!CXleiX)3$%nVg(!Cun;$&Df>v{ zQ@!o*CR`- zJtZ>k$irkQE-n~V$D>_p=s~EEqhpu=%&y#+!J5Lvd^_bb1)dD^2HA8@pxu1L#2WTB zETH|Bu(AF|=kx+qexi7{czJtZZT9fi2_2g=JhXPE7&ZkbkI_g*rbAy@0+(7$;J1cJfIy<%1 zRbzG|791bVb1yv>c=@*8`fdZ9%ocmRmG;sWfADU%c<#|ws9zb{`h)W+N^X2nriy=- z*jPVZaKlOZXu0YDMZcSphSd)0@#tUp>7)bwERg%yiBFbYqdp3flAGU;aSpEJ+&$;v4s$sy~3B}Qg1=lFA z(G;g<=!14SY;ih_{i(x@vqn7BoeVxpse@4Pw{KrBq5tdHW9ckvB!WU)YA0Uq1?3+H z3CH$&t{=Zr_L~v%&42zT8{x@mggq%lSkHxW>(`~W!)FwYb?C1l?ANL{vdcEI>tf{q zf1@XLxuh1`u#P_y0d9tz`sOskag4-m$HYvE$Fvoi)b~va^k>k!%tpqv7iSDa3BzPl zkHa|*NA-c%L;t0k<-TAnFjA>M1&4s8kD1CL#gZN>p+wOURz;~ zxuf?6R;;)#pNI47NDnr_Q^D|Es(%y~fEd4O$TIH?FZg|<+m z>LJ0Efp(iN4vi)Ttq?^(fq%rhk1O`qKEA{uig8X%5f=vA!02k6bE{EK>-^p?q`KxA zHTZlpxh_KfKltQ4LB!`y-ru+)Pu{OUkA+@Vg6SN!yx@yASW@x@# z-w@`8hGh%_aYz#Z)G!`;to-Y1Z-BK3Aba><0wj#eF{n!{vKcB;lOD_s4$Bxhp&3C) z%_B&MI?VS>Xlt~P4N9oYsn9baKrSJ1K1PV#uJE1+U?y6)?1~sS#Ef6a0V_oAP#6(b zSbzyCb0QR90zgJcY=|79*B81$4DnoqeE>#ypaIeG8~i|41YpfKmMa3_9U=yL0rp{B z2EY#{MF6LKW1%8|`Y7SDTjFw*0~RCz?{0N#JqKP1l(0ok$PWJ%<%>mLzd!5iw8-ou zo7z(z$}cd(-*yx}pr5#Y`Vicqk$jN=rUbavnSu+{p0?lgK91SaWcbyBAi{y%3ooKu zm-dp%-fdS5=9t~3Q)PNnd+&-upJ*g|`Fsz$6a0G!*~5nef>>Z5-h_A$2)pAQ0AB77 zih&=l_1Fg@gk}gKdV8QQaxmW`p;zf4p3AV!5Rv9%iP!mr621u^-4MTq5q~`oS8o7% zND04C4+TW#Xed0a@C91v)V^??Fu;~yNC8gx=$3f-n{W>UROU?RMjUaQSCF41Oj8ha zhbW9oFSZ#dQb=$4wdKnS@is=t!Zk4xj4&Z`=$DX4JXTCdyp6WB?xW#|S2411%YGzX z!=fl0{&}`?|M0r!qdOxikO*M%T3CnPh4$!nx2%qvS^67dr#*ZZ`Sgrpn`;u-PA@qT zIa{Y4Of)!ng=X|AEdZ8y5gY^>4^_ilFdn21RTG*I_Ig})kMN&)EOxOy-x&B8+wnzt zkpDv-D`jqMX72JoiEOUAts86NQ_ci?n9jtZ>q-tGLc3xMPEw9K;{?XAn zM&fd-j(g8MeEVHyTfb+=LC+u-J^3GBxodVBT=#En5ro%8c;Xq8cnE?{wtnmC4#WH6 z+gPCA^mggK0ZW(97;M*mXIX0M1&&(i*S|L%S<`GFodBX{Y&JyZueK{&A@yo~L}(~= z%~vtB(DT=_QfO!p1H5Ib zB}0h!&$Uh2&aQSehy0}Urp|wODXW^k3#cM_50Q52*ZLN$%ACn~22);m9p16(J)t0U z-{e%9f1`bUSX|7F4k|8>$gs}yOv8A30#8Wk?Um{qO*z{!sAI)0;#Gcamy}OzeAFg! zRC&_o!wKVCgHz_wMn8n9zm1SKmiF5o?YL>Buq&6TL%`5+fmY9f#fIZsX0lU8+wYR< zK+m`&qnXl+E)KO_v}uoa@)506=xiT@3rp8ab{3XJ?j7L={;L-o>*TAwu@(9JqJx#4 z(naT;^If=v9kxS+B0W5I-AucS1GiF6@S|Ve(|>a16!T$lznk*RiEkoB%g+H24c*!K zRvCwCBD6Aa=4cZJQ&CmQU5wpKGfx!k-0n|hP8-9s%fIvsXliAZnPlfVZtfdM#Aa=G zSMoZP;kXY}rHC^{;7-xMw_EL+FsX~vXv-ech0~m8@pY@8sxRkj=#{s9V9U#f35c0_ z42l04wazcOyuH&?dJ4iHN<khC_cCZCP|j`< z7uzmsTSXA04Y#D;72AQfBZ%_Sjr8^|bj9~R+1f9QPB1_BoarCtvjA&q9#)xw#@JHK zLv^wl)&U4-ataIrIQ0V7gh-IC4v9&214tMK-uTp{){q0k>&MGy)(S$b@?cR>pRx9z zdr{tZK^pzVeu~R}%N`spH0-dp?gEibfRfAJWNGpuigI-~vzu#8K5Tyz`k2 z2wdamgj0n%PC1O4L(Veqsy%YT>5^$0U zU2;HyfHk{Z-_Q(q^Ceg0~Kpc5uq_p znis*Ng~TWuXCsiZ7?>(4tJnc!Y`-pHdSUWX5}uxO^*hARzk7myf+R)Iu@|H-_KFzMc2@&^D^3(|Bc>nMD>kCdx?O%Sm--i4Bx2+P zBf{m}l>maLbvsrl(vE1Bzu4d?+eZ2B4{AT$@EM4x-<4}W4Om=aKy*jli-}L0Dzj8t zDyC;-xi^*SE>`IX>RHSoR+!c2=vzvrD#~~?)g;*D+stGa*;Ru+uK(T?>7`bc7KO#! zCZDO4%9ieFDbvtoh0BO!>50TTo8hu$J|l{DXe%R7Y$t4eL;P7|hmKAMk~NrO{D+=$ zJ{!m`Td7kH->s6zCgC3T`v|Qw{K3brL1kDacD~&vIb`Lh&a7Fr39)x}BHuwxY?B6n z^0 zs!NQs0jEsUVka`T8EsfaVMc;;7WX{?9n)CZ_uYj~ej+!R&CZqr>^ia)o=rl!V>6jm zr~PRF6?MrF{Uo<|r_cnepBOK=>R!B=nbfc!qN#@pII9+Ou}@x5LUq=6Wt!hK5*i~O z<#DIn9b9C1w6&`iV>(K6(LEaW6BFV?7kmbSZms1|8g6G%7NJ^!HjpX@(5#xgQeaaZ zlCZ3rXtwqV(n%cxIdM?w?~@r>_(CW@xkL!CN5*mJpV-;Q zN6O%`5xwngxKjaCN^d#qa3>M&-g(|uLGdXF65U?DeEM6$D)| zKZ$cRV?hMyT>wnvt`fQc*6ueQv#4E|xd>}6xL1f3e5a7zuKw)^ud14gs%xuUf^3N) z+bezOSy+-A@cn!EI-e^~@SfW|c4IQTPWTxT3<9%-FnNrq7e3TkYqQOmFpd=q)*O*N zkGGe4p%X3nIYg;LqdxsI>kgoPkIFP>a$w-q9I>J`_q4zG%1>f8C9;<_W}y3TeZM z6JL0>vDGLJPjUd5$cv>_wm;Z;BFSAYgEy|EPm1b>C^M3!6D7~v)%TpzN4mH{?lRc{ z!=i9tBJta-I&vc?LZZ_OMqZFKmQ=Pqb@J9T_CUaGoAOaYt#R()Fxza+wuf}kty3^K z`5v@O{x>tYGH)T$0AjgXWI9ff3qA-$(fOKu&}+f%Pj}J>nvwF+n}z`rF_r0b&u}uy7?1K($jmcnJ_% z?Bob6$v6SJ1c+ot2DZ9@K1Q3Q}u#Ujr(YLv)QM-h-LshLivh>VV$lnC*v+6ZxsO8#VM-lcuTL zyQDmNaMVbt-^fp)RSv;ArlFJXz&m1vE+9cV`XQUvkPbkxq>_-#T8PG$VGgoEh$&{Z zV7-FF-h_G%L7Z=@_cdEBInUSaI4QRaJcGa}wUnGMcEr8Fw;?HOSPPfK1khHirgPix z{wwgjw=&2lK!SkGebK3O{}6co6?HU>?cB}ftX=;de&RKBzrqjtN5qE0e98zYEhL=F z54M&t7ge8PYZ!a0tTZ_tDHP`{1t852=}bA9zZe+e$msefI!`CDw$Bj4!H}{CncKUG zSFoGMY)hDy)?FHpTieeM=Vzc(L=yhg&`!u4SQyh&kWzZ^ zMugu4exV70Tzo%}5e&k`Zv5HN&n$u;sj z5eF51wBs38RB#Z4lZllaN5Sbh+eFIs7H*Jv5QbCGl4~5Wd!$ZOC+(Dv zJB|ONfl9pzZZ278X>!h6G(RimNH)!*&Ogv?Vd0!*vBHX`+s;$QYC=s;m&wM|M?0VS zXNGl}-Qtq0g|-M6d+~JT?c8$_O786M@ruqN2}P{(8yFVp+te$NU~8B%>vCDElz8v&%3a97OTLW^oUxTkTcC-S zxara*n|`k~Gd<-xwCvPwRcBRXf4dIWqQsn`{BCw!t}gp~Jxg4;(k#o<NJrWI|EzG>rvl}Mp-p7uS~b&T@-6WMOsqGZQ@skBWoiTIK2;UDbqH6V|^ zN>(v9txvz&d5m}^2N%2a#os{&1+)i6?b2b25;bqw`SL(=|*DYZLpK_$u*gC@OCK`nU%X}>P%NE|= zgxSY?ddQJZTu};)ogG{g;&HKDSg?#Q9a1hVW5dW;gL*kb#;~@U3VB$oI|37sYu0#0BA{+CM(g{Bzq-jNx@Ug$T0SM>vAPn0X;v&@+p6YvVFp;k!+4; zl{;;ckdsjHabuBuI_FeA6Aj@y;{rC5BCq(uvtR4AcRwk|cZ#s4Z`Ou@JU%#0m%=0f5f~u-zC>H2%fK$_N zy>h<^w3A!!6QzV2 z_0sQ1fM)yMP=a!PYY!j)4d#I1q&r5}OM2xI>>1{FqY&%~`-^wzKofF{%!%tDT^Aw& zW&;;)tR8lQ@@h*d7azkbzwf-T;FsYJc;SoT>5#g3XtJv&2_3g023|@|TTHynwC1n{ z#c0(^)q#lH&s_A}b5ik0U#aKnC_quIJ(;@Zuz*TGEh6QZl-cVtJy*%7RPS2^vI7vJ4 zL>!KRK`S6*RZ-xD_f`TOXIcu@g_X34AlU^{2TxRPn-;4{Huw3H$$F5fIS?+2o zaFuh;EGKwJqGvWRrD<=Qs@87!`t07r7KnJElOm1CvN4htrt-0k|IMFjmM&n>#a3en zW&^Q(Pn*GETzu5Eh+^U_$)r|t30JhZ9zQ0Tmt2Wr z>g|yj)^iWO2eBJ#^usY@M|+-`!XO8TyL8nUw%TK5o6%2&J6&94jQ<0->{uo?U;zaz7N-o_I3gd zcqovD+#3rR%W#7#^~!uP6YY%#`{bQmZf{wvR)N6QjjI(1cWU$=t-RxO(~NCWb8f1X zpcB5_oe#>-eG(Toxx4Q5HYdhY@)u;*dT%(D zqxx~eI>BI#@DmxajU!fQQIK$AZ)V8qW$I%3$Rf^-gEI$=->KsA0Xz!l^CULWhvk;HZmQtEW272k~Wt4$^3majx&X7l@1bw{ForWYoLg=OU15wH3Z5u`ZD#S@I z94r0lr2I?u$npfS`ovXo;y5J9e@!=YL-d3K+NwuFd_Zn89Zyw2|0DR#~f2W z)7X8l>q!@#KQ+Jiq*9`y%D;gGQ183i zrOG9Gm6{fT6^p9(&T%mm`?s4-`&8;*>&nQKy4Z$R%iVj>!64-!t^Zp z0q!ED<5O+?M}6ecZ9eT!KCZ=h3Bm*p&B*{6$RRaP=0&R*{~snPL8?VRG;biz}&ar{vwXIf*2Gqgd% z@#A>MRBZu=@@{8f?U%9Nq>H_%gv@e<*5!#SNA}+~pf#0U&g6q7U2_ZpDzH>Qq;io- zq@oeE5ZDea5?L#TJvf_sMIL8zcSZjTG4S;hR_dAkkwkKhKjX(l`b?y;(rDw^d2!hk z&vK$i0iLcdQ|k7F_lY%{75KD2ur&+AH*pQlsym*8K{n^TnPwrw{a1}zJ0fR8iY2?R zWDd`gd^k&ckqD{^Z3#(n-$AKrmC#X5LFyk+b_lsWarP+9RS>aKrV_#dAaakoUjzZR zFzCytCUXoYA?c0}1|&-R5zUY?7vZlY{AE}^j9I}ni0^B#)N6`SegeV4rcnX6^p1P9 zmx@8&@U8G|PUSBzPIwnVHHZ4`SWolU4CXB7yhA;$1Vi14)yHwB_WQ3HM>JAH-_VyW z5=bx+4TZCj@*8^-9RSldQUauOyN%6rqtz#UZ$a#WCM+)0msrDk1??VQ_Yo~+@ z7a0SJ8Hv9@^vP<=VZ=5!Lf$9DM-|<|7_nQzEIWVTjZq9=iiZp8TfkqePq6>=4V}0< z=k%}0hxNs&6a9m4X#LwK)O1~e66hZXv3|nj{X#)5Oq}b;L!~LQA~G-vuwf8;a+Jmq zu{HG5D%x=KJo|4TUYAH#+pkdYDL5IYbgb{nax-=6RAX?6NIK_v{Kvds1*2m2 z2;*J=A(Vp!bDks^r|U|Du1m$3F=?csv+gwIp_3${Cu_#UPF}=Mh9!-Rh=B!H+r{q$ zleO9EyRK~lHOAX^K#X7gN8Ww5h_1SqCSABNeZ>y}p2fv4dH%L{V8I(kEOgUflq#aq@CIR2M0|8wb zmFTPpGg%$i`F5UB9TCxq7TmCG%`%cgyZ1aTivTKqmUGASCG-uJOKm!c{OX%d+vqaI z{$j(+1UqWkB51m_-&4>)HiC;ZtCT*4N8|zh^@?zT0ewb^$}AJ>JX-9>w2YE5eU44L zg$zN&yKv)K6w>fHT`E5RaA zKqw0(<_1+AsKW+m|2rSMwNhNDC5oI%eoKzr#b|~WNd25)84Sr&1lAMn*`#E#iq3!* zQnQzG@_NSg>4a?#^WxWQa1Y z;>i|$LeJnoZ>Y#I1&N;tD>-RzBykT_5#W`Zs<|*neh4P)hxuJOp_Yr#|M+lW(hrEd z`->P<7nh3Af2!bwQi_-qCR38G*el7C^#M9OMH|-Nsu@LWXGQ4Fj;w!qE_J#sJ|yOyUIxM#Z_Ga+$@&VSgM=rliyR1LnXO!QfMocOb=J3##gmq zRxdq!wKOS{GSM8VBegINp^l43zY)qFnD8a(rHm1w-&!J(3J#-=OGdw8$R4QhCHbYm zw4i@n2p?GLm331msilk^W0RiydW82>+jz)twht}{4c>^GV)wh7R5%m(u-BK?*ejYTCS^c`xnF&a%U z${e2xbijtal~emX#vl6`-ctZ%!xceXuQG&|O9gV{8N}%rTuAxN*OOuLZ#)F_@9Dd@ zu47Ihe{ufj^93|%r${RO{cJ9qy%~*!Cr?H8g^Meb{9|*<4Fi$OEea=*>upfBV!QIZ zn!*oSWgT_|N$do%?pUN$0&x!l`E)GybsCuk<@r|lVP^)Cx#8Kn6u~77Q&|HVLG&us zoQTP<{)*GjDy?O^+L~CLL1u#CIk~|!t0~vY$@?nCboOFWx_E8x;;Na`BDiGr#%L~B z(MprW_Vcv-5u6xBY*xmIjSBv&o3B{~`1SNMeRBa8+mZhgJ zX*m35FtuM4oM^WxG&1o+#*y{=W0O`fq#T1<_Q&qrXn#7ZqByI`;4^t2!AH(gS1bO9 z5X~K%Ixtyf&<7aFpekPxe>T~cKF2`#8%%S{E1M6y>vyG;ymiEp9Js%m9l_#vTMY

j*86YIEaS8frs@j!N2>P7`ZB#5h<>-Dcgzr50Sn{IfJCi^qD+&y$t8Z%gVf==UhC zco2jFq(0N^vLtu3`Onw>iG$*qO`*?d#ggUTgE$tqw^@yN`iQ)f+7^NJKf=sGos(NX z&A$f3EW($^`@eevo^t%HRuWfJogQ8h?SV6&UB>I5^+9&yZVc*Ewtgu%2Sy?0=X9d# z;r7`YN>BET(e;Qw$DhMHhX11KTb-j-z-pFQWVT>blhQ}u)aGi)17Y*T0)i&ksU&}d zrPp5WWH?Yia3s)5rdyFpb}vxPHHNO@OsaTvM7g-7O&?lCWg9z4w2!;8DjbvZ0E-f? zN0IA1irAu%(3t!Tw32LDCH=uu?dyi#ThD{!gohiLWJ&MYk9yW z-#+z(T^GnO0U_OXTv4hDBWS}fy=Ppx1~}#$+t{TjhM zhyYeUJ?zA?O8P<;TO*UjE@THvsC@XwZ2C)F^UL_{d-p`7ea1w?Y1u^^NHQJ&Jw@o8 zK%(fDb`J^vYKEGSVCu54azC=#KlYS#*;Z5eWXU)Iy%039*A|C*ZfLk zI8Sof9o)&KB(V=Br2v^$fCT0-&8z25gs~PWfF1UMGx?Yun-mB94TE1^D`*EH>^u(C zGYhI2BW?*R{LMGN0DJK7M6k)`#wAjnmAn`6t`) zeIt5uoPFv2Pjr)i#@m$bb~+f5%tMtwaClgace|KAAc*{~hnG1P=grew zW7Xq!-AZ8iC_nlDQ38i_&+l)z>Zmi3X6;Dlvs|@dt5daU<3b%0eZ~^p ze%3i%w||-zw56h5TZhT{^LLfbl1A*`JF8{(2f6Y?^GEn)GHf{NYR?5f@ow=VTRyfw zJ&g>+8XB4lO`Um~(jM^ygY0KKp)-fKHm9??^~>@47Ywd>F{SGW;JIaEj9eet94D+dKtrCo)@5omy z4do$PcmC>`5La{tHD}c?_v!#Wc_B)NI~R8`Wms}HP|#b|V$1D@P$}YV6((!(=qM+d z@lQN6%~6=Iw{`Ukkq7=RhZ28qX3ZH&?GQZtu|)F9RF?;HeWDNcy!HmeV)@Z$ zPEE>z_5>yt@RqMotCKc2Tbr|P#g4G9`MCO5h)wGZjCV`c2;CG`uE8{h0C*;4D%aPs zPf(84?!evZ>{2$&@F1bWdz4rwv^CG~NMaS7Sgf)kG<(yS`IzS?hiA)YbDHL9vdx1& z|Cnxh8p|m{r0wh-g#iWXnB)<0(RLa*H^_xU83O;qECcSl9_gSPfJ8jv>ja(|4|zgh zp-O3r&cZ?V-H^mS$KfHijf78hhAxco2c=P(FWd&11X<78b#DaW2ODD~>kNu_IP>i# z_fcT{^iUzzk+wq<(CgF;rVXWN1R!Z)aR4$W_h1>N*ol2S2Dd>`1D4u2(g-y+g1lW0 z)?bPQ!yfjR&n6fnW*AXW18Wk0{0-uoyk#E6CJ9jwp0jhcye{O z@iRi*gx-1V*m1|&%wllG8>J?62M_tgpk6oeyNpjoe2K=lHWJ$wDZF^4bCHxoOmHaX+z8+)Kng(Sv9I*h`~i^3Bmv3+b6$CG$XsN) zE@MT5eNN)|_cuLTUIkvyA`g4FK0Z+U*cbKy%=J5%m_-?JMHR%+>vHtpXOcyVN{n#m zlsA+M3h)Pqu_ab+XS9c~SV~N=%|~V6OgwjiyxGG6#& zWY=xf3D1Z9&c00;t2o_uc;a0}z3<+!lb&S26>S^uRIUwszNk(7CmKdf$GXiDL*QV6 z2eKeyPb)(~&Ii+{T>9zH7)xA*-&gi&rMWUL@syQgHRo%)d@a4A11`{r?47s*4V9n+ zbwOT>4($>vH?TGZXy$OefN8fZaliFPL@F7jvY^|=S!SYITweQ-Ms1+AD;f~q@(>$t zJz}+PjYY9CwQBiB-gec(dt@f6XW%i`2dy>MS^|lP&EKpn zrF*1M>LnT2g9SlbKiBsg2u zvoo;4NTCYcyv+7 zQH+0!Q)6WJSH(?+VSa_b&PT>cCj2CZ>wSBFflT@R<^Au{>0)`*nJ-;p zn{-5rbBPf5P5@8hnZFRd|n<)>4>YRuE@M`%YUM_4#@xU?U(rWBdH&wP@p7!0r zGwnU&oV3E_V4D+=Z0X!P|J6PwV6NKk_+lc}zGj!m{sAFG9o-%Nw{84GR}oDZeRSq- z%e+vDF(xLa;EuE=s7)-C1SVRiA4CI?>`3&qKQ zgagg7zRE&>F`s_>Z6tmRp8Rr=tsJ9m)m;t*iPD)fRGK({Vizie?)ayo z2LR8|w?U^<(Wt76atz8lkI~VtUHuO#wHubb3!{d;PWd00y+0Z!%wWYlCBnc$!z>c( z2;sp&zu%j*!O?Y=(nbyb9C)exHn15(%Si*HXoEzU5@P~66tjPAT6$ZSA_G7^agd-` zn9(uEOpKniQbF&m@1znu;4PtiHV~q)dcdDULSb^?l0jpfaBDB9`nYmHI~*-{H(;+d zqwqbVF2iG}cKnD92F{|U7}0x$OWv*JYXwB)rPZ--n?vAS4ldyZ(v$#JRAbFbfY@E) zIR)fz?2ClNKMIK(6_5$xTxjsgmtb!&*)M&9Cn3Vi)ftmgnIc(7cyRuW?l`TBSg!ktb`rk_H0$>* z$nP3k_Iw%t)N_yH!()DaFG!6sLn3|}RMK!0p&UOSOM{6~Dv9e!Easrsc~rg&@W^3( zL$$|k{VzPQtFXF+4AG1jueGARuDm@t504q0Ol0(ot92FN06aJ@Khhnoqeit3lL1Tx zF)4_g##&^ZF-QD7)P+}kg}I^>)EGeeIZr2Y9>|B8dO>(D z%)-4;Skq#8hM;k1i{{`8|ZS&+Pis)t*@rHM{enV~4&Vms6Qm82{k$*KAu@Pm1xGA=0GI z5990P5#2Nxu$e?UPwCXI6YT6wAMILx%Z$G>(Q`)BkI)xBc~xrzD0QT74NXe;QGU&3cBf#$jFdJ3I*dp(vi_5YeWPq zNVmQAQ+T6)^K)z)@G>u2N1l9XOiAg8%u_mdID9Fkua}$X(H5~|83HJB}!#9UnULlRg6;qgGnn{8M~VQUwbS6$Dq+a$~1Lk zV#RB{j!mp%JB0~J$m2s$V*zPw282^?Ux2=u%hM%0(K|0DS`=MR`9Zy(S*k-OeQ0dq zG40uNsdsdim~Yqvsx|C_Nt41VWkM4#gXx?CtSD19m3lzxV8SlLlYGn2H6|vgCV4lC zCjln&CqfredL0sZbr-tLsBEP7usa>}2bkejjBB&)+D$j7k0T z{2|u@=Fq#o`(F6+hr2x1jb>e?uyr_%4K-XD3@ILc@a9Qk(O&ndXa1GSU)W^a^dhW8 zwHxLyr=_iL^L3c9F`wm0?p3=isL=l0a#e42mk8^^h-syGG&cpGyju97k9!S)=%3x1pl<6))hGJ7}Skms+f6XXV0A@_&zbXqfynmiiP&BbIH+7RRHg$7!@&2E# zO>Ks)7rr{q=qIr3_Yv}1Q)_D3a!IK?n1?i+6cBs7)mlng)M02yH`lH#npG+;1Z+#GgNB;80Fqq_L{ji&c=T~<>cVb@+m+*i#XHa6a^@}Y_}B3- z%jwtYo~wMzJV+;yKuhdl)%YjcL0X62k=efUWt=6Kt|`FY10HPOqe* zOl`d;C;Jb}@XJoGcoFn%z2;m4=a*Sd`~fpg=z%+$OzdA<>iBX7is47E%F*+zhVt>Q zvB0wx^lUR-(>S%9V#R^FJS-gZfqs2`OS`H^*3CIIOz7VeI*c%|e&?j(;1y1aLUBkg zw7bvW&{J8eH?(xOn!fr&X|#FD;aG809fA+0B{tLS93{E~KU6=FCw=Cs5tlbiYf z5%!irZLr_EcU#;`aCevDF2&v5-QC^YA-G#{ceg;0;)UW4#oeuNc;3Cw%zK`*=YKvV zb0;(TlF6*B`&!raTl<^E!_Wa6jJQjjr`}JoHkfC|rMmSBc@ulO(>$}kb36R+Q&Mjd zvbZ$SnN|I8qwu1l#?156A158Xwr zmTb2N-%b_^tZ_oGu!)vOjllZN#i2em3(Q;CS^T=a#sr@NkW^E_4W*@cmhZtr&Q4r+L#BOgmDH|VvWQ{l<4kn+ItMe>>ubFO1uj|Wzc5b*Gt)LEEs$(qp zCNZ9IV1S+va-H@y12cC_>y!0Cf(}F*t%UQY9LsKhjI`@KL?jL75$uJxF>@Wbs zUeJy9a48(`MTY-aF`@lafX|{2;orL?6`f(4%oT)%6ust(=R*(tMNYrfKQMA-TzOhO z*_ry(qGUu3&U}b+zpo<+Cx8dHkiEB%HT?dyRF0JvQ@7FNyuW~)zN1wcul=91A_XW7R!XOk zptL=n7(#WsD`6CyT*$JcymVE&I~%f}Xmm6|Y>h59MRuK_aA2N*KCZxNZ;QQGkyf*H zPti#U&=zpuV~^2D!5m>lsdL4~?Ae_r**z8$QIlmHNEqYoWzYo49`@as9+Z;b?p0hO zmE&LXE=pt#^w<#%VaxU8lpK%p2w}@P^iOd|Ba@M8JqZvHV@ENu1IfD5{)~+xfx9u3 zxgOh$WsbdJJ*u0`=;xaJc9oi1^(H=Y6Rl5Ab8!>T0yQ@#;ei<2W}_b&Ve)r7Sc~2o z4@8J^wYckd#(sv@Yr*>@;w&?Qu6;z{kKRw)d?_`jhLF#)+{5%(A)Bumjc_Ir1v?$1|*HHO23Jtn+nLij*GrgQuKY2*yjZFN$sVl zbjOHgZpyxni@ljs1fcO{_GLUWkOou%zk$-9Fn8LbUkRjpL+4of;vUaP1KNNAtUp=$ zLLPHT-vfaLARSYGtg9N*_efv>-3H~2-OvT*6RY&wnPZ)I_cRq(T283ay?D=^sv>#vMKdiLi%0=3?Q;kdK!-m2pjGDvA3CkGWzPj!%F&& zLJ>eGKz);rwn=gQu6j)biZJWVeuN->|3(pjXP^E=7ul!0_g=Fn7@Y9)Rc)wGc?(Ho zY%_@0?9F-yCE7O`*oQ!r{6x5e67?!3{kHoUI`IH_r%l={+7paY81p5%s>6IzrwHH_ zV7OWQy7}#zK>CgKv3kNHN|65g_%#6KJoSle2qm(c=Y2EcrGaNFAu)cD7H?|ja3UF7 z_Mv1hopD#YA>VdpdS4OmZm^E9|KRZL0OBshGxC)3`3>s$Z;TjUtVFaYy1~zFW@}$Q ztC_+dLUp_sLiw;Ta@*s-mCuOPOkN#cwD7Z~4AKephp)<*u>J7hdXaMB(x@1>SL%_w zj%aUBWtlJ^&ZM^fHH|PKr&qQ5FoSD<@RjKQLlY?XKaC;O;XGA;W4t+-13NNhfg=cz z0N5^EVH3!7^~h0jL?)KZDAr`jdXC-?Q-vc7nnmH~^aVY~O(+jBCG@Zn+R+`x=GFzq z*4CD6_XhWtm=blLsazxSTR#R0qJ!7>zZd)uYcEs0H^}~tHs1o1!9SFjuIQ-z<2Jc2 zJ0UK3otXW{_aW5%56EwPsNMnvc`m7_avqz8C;;go zqNMKlB@kR|p#ef86 z`XtS7S6m_yLuddzZ0RT}iacXY+6JW^t8n=!3vv!-`s5{gBNkUF+oYwR^}5E{L+;@i z0A6x;mg*Ewbz>|MIpek=7eook)1;+w#q=erhJ2&cp&P(ACVZN!q9GYX38`-SEBHa{ zC=CjI6E5wYz#*Y<6Z|6af}!%1Kord1tQb`t!vpnXe3Wg?Q zrWLE9CM5&<+)M^Vv<+D_f?<9~MCBKU7Q4`71r*iz<<#VJ=-m(pdCVB6uF zx<3off)H1P`?u|ppkMWx=Z!>_$A;jwmG9vJ-bCNiVI?@C|cJ zVj;XnmUV^vqOuFDr5DRA!-I(poh2eD(8X^U9 zUaCQUt(+J12>;fF`G;)$$FoQv|)j)Y0mvq)K(=n&j3cs&j|x zpS`_p*1bk*I}~V8o_eJyD5S!a61Nb5UJeU=$hJWd7NM0S{R`EY4nbiK^O~MGJN|wS z0^SR>9&zJXTAVpN*uf=&0FmO&7|z8XpmSU4Hs77xF%E^3Qn={;t>fm z`O%IWPCX7()Xtvfvl?L>GRXm2(MPuE26Hj#dvlmYhYn`Uu!;O;<9L2+U&@3 zoDP11T>DCF!QAIY2n6yZ;#DY(5+L{yxIIr|FTuBGMp6`olm$~pl-8F zU?(B0#18nHH*y1}56r6$xSHZ4K@7c5nh63H9DVR(wzPm;qTLy_L8_clC#cfb2ZK~5 zhQjX{5Q7VE(`GqGwphJW`7Cu^qBYobj;{?A=b&uk9E42_H%8s?x{gaE-#L=ut|3Tf zCBtirw(Yo);=kf+xyRGa2{(vhMIqgSHQ-g--UARt`+OMPk}h~3m!oP)cVr)$gR`L{ zY|_s5+|UIpo89)@$b~2yW{GcTAC)7aQEPf1jzghwd2}C#BYWA_s3~p|nPa+fdD0vu z+s`)7;)XOkeYyMR{wKx~1fsY3BPxD?0%eiJqFR9kj+=t__cB7Q@ERww+&sIx6~qMT zlgb+XPqn;fheqBuj~<+P0+stWG_pD@9?*VB`mzR1R1-DZF%kV5k*P`Vw14~PGrfxHFc0RptcE6OSKJ6w=E8^Zv@y=Ze!AIs{`zt0QOYd$aLHC zfb&McIi*otYJFO$Pj#q|RQt4adwwWPbtsIK=cu%2MyNz}s2e$-H3gqI$eV8436NJ0 z;7Gjuk$Q;^hgTC?CgnLSy(9`UryC$Qib<_c4YgATxdYra;drGy7o?ZOK=yRo+<<#c zI3KAc3yM>!Z7;fQD1d%5K%aEmjBXnPpx*+}C)*B8b^OZHruMz+&%LZMoOH(s@D;jY zZ0{+5E_5mW!-Qr>N_ok^p4@_&^-kHFn;!rlJzuWU#(64w<-g?RKU@E_NpkXY62!BS z?P@)3$_)};DtDTR4L{m-FZfaDgomGvz)UL{rWp#@7Rj_!X{eOijdYm(TsZSUQ;8&~ zShcYqvJ>(2nl*Ye1xC`@=Avbv^%u1UKRK2>Ds4MfQCl)U;qa39%{}B9Wvg(W8_mnr zWpj_~o0J72ttxdwQ?TF5X{-!AD{QVKS!fI*r<@gwakMD0!+bSTjY5|FnwufpuJ4L5 zr4%Opn!&M;mr*$NxjUKnfr{F#pK>T8fjWFh;c`w_yEQ>gxxVEhyhp}k`SzNid?{F3 zjJ-=hSV?cT(%A1uQ89WA^vTx)5Oc}L+ z+SM*w1yS82T=b$%$uk=m4?Vw@`)-;SJ9Fd5qg3?tjPA26SV8?2y+&71?P?DF zy<5F7KaLH!G7nd+@Zz+RX3~f3yVp#qZuC%c5y(=x8E)rCIIN%}~ZJg>yCF zD^sl5E}S7x*Fw~TVw}Hhzt`JqA~-xpPWRcm3n4!7zZPU0BkK4d z`|TlwB1{-jh>fpJmuXxG7m*a63iN|8#uNhRRZf!C7{az+l}`bwI|b#K4lz7ey&1H~ zQAc9(%uVI$Ir{U&`5`9KeVj>-w^7bw9CJy)2V7QDy!<~Z=rkXMTWG&L68t(&39z+#w)j|AtqRe zPBxCwv1QFMv5uitqG{#itgf4K0m5zTuGxtkrdd{5ZvGPfq8e7yJzVIKWej;&Jr8Rx zAByHp;_c3Fhv`q<3Fe4^Xcqe-^Z{E)GaNYIpJ`&HT0pCcQO#j#$Tri_a&cx{Fl5(z ztd=jK7)!L2d$kxd2AGYvR=l3_yZ)+XPg7yK25n~`9s8nxpQ>Z>iiFro(h_zEX=3MV5nSK+#OU>#s zOHa6v8wm$~Q!hh7ig&Wzv#rv6)+)9!VPH8;l32%Jh5jGig6)#N`qW9Qux*JE5r{ zmU$wE$ql}0RVx07Y&>`GN$ap>ew^iut7Y;1-1IIGIT6+$t}`Ksg%9(nU?ARAU>MGy z(gc6p&Om(g{xM7fVZqlL>J3nI*KDzDM19wo=?@Rg4EU+iV%KE1WLCF+mLadWY?H|w z3vn~qL{au(fPn|vW`Z&T2a>@hWhG`^@m`&NcQrodF-X7fcGvZrkv6~G;>{_SC6i9eGRVQI zf?zgPb#22#QX4dy!N>-lkfQw)ZnH^RoS z9%cFM5ml+d|I-B9sd5{;?t9+BXI;UG7Yt+6$Y7>>DkIS=5`p8{&S~bL)UrelH8Is` zBEw769bJ6jd|P|DvlG`3{jD9zM-L#_Z|F|Wim0*|XJsVv4Hh(53|*BYo<*%UNFFw> zbfcesXW|Qb!1FEIaivYvSSahI{}cf6e_C4d>&imBKIR*lA68=G|4=IwE&n?opr+%p zDvB-;eG%_$ntK`zH zqaE*@yaAL%Ki3vIj~D}b0GfGM`)a?J)E1;sGKO!O9J%|pv32Vj7}ghL)YO>}ldE(& zk#vfj7E21*tkp_>;F*cp)zYrE9Q&sJE-(!_3#76xXFtVlAc&NWiqg|y*R17EOJY^i zX>+M%A8FAouDm$i`&qgV8@x8Z#|Hc8PjD$SUfNRC?gblJ`QtR} zBjalYIrmg5LlEDomPoppMy29cMAeGetq?MwqKrb@$L|=Uk4mhnfvs7 zZDxR%hINKvp}&y;Q>>1ZifHN~s4rERblh5>n$O(7SJCUSeb5OJI=SkGyv>vgIJuQjwbTqRJdMRc{#{>kj91%{^v<#YlPJozmPMeLg)&fq3CTlfJJLQu{ z*Oh>nYF)viV{GzTnb1cmqGbu{2x%qfF+8b90KK%MXiNRlDwep&*>cO_!$wgoo=x=b zhXGs|F+qPCQK{%?{wp0EP<4d-9M0_lGmi`^kHs8QkD-abP%+jKXGoSk zD;)_(xouM%n^R9MQo^-fXL#7`*Wd;Yrdv~Dq)co0JWHOQX#Y?AfLQebTTGIzAxoy0 z$RDS5^DYOdV#w6bO(##d+E;=Uok5eBR`6uYgL7>Dojc{51)M=&b@2oi9q+NC`HTH$ zz(VO<1sU~xL&`%Xr&L#_HYo)xuAR8NQsz2%xB6|N<0>W+m zWFz>aY>6QKKNEkdM$Q&y|2qefHtGD4g9!Ao`>T1q(kN%lGz3YtCCTy=c!}08KICM& zGt_Dl($b|{yMft8QMyvKCnyy0E>r?y9C&!}0%vAMXp7fa^ z-WH|ry?JLiXEy->PZ*yz!lm&kKLA1YBSw(+V8buxj#oHXFS@={U@TqVF|ZzE?@q92 zA$nLNbf=Zid`nb|y|!uySZ<^S7ukrCRXfeg(J+M|eWS;5cImJ|XTi|E&C16-_dIig zUd#1&D?x&1sR=9L9$|-qws@624#iw8Hu(&0+ll&Ak(;#-Ho0js4`uEllPNM}sJzaI z$PX$XMBfCfO59oA#>P1UEa`Dx}dv)djvbH#~bd~^=$I&|SZBO=NXrHh; z!R-sTIaUUf7`DfAjEPFQ?$Vh1W&bm26j+o|+#j-W1ifbC5t~|=Aq+@fHuoVLSXf%j zc5~y_Oj)vLMk6)c7|1}*u?n$U}v+%nRUW;Z6D^~?*4F!Ys9|BNdQdz zMekKtGt9K;DUY8f?Xg*}(z*2ZhlDKSn+zqPqZ{^mDKJj7}w zH^+rHa)9`zZ|L|1Bja!Vj+iR3Qo@dz!4*8MJXCa~;-}ils#pUt@X>c>lwty~O=Rgn zCQDA(;@?!x8CNCLhqb~M)Ls|rIpc2h!;K-TvJoT+t1bE%xGOo3>NA|>Q5lo-0bHy= z_zcpmFB#_XT)fFkoiDul@te`#~#(LV|cB2qceE2RFL*o0+N;qb;0b`*m` zQWn~)5>iVgWXLpebLL?AYhsmjVmsn>7l6DBI%=FapkO**M|57AZLFVp-9?1ydim>bgeT=oX;eE64L(AnEDWAEdpR{RmpB>fNFm#Twq- z#y=_;dXHTXy%j0%&q267p;4JMsc=YG6g;%ur6Hl#C<O3nUY+67+}a1U#717Duzm@xmkpM1W+!~NNU;QQZum`~l%L|7j|{qcba zDC`g)FQx+VNo(W<_(UZ0g^H)hte1J)y@I?D=#ZQedn>0sGR&Tg*sE(+*(Z|{pkGZl?R&T4)3c3a93@oQt(uPbB^_x$ub=^bO`8pq zrLNkP35qw#@jbLJMPY!sh&jxhTnZi68CGW%j*BuTxL6@|IXDr6NRc&MBLzXj!UXv6?iiMezPjVMI=2_2r0jlLrM4OzNQH&Z~HJ$H2I|@H!RZM_Gy?VF{iQ13nS`iUE?B zLxOXWbE1%wMQg`;aTC&?V3lv)gT=~`ViE?|J7^?iJnx^$vZ%>S`1K(r9-#0(%%G2J zH(0`jwcfQ9X%rM}3LLd)4z6LEX_tI|INUpTaO}&ckvlT~q?W7@Kx3g8pf!HBbJP2+ z=ka0pROI)a4OjNQV33@fWZ9xsWeV_#>KyN3kWKrHwGI4Ckzwd)i=yYK*DC@x?MKgH z*`jAZsOb1qhMmJ*9eXtyi$z?^a>)$s>ps_vB`})Ig&R_=IQ%UxVf%q=*lUNCD~fy# z0|rul=Yb8Nf4L>bg;rpZJmUg!>l=#cmxB5X_<{utOm|}6CvJ3IrZPqC1^}4dmgILW z36+-y^{NyC)dC_K6!O1YWBn3*p{rW@6@b*A-M&naF_4FPxsA~-e!;8@gNE~?1=U(k zEg)!OQ&G5s{^uWe5)sw}ROu%mMM2r?=e;e2<^_CUF1(q+T}L&(xFf_s_9G0oC&!hH zsWL$TKF;KG@>HTWCCd{}H!2k0K1@o@)G5f5Z=%j=lCu{y7KlNsc#)QG6J8<70!+Yr z;+P%P856&DBHP@~x{$#3;+>TwvEPdjCA%QUqZx{OjkNK{H8SOxm1MTxTNEX88556o zA`95b>J!KIT3n38lNyP8%{C;bZhpA*SiN9)Oy8$f-+meGqA)}~37~0DAHN(p=;y26 z?HY*FdHPq4;3;bO`T3*O!TyLtasG!I@n4|>Q8QaBJF5?i<^L5qP~H4kR)YSeeLV!HMYiP!b?)@bXJTj7~mRJO3~5 zE7`ND&pAc%m$KxHy=t?Di@Qx5RS^^GF64y+OCeR{JeW>W2kC!t|w{xvSBm`7p?8x`ZcRWLS|n+u7FR^*?NuCDn6Mv_+w1}j1JBy9FiWWdZsuj zoou@GWx6Mw9%g#HQ0cn=i%hgM4`cL(79^9Yd_@ybHb=W4q}=qblNqh#TSFStMeBVT z6>Vg?LtWV(@q!2upuu>-rM~RC|LWXg++k#2XL{yH`+81pFs$mj(;Y3)i+E`Udw`Rb zfR(>73*7Bk7`v~v6=;_BJ!B-x4fh&B46YEr@x@O)Z{f@m*?3D!PO$Q(W9}h1jK1Lf1$3*++xkIzubhjd}nd~QkCnA@00^6oI?rA z9z4FYK#mnXZS(57;qlvv;1BxsNAeLu@nOs zC>rYrRnL6Z<)8mc7{x^z>-H*y%~HywSA3^w_yt8@h}PO#l-DP4-$D|;N$i&Zppy>LYNGx5Kijn(jZAEeEX)|J?9Kn1 zhZL4DE)5~{Rm@>zB;)aW)7Q1^5-Whm8EqmWWN?v?6BU1M)=~oz%R1SO9pmdKLFpVE zp*5`-TKn}K!sDBhzYzTt@c?{sF4IE>KP_tM7LxE}jSBs}EXL;Xzm8;^*z{T1(HaYvvRxSIeT)Ik<`sEkKk342lhcF=^jV?JwnNi}U}o*b zREt=6rolg}egR{Q+3;C!#}tc|4VGyBG_qY8Y`Iz1;%%M%HDy*fXrdYH__ojub)7bt z`t8t)Z9gF2jLRO_kz_yV(1=vw3D4wusX}1e;)ln3ce*%bUjq!}cZK6W{kL{Tq72zS z|Ixige(3Cs|DiG~x;Z;KxcuKL`+sLtGs%;gD7aG4wZFisxg^PlEXY74v4sMVegLv4 zp0{vIlU-NFWOJl6AJ1b12Q&Ro4%g#3Hq{KO(B!FQ+|yf6rv( z^}Ap1Z~`A@{sHWUE7Su9d?CTufcAkjz+CmPCCx~Ecs6SF$X5}uVJIAx(>E3KtIXIR zu~zQYKQoe;$K&x(ZQ-$NZEJtGazwAD;!0&!p0u!OJTMrnw$+%cuTWsuCz=R?Y8Un*mT5yBUbAvyEuAT@n2nVXE=y2C9-Mx zw&K2g)bbGk3%y?o%FvXbyIWZk57ezm{i57>hC5&v8JV_VMa8;X`zbYI)^^>>>aNy; zo8e2Wu048H+qm23<+>@LLIxViHA2O0CVmOfCjtt76)j%V2A=xI z+a#M`8n$(6v>~VKwDHX6-n~P+6_8Ati5r6I-Df3Qh%cO@Ek27ys$u+}XFGp8-`Y=; zm0t<*jlKHP@|VxgmL3qY0}Bv89~|qgxAzr#dERteo@D88*3|nI_A$p1(W^&3WObz5+sqW8B=D-?Pkry_S}(`frLt zq5m?d-8+Pmp1Hx)&NMv=%tHf)TlGn9$_SK7CgK0cpaf44gX#a#)^AB|03X_Vw)_Lld6-a}`u`f*q9S>w0Ia=@W33Y2ir$D+vR#Y6-yphYP}oYN7QQggAMHDh%$=2 zE5E~>o7@ZE1I@QxaNYDLU2vIIxu?MhJH1uLFU9!}nbnvEZXAY(VVvjZqvBvRG67Ku zQ;x7Y-#_R%Wo(7*94`OrZORa3gBq3z_27CdC_ZO0n?)Bqq;X{7gQ_Pen>i~;qnHu) z^OoFBv>F&0#O29WqArHAeev_OgA_l(NUu|9|DnXHwOwA{#T7fRg|w5+7SBa%zm4k; zf}4yns;sdDe&nEE#Fv)^PRlC@Xcb{i!iQ{ic4WOisi_sPcE@!lG4I2lqa{i++@FSj zuFDYBG=jTCnN*zoEPB>goI$)f@RS??m*Ro?9NsaGfKGaIiuw+X-9oy4UL4i@HcOcg z8hxD151Rx&8-C?QYPPqP66a-PkBh>41vb+YQz~JpT)#=n?t0z&8Jt>0ci7 z!fA-RJ%|T}@K=EYPP4Eqlu4MePx6?Z(=VpEXV9&vYRgxrtCWZunVI2kq{HMl;*-(B%!}I=vmf+1ctH_f`LiC8c>qB>Tdix%OWt?1 z=4nBpN|u|`PUWneN7Q82WQ4`}YU#kR5y|&JQ+2<~T#*0s%XHbyNtoa5ySz}eEF10T z=$(Mj6zbU~QZ@ba1?EshC;TM%iE*C5HsoXfpl0Z>hK4>^GO>j)d7(T#4%ZY#JWD=VOH{TS>$rxu^vS(xR)A+N~Fw# zddE4`LyF<`Un8{j@ZaD6d|c2LK13Xr|Ih}AIM}kWpVA{TXC~pIGv!{E&Oa7cp&`nQg@x9?tm8u0TdcMW4)G&qtXBchaOZq zhOSXlEpdq!WW{+WH=?iio9-^$jf%T; z`nTTn%55~tVvX6Hl@@o`Q3*LBx^}I_VzpV9RXPFPS*P3d?P=#~%Gc?dOuYTlUM-i~ z#iN6xNOjVezd3u6G|=C zSeLfbg?O{N?#xZ?%}m)eiMva+VmLoB-LMM}<6KF43=`!B+?tFQ%0cC@jQcS-Uy4Yj zF|MUftJShm28I%k%?bww)Zm#P3N`nyte1Fkq70_9D%*8}|HLLmt zBVmv|N`mS4O?^v@(NHLI9xY_GMj9$hlkHV?UdJbF)vMiVgD>{II%IE@ylRjJqK*B^ak9@)3=rI zTSOmyvdauUI2A(6*){syMofYA;P=_U!^ zSKn8+@6X$Hg8HP|(tHxrb*?nTfIEaQC@-@hX4@HGe!$9Ljlg{|7L@b^nq;)Vr{cK> zJW*3f@}sT%p~8a55_v5kDOb>@0jcJ%`M}l(?t6Z*I!61%i=AZU78NTR?0$u<_HX{5z61Vv63`*) zr%%^p|M~8uZ1z9$eLkp~DrO5Kn=Z+e4s*ZsHyST^OX#6~YSSjRTH#411Am0=1Y2db z2C-F0!9#g~ksgqsoCXOnwxfgFnRln!W2d-w3yOCS4-r|X4iDS6V*L+tnoFM74_<}^ z6sDivH#d7O{L)?0@!!t3RD>e|HS{`~+ZxD^?A=xo5blq05RKby=o_ThyfEQp$ewr` z4TBd1pwAc5j*_Eg_Ya4Wf|4^7w^UEPkQ9Z1dqfn7iA;%mFaT&NY}soOI1zG0iYF?# zL2^W@r=(C+fH?3~6GRPHEcK=Z!iF=EdJ_Y2!R<;BrZNeEM1cXROj00IU_i1WwGsT7 z0y(-=hV=CXpj0YD{+bA2E=f)KR1um9Fi(1w1=+$;kiXLy#fNr~3#KzEfP8@llA6Ru zBx3B3>EL20=t-VhLQ4QS-X=^AYoDs0SWfDM2?n2%yX-SbuS12c( zVk!t{Ls3nZNku|gm}}Y_+8zoiMIVj~SC(}8jf93o3~)lqP?&5Q6nghzT^#}(0qc|Y zzQbjc=g0%9{vg6x0P*R!>ESGrhd|sCpbqi~X)XoO6L~{8hfI6mo+BKVbbBOu+8h|b zNW!fQ!h}l)4*l$u1^pqHkZBhNrII&{)&d>b#~J{&NsbIs)omx_x(d$Sp)~+|;;0gj z;!rzsD(NN0ZAzeK7%%yntaD?i4*8li0O6Vgt}Qu-)Tk_!SJK#SgJzo%s6w&EuuTZm zEPaq}PuO#X>mpxM0MOlkhC>0;r`79#M93RFA0(G#oYNphK!fBB@efj-p{7xK*$||Q zT12-sG+Jn|JRyu<#qvZOTewiA#Zk;wVV0k|E8iHq&4_4ev&|QWMAxsSv~S+lZWq=z;al8g@!8yF0k%Wnwg1Ms%;H z+|S{=#=I~lxks*~@#|?_wCh3>a!LD^xS=&`1l4W}g_hB!t0DyRzx*0&4)A@#nivmD zDSf!+g+ic=Y8<`zGC)b1h@pG}lwQ7k-U>D~<2o0}oz@K%5v>wCxwEc62t)~q{#4$! zLkOLdBqT(!jX3gmdfKB*&)EKO6Q~=Hu}O_9Cqqpba_VuW_1!42QA;ch{n)Bp zMy6+#g;Z+=g*stmJqT4@{s804WvtWAnkl`dc|+Tf7ksX*k&9OrOJ0Zb@Ud)%ELvrl z>{+d|^7t5noj-e~j{DcI*-Yz`Wdxm))uSu5V$1k&<0$pDWNC$#RT&>^hxiup-R7!= z2}&JSAKMydpZe$;hFRM8M$3HVB~TmT%po(cS2Fmni2`RGEhJowA!-bB=f-X3P@`{F=AVv_Pgu}K&m~=5 z-Cd`H5mGclWRlefXWJ|Z{OCAh@+C8#xFL%QrqPZc=MkRyO5*E+Ck=u;V$sA{Vm+i` zL%|n1QOuc3-}yc2+*q0x;$;hG=x0pf!%BmTFPebMe(Fll*1m6sV*Ku;Dt1w(JZn;2 z1m!M1J*wsRsuFC32W7GaQ@hC5{KP5*4VGCKUwHe77gDx9J;CV-r-MqLKh`jFu)CLf zX4?f7KjevS??5jZ5(=hzA z-fh1m6`7}-OwVRq11k>L$XM>#rws>zW!VLcPuep#Ml#P!Bm<$>-Yh&4Xl$}@delY+R>!m(8k}w0H>Gw z35U$7T!3`^kD~tzp0XCiL9QXwXFdmNL&6pFq)igTC_dmD=*;yS*Qz5y`(YTdz2+%J`n%%QZXZ|` z^>4Xhec4T6Uy)=Yl;hw6bzM_lwgdFxuTc-p3k1KILBoGVWP*S|MPz{pK}BSS5J5#` zg&0JQWr83>jb(u-MvY~LFhPxFh1f-vWr9FMm1TiQMn%sGqJfQ`=vRfDo9K6iWS#iV z9Gq!xh#9EANps@h0x)7TsU<6f5~R&!KMy1`>r6bcJv0E8 zXQJN<7HCc!Mr;SogIdiAf%nxlGbj$0BQt0YHOB~gO4Kn8hYN3j+k8tG@*p$l2Gy1W z0*knP22nS3%b3*>E||f5OBpgfGiV%D*9f{+)G-yO9e04;d`la0F*E1{)s`KClem2r z(N?5>9dRwxFaoC?XW-*A>X2=jL93{zY!DvA?Q@80VO#dBjwr!Cll^|M?wLU^sCTK648o5j;s~|Nsrk0B}9}l@0-U5i@4hDJqE=ld6%PN z8h!jnmc}oAvlW{oLcQ*Ijn4(>wIu*ptx;M@*G;{MvNBVXze-7yO#N5|{3B8r|JBA$JNg{KPPEwJW zVr)TfF^&*Rh(5Igam;sGG(nv|y$z0p6*pM+jTYK7_!bJy2i-SP&*-BTw`hR5=EGJ_ zdTyw{K3xLzGt><+r$zd=C%XI4mfcUG!hPfXBp;!=KlZM_M#E5?Y6vh^$I&hpJ)dED zp~U#McB* z4~GzH3_jm{@&zP+Hy8|T)S;+n-=E?|?OE~ns6YD)+MUrc`8`#Wpcql@ zLcL(BpCnOX)>6bW=Lwlws2{?Mx?hU536WW!I#JC+LqdbY;z&8y3*-y3Uo}shAJ9*l zsO;A%?{eMZNEOm)*OJ3LRk_@$f<-{MfKu+*8~we-N%B1R>9kouo`6>Fv_I<$!mY3y z$xFZ}`pmUgJ>Zcj{@KaX^cJoZjG@_Wk0~-0!>7w#Gi8{6c%v zzPP{cJ(}x7{xC29i?DZ!uDoBjKT|PQQY*Gyv2D9z+qP}1V%rtlR>iiHifwn^bN>5u zpWQun->orzV_kkVpE*A><38;K?cwW#Oiq!FX?Maq?u(T(`D^>~R%J@5=knIr2d{U` z>fndZ!}lY{g}^h{dSCIJl82hDvCZkjiqMry!_TlTex5%*e6L_Hg7+A&foJ$jQVYmu zIYys}UE(|wAHdg~W8;&)XSy>f<*F;) zLmw$^ik*`m@DJUyV+++Q-J>6D9>Fg(ug%M!veimr%iR+nxEn=2QyFFDnI%@C|9Xy{sWYk zE`UeuXC4B=NE^a`6{t^sh3AH_^Wo;eg)(Ez^9d*Wm`8z!2I*E|6REK9_K3xlA;stKk6dGSg}3!Gv&;l&{HF!0eZ z-7;(=^)IFYUGa0sa;UjK;e(-uSy*7IB>Xi=u}BDeP}w+eN{_P_IAy3|6_!Fz!!R(G znvAu}A}uS_;a|26v^6NFAJ9zw0e{U(mO{8WsAL@W$e@h9DKfc&?P5#w3cX<~W)XG`H}rwPCv5pqOMe41+SdQ(>B*azD==TM zNpdsTYLM8XCXKEd!C0$Ijm{Riuq#`uvL1_quF6f;6e(no;wFHDQXSF1D`PL*K^znD zeSpCp1!tEDgRCor=$hvvsCj2<%jap%9^~T;D~3;fXy@<)b8r2olq4G4YMi8l%BnByL(#zq1K2Ij_=hVojq}ZXICRKvN^LhR|%@zKwTIaJGoh zPrQogCddUlWj4HBGx)7qGo)8DnaFKQ0Ty39xtBb7;VRge#>;Z0&^(z&qsh3vSkuKk zkOrzzKDmEYcnr}x{h(CEfTQ@UZX|t0(WI=*TM zC#$cQta;0IcmLo^_MTpa06+gtXy*p`WsdoUm(%>hG5jYKgQAnZ)Bk(guKkb1zG2v9 zW01&B5fGu$fE9od@C1Pv4rFG592+oepuznmfs;9|M<&p)8r%3X`w{q1OXt0?TwYP7 zk@L#7ia$fX=4cD*eDpiH4(%}8`?2*k+wrnBL3iuz4z>rz3oi)W0y>fcf*zZnBZ$y! zU5Q;0KE4$duYksXM|zQ-m4v{AGJf`nu00IkoosQJA?^f+Nx=a zVVb&KH4$gTKD+_hPtxyx!HQXop|Gy4-_k=rvEljcx8&7?i9^}%AhX4C)xBhkQx~s= zX{sc*(Q-?;@R^p`pV(hjZRr{+&Dh9DFMl&kgsROUHWtz0X^hj+f{&8T9n(miMYN-< z7o(o?ZIbs+II+9snPyGu$qL1Xn)`6EsM^Y~kce8HJVe#SCkm_nDn5^E#S}}+P_6Y_ zsB7C;ibFRS)WL6`4aI~m8fu+$?De+fps?-$q_{1bq&3$=U zQfQ@Qa8J6dM@Ub)pGq=8WMC|Ixq1bB47*)R+#ek&DkMl->kpszU4tXW(ELx+ zG}qN1bIvZt&Wd9!mXR7f)e4?J(FAQ{X5~b`g6-RXUE}IiBv@cAv=94movX_h&92Cu z6>X&zpxVyj?N^%krLdcwe<*6i1C@>v7ewo2724P93a_$e1e%2qR8Cad%OuK4^sx`L z+Z;u-FGxmykrpImkpFQW+EUp0PSm=BoSM1asn2psYOQiOyD{mvr={AL=j$8z{5tmb zY~74&`RRx`j*W{-##;))_l?m8{Vzgv#j z5JN+mMd99}zBn!V&O$=-@9S-&yG>PyJ)mDd%7JNUg3@Nvj%iZnSB8X5aZ9V8e)+un z`rRNUW^$Qz$8_r;Y30D$+5XxipLJ;y&(30#tg^<^-lF?kA2D`GnRa8l%t8$tvnF|P zZ-yew3;*{SISIG&SXBVbFpPB(gX||UE0SW#8vBUY_t5^KM?=IoxhWj{J}A1smu8}AwMwF&VMv5=d!k)7Y=M9tVu?=Q zI7lHxwl#B!&>*s_H7U19hh<`0&zN($8B)Cgs!QNcw3;>YOBA~lKOP_VX1`9*2Z((h z&x=Y=TC`z0?cE(=P#!yb=%+~Ss;LTOu} z%NlmO!JB(ft6PW!Bm5;x&fWcHvuCOHl$>r;iEFb1#^6JtBZ%&fA8#-@1j-Bf@o#NG zbrEe~bYI9rldo;|KbcKMtc(LGqu;> z<~*O*Jl-xn;)5VuQ3QhH6(fqtrzyZ5p!@)|#eI7$G+zpuzTB`ZfE|aWn8Gjv-A5w2mu?O#ilGV&xfP;}W)qJRi=xoh~b9 zaA`p*k|XC=%XdaV^0lH6+X96DdNK@C@W)5MscS5 z$OpE1IR(Emj1uH)qNt4SmYUAe)AFh5jrmxwPJ(u5K-+ zQD_iPoR;=2QdnvURNl-)W7_O4g2pXVi#rmISf5dYhED`^kjr>dhNahpaK-<%VLk<| zghiQfT<@p7m`fh|0P(!ceHMS2{&!nlxnT~h!`D3h`~Oe7jmZDB+bI8+lWtSU2H010 zCs?ac0f8N03{KYwV?YUy_GgeRtUX)m zKiBsQD0{P(Kp3Q-ek3$$d}-@@o8|q$`~04RpMyN3dB-mCt|UDz`m3#US;+g2;d=(m z3yH*ah;d{n6gT(&4&wJ5vZH!%@2a1I&C-<8Wz*Y#kgequwau00QTyXLf;}RJV#*|z z&|sq>*6X9aMVMUY?`M(o#@2BJMPaKbqsy0>aSD_WZ;Q<(WU!KtDVTSH=NCL0^OFJf zzb#~{+mtK!sq$e-A!^~XOUsn67sB?Ea|79AW#;rTU9Fic&0}_qcq~;;Ry+omHz$*6 zHjD$#?&bl|`#$hjGpJ*m@;n_Uy-FK(gL}uAjYr82V#6MJ#mWV=fBZzO3Vu)j97~Iw zWgW+DnLWq+OX^3e3d?WX;BR4ENfvx=H_rGIJC1LwhLEwTL|F@m{81MJ?YE>z1xNn+ zwA=XX4(M0Zw$uY3lC`#}t=5OGJEZ=!sp=;>dF~pTZd(Y=A4H`bP}QtT9;@{=ptF+v ztfp?trb*AQ_C6aE5K{VS1(6C3_^mW9^EB)DLHYi>lPBr*jRs(t5;_m{}h{GcRWVRw_38FTl~=7Ym&HtZnc|N z5jqZ^i(XJoO#gt0IYsXFfxQcOhzM-!;ox&cv9H6}i@D^>;XVHg#?%YCMNZr)0!Stm z9>^RT52Gp83u+ec{l(X{o9Jl31%Byql|7Zo{&TO`7Q*`TvnoMktkJpF%x<AP5L4u*UJ zrNXg{(IvZ#D~l<5ndA&fXMFxQETyBfTG!2{MsL~_luqdaN~}Ho%uwd<$js%_MXZAn zs(flDcRxPxUS>97B5oe9x*Z_fX|eL_Q^jgH#Sl3!ry;Ohj)LZ{+Atv01}Vk~&TS71_zMUk6q9LP$`DXY zeWfRE#3-rF2;|g4gEz^J|MeW+hR0nnLMW2s5zmD2e3^Je#@iI-tDdg}CTBax^>#DU z?gR$?_s5e~e;Pi;1KfQeEkfG>;o!0Zi0VMW8MzNL#7$eY)y4-Lz4m69l|lssXQa1}(E(FGwBjYlmo4 zP9<@gj!)l=BdS9fh^R1x+i@uCl}~bKHM@z_ zhxOxF&*@oOrrX%RJiHXLo{Ei6>IErERZ*%XRndng0n(Xdq=3EDbMkrvku7D`D)5y9 z<8`7Bm8%uy45rgRSV*H~Ww+X+<{U606;Y1pB~UR*y+t7zs5Ip5OjLjwHn_~>Ks5&l z5i_;JHqd5G_Bh5`h=Z6ruaMG%Sk{SW(>!)0CCP(#?eCE-8zvb>T-NO3_Q;DC{xC~$cEBL6GOa+nUa)!a5e z0k3+4r+e4!?{fmsNG)&z=uUfH0I8E+-^(9&gdhpKqFDLt_>b$54^y(cmK0O6+q^}4 z4=A+15HdyQ)4=kk@FH?|qXu`~zPVyeQjiEUw&%WGZKH$Lc=N3>gvl>K=l;|)DYvTNI)iv z&7_9ZLjGCTJRk*(C{>7n303$WG`%iDy& z)f?8Q`W>f6S}SUhHK2|3DBdBp%Y$^8@Z>iTO2SKIpED3h;e`_{M-rx#s$ftI{(2Ll zo(w;zK;tf>n3BAouV1H0EVhq1Y#<%*vzMY$s>W1tOk%!ZqpG#drb4-?RKr5R5V_4} zIieD)jkLG-t*$Qr_o$_^3~i~0gbH`1S7UkBf?CosD`a@unXER8dKt1K!Ok-MLR~pk zI=E&QMqTldvHK>ga%a@T%7Z_-6*Kj`bx1MgSZRhxve;*vf=LU5c9}tf&G>io>|M2t zTuF(tmGK6O7l;N8wd&gNA)G2i9r*@MQH)nU#A1Mqq6Hcii_4xErdfB{vq7+ORxqVL zugmYkTtwNu=hcwBEIJ8b(PUgiC(vSLt~TT0W+C#~k&Mc)3e{V3CRIlvED*E~BIy@O zlZ7PW8vDWcIezJ~%XuMnbyXEYbqvDXeGmW5NI3HF0b0EpVu7AjhY+PE!`S#kEWt*q z*{FBE309HEOxJ#<%q%WEOU#c01at=PRKT6>iE27it)TXTWXg%Mk#n06=lxuWwtlfI z?v-RQ_EMQmMY@-K*^bNJt%bVkSz9<&G+!mp!Y>wE%Nx`HoHaL^VTNVBp27Jg%}bO0 z#PW)V7VxYUe-Z8q$^zk)(F)G~N|e;rTs;YScnlE{O{J?`yEEC4vj~eus%dlO0+HR+ zW6b7@*E+D?B>ZNt&=0FP9)B_WaEZg#?5k8k~yJE-@dZb4(g@{VBo z*^F&X_T?s^nz(5T;yqZv&3h$Y{Qy-BYw-4=4K5g~Tu7~J36HYfNirH*Wfx;k(9uSU z=3A9K{DW>IZ-lN@IY?Ab-S4!u?)TWLf4&-0GLpgmc_X)^_TBHl66u$Sb_a^AW854& z5gL#$!s1+&g6kHx6K?r`lEAW99Kpi2_<`(BWeYgb@$F@@r zLed+^jToR6cuEup4n^cFpeAVr%XmcXC6pRZ0!4HV4Co-86#oTvEEMIw1`6hyU<;NX zYs(1LcTD!O>DGC2$5l{{H}_{bNH^MjM`znB6C%xw}t7)1cs*g`lW_BZzDAhn!_4vo^Pv znQJI4mXKDUI4T`KDvgTyvK~go#1s@1?6JPYiNplGS(77C){C}nl6fH>DOysDvjSdG)~D)pdck1vj8<~D2_e?hUr^6}UeS*kqMRvR zL67+bh@o;1F_v!IL&()wDP0u?l&G;%E*EW^Cr`j;%U@BC4Z^xmE%(=n^g6<3o34~^ zLqO2gY2|N|LeSN7tMpRA>KMCc1}vd;Oy0Ob*iyJ99KgVCDqOLSA)|H_=+TWOpmbDh z^CS~OTv9#x8D|81NA((D{Jm`mkyB%(dQ}!Mr6!8vRkaP5j0@YPbcH%53!zK3JnT>w zV2k24##p}11+i6srf?M~sOMfMsAQ&(sc5EbX1G#GFO`>Ntd%E}XOQ;uNa@HRO)0gK zKJ6hN*Jk6a>M`5VSqFb#E7+>v4t`C4QyO&waqKiiFL z71unbxWKwLj0;chXOS6EwkXBDb8vF72bE{LVUqQXo2T&+NTt=n|(UsriYVs(5o|jpaS6|OCp*s#5{fLh$ zWTaAvcgiP6jcK?aSTk&Z4PKu;EP~)JR3>bWn90Up?Hi;CSol`m9S>NN3{{*l3Q_Uh z`7jV$`e1CI+E|?y7I{!J6Uh&iv|`3O&yIfiyYbr*3eD|Q!YNcCYlfW$rqT_$no2_B z6i-Jv=!{d2{zzy07;I%D-idP_^Vo2alP)Ly!VIeUVbL8+^L;iwh7w;FTuK`(_yRl@ zU2FSR<=X9dQmr8xuXk$WA?KUwFVFKWU&7Z%oe_WU$96CqtYKtmSczb>Lh%TfzKfDZ z`#GJpga%r|RqKlGkr#JVUf;x-`w~mf}}i_8VK|eb1BywbH|zr^WTJn5a($w<>*$$ zV3sm;1qtQp6%V!<8jCJ^bm;A1_uiNDH$Q*SUM70iHuLt()zHNbDRPJYPVNfKT3Bfqj(B@LxZD~FXvKS4oldD)O4*G zLQ(#@>nBJPJ1#-`iB=0Sgpl`j>6kZP66f1ljEL=FJIVp{auk0_mcuyI;ucp*JI9TJTiA>zO zI-7kt^JxQLedy1ZDH6mid0jWF->pCRG;b1354Pz)iYiE;RCmjujXxie_qWZsX`sIM z>+bhhvok}5?mYr$HBd)-!H?`M)qDS{1V#jNLr?<4NTF*Hr8%YK@9rrtB|n>tHc@JNW$r585N{*gh`;UV?$=RnN%A!p^%B$! z0oG;pDlyf>^g1z@`L_|6YT|pynC!^?CrtF<{3}iLu>IS|dp5u?7{DHpTBZQ&a@*gS zt_b`?$9r5rxyF0?!Clh*#h|$@An}N=S}@%LdJ;^wDM7#fGY6i{05*lRYz_GUbejZl z%WXq2T@m@GjQ5Oz>o9;-Be_igI%K!unXbtEo5y?Bz&9DdJYhO4AvcMyx-g&odO}UM zi9zu*{Nke((1UUJ(|$bR(IVSG+}5NMz( zOnSs|#*k;=I41mvcIn^H{jHedg!KF{F~kX$0A}Df<9@OPTL1vjPd!Lj7yv7n5Hw1f zzrpnLD)@B7GbEWA6!})3AN#L$v+|)&Ey!)o8Y{5V%6*`igpA3zaz3o#yQLfiG)m{= zrW^RUMtCc&EeAVq94Us9YP6WW+VHu1sd&#)2rBQ9GEw)zntMIEoE9{kX51q~0{Nb= z2Vf{6wyUIWFf-Z0u!09aP+7E7qgG2p`6_TX=;K-W*Is=6A6uwlLdE`Jgs;)Aud8gP z+xE_6L2o^U+zQAa)!GBI)68G`szMSXqw{G^MA*)1T?m;e0i7c*r|K#l7okgR{uPa0 zJVv%fqAHlBaa=tm1o*DK&|X-g3ABS7P}#)z4m&Pbsj7Mr+)&$vU56idnFuEkpXz32Y4NsvJ_S!eVtmLIC!e>>IR#u`DbSh?e_ZCie$_KsTytqpeZu308Dh{W-z zUCuKIrR)?wv#TX$Zs%2aqqr_$3FQ@iQ~Tl?7vYonbUZ0cl%<5zq3h)$0;vI5S)29PVk+##Y&fQOj15gdU9zVSbp2)1j zHmQATpU^7BZBe_0-)c2#eKMc+26J-Z7vxZUe?7&qO4}miqit0s9U)%h?s7HmyeM4D zUu&;IzZ+dMTyLz3^U(4V@|H!<$|2?;`6+4U`vnf9goFu3-eCOh#(@eP0CGf%kVi|kf=w-U{i*zxwEI~s zGgFP^+xXONsd#TWpiA|Ezsmg1dA$_*S;DH_E$|k-8a1J0z(T;bY{A6;y~zagC(x=9 zo1SF(gkI0AMj4Zyd%0YGhC)(_H5mytrevQvHxtR5gpSv#=&L{SW20rRXMc#%A0Aw@K6Zs;rqmLwrx;JR7fph^MG&Yis@!Oq={shc9cbuV6-L7D}Gdz zG7Q@ml8W#x$+OI0RVB%~Fk(Bd4I$5?z;5)CJ>E=Xd;O#$jLu|-J57^agE2ww-a2sN zWZEco6@%3}EL9EKe&uBBE3Glnj_EpFy4tf|Dr(PbtTEn>nG?Z&jVM>OG5ZW>GG0Ax z@#cdMPjgt=ByO3ieJzE#t7_FhY0lL zhsf0;^cVvIqQGtH^wZRBMwwBJf9tUXuwXJS7PJ5dp}9U233(%lFb63#lk+hm4KitL z#cBu%Tukd*v^pBQYJY5R{5oa%7k0u!`wKhKk&>|hE3j?t-E4{GwZ(D+8p5V2tH&}? zexS*+M$I0BedK?@+2yBn?B7*#Y56LnS)o4p#hlP=q|0!bF=wf0+h7bQYZyi&F<4@P zcN})WYS5ufkvyPl$yM8Ew0_j|q1~Xit(;ME415Ju!V|7{F3D2{jRgv$rKYSUie ziM(zaB^9%sMbW9jp;9Yk<_c1~1U5=m^?FDCLU|}HN3Ga75*Q;c@0BHj@!?InP}))3 zaNKfHK_pPwj4_R)Mlw{CoH0cy z_w4)XvOto5NzzN6oVV|~Y{g8~dI9f?WT=VuVm(P!MpWWEju4mnioK*-D)hlrh)O6{ zJ5^sj#FDi(YbHX?a5@DG%vrfDwyEvZ+_w|8`Wn3u07~&XeSlEn;IZIr>~M|tF!x%y z!@0jXc;fx~=p^l0qDR-9H}{03Pq?gWL}T6O8H2X)ZAP-qt7(S}OpC1(pe>nuJ2^hSlD7`{N4`}{DPn^vYVPW=TeW53S zX=fvZeQC$KKzFb488HSy`g*iQC|g7(HV1abM&Kj`Gbndpxi(MfgxR2nX)(A%ESw1*k#gfgv9x zF%-|bmSY|x9R3UFaZUd~opJLoVRpUN=*ljyD*rTt5#DoeA%yrAbh`Pu8EbO-X{;dN z>ebwU_wDULR%i{ONVG-B|KgCD;QDbo+%kFS?o+Va@FziaaTxTA?(=U>xs@Zpk=)nr zK=#E9Blr)ygNWPzp$aB$D9rJr_{mAm35F{Gfk2Ug9EBk0($Zkb;Cu=`;&b{V_ytFo1w($Qjt=r5&* z@#7ft!3n*>Cb37bWZY8<0Is+yp-$$O)|Ytf=(rTKQe0|s@Cch(9EJ{1U1_qEQ?x_^ z9d^>7apxSY&b$?Xnp|U|oM(;vr<1zU0fuPOCzC4_ApyNineLlPML&&gFq5`6sc}as zO+PH;4l10j6khWr-VQ3eOPZOIcUQ>Tz$hyz!-`sh3C63~D`L7aDTmc`B)zH3Kr~=%b7QgIz5}NcZ?1IQXhm6#!CYm-Lj-uj< z-3ZVF?0nIUTKVo#*cBE}xwArb{?SxD%=52gJkd@vG%$WrR6wR}24s-evyP&7 zU{+vv+Nxb{k1^!?6TF)YGk^$JFuPX~zoAE6i|Yz!As4yL&T%rt=I*4-vvJP@EoU>u z4iDbjS!jd-6%mbo$g|fiX;bel#NR2oC8c!0I(%{SFGoT|h@aY7^XO9i8M1 z!hH{papybILqNx1s;dmYl5U&|-H4}doaP5XrB2*;jN4k65fbUV2{@#?0I!>G_F`^W zIct*_F-+q^>`ktN!$SIy7roxDSNPjuoEa~wz0AO z$HD6OFXHEabD|KKTHzG~ge3Av{rp%`zK-jPb;*Imd`dGiqi|j)_+wy6*7){#Q1E5f z6SCL+;L;b=+ajfL>I1~`pF&Ga#|8}|O;xprV54&8R{H;~~%s)0ocdetKM3hdmRYL%i< zSr+|($vbBI9Y@%tvPz*GVf+g#+48x#KQ|oEX4nS5@x|w}2C7(=5XQi zKTDU{tP)vk>^DAfPhmdQX8dBrysZ`+tndo;)nHd)HhBeLorxx{)0xz5v}G;dcd8na zlyde%5Hp`X+GcoK2V4)f&HW`aW}lpeLFodGJT&WLBvk^`60upwWP41Qh7>SKf4p8? z12<=7pK$AuKXdXjZv!G=KHxHyYQv{6>OR}SQ8B%~yjMSdRBg+kD!NfY3W=a<0+2r& zOg8L)ha984i&mJo>L2IYoZG{4H}%Atqo&Xo5>3Q102uDFY^dMMPht)=OvRd89jD4e zmEPA!C8{}1fYeTFGjpXVDJM;aUJdW2_;UstTMg3W?DZ%6Gix+;jN@b9 zI_1Sri(VPMtHCh{h1xRo69I-BfW2uqOaG5CFG-^)qTW~OzZHct(-VAu2$v1-I{fbZ zm_wR_$(y4Sd?8nwK0S{RGy!?T4&(1hwCgSAiqY+mkbKk!q z77P1Z>??4#!yt;UF0d(ob2%8*BN-n}Jo(cf_)E4FX%kT%LWja+?aisp{jd0i>A<7{ zRc0=lc|Yk^L4jezL^$@IXHe#4$Gh>D!F{bFVAX>pzev zft5+zyhk?JU&8r`AgTQzn#mTT;xjwXLKb}5aOAe4us5xT=s#1 zLto&SVX#H;Xv%yi)B<70^pGXdq(>$e64zes3)D)IIZCHr;%TWxxS9L+}b*Npn^ z%hUd!HU~v>4`XE;^Z#ZJ^YfbsRMrf-saHU5Pfr;4Ewa~x*i%qdqoyUL>AXv^ zWu>jJrLX6C=Mt~`-P2c(jHlfM1Q)E$__i|nP-d$09?0M2O#QzsY-vjyZeX^Y*U8v6 z9W-`7bAyMc9EP|+D?zPsx~QgUr+are{AK+?LN8&QV8A)NbaORxL;Jsjdq4={WTUea z*(LVFf$ID7BGAF!!2#@^@_KT67!df7Fp$FRo@#q)dZ0sdpoJk~9Nk94&cl*I227l8aK~w zXi_8Bsc}du-YP;`tE99gam6cX3AmF~56az} zrv-zGIIe4zPNc5JTjySBQdUK^=6-9d=SnMNO?P7E zE~Ibv`I95b(&mO>2tSwMJ8%CX?8(O?8aI3R7~X*WV4z8qhaD&#r~N`8-BnQl%_ok` z2=jfAiHi|(FiB_Rww2bD9SpZ?#6%Djl>JDc3hTKQ=ZaJ9vB})nU+4__3?J4&tB_?t zefVx%Km_?^ppM46bjjTT8!tU?<#6lwM;hxmzp?`d8I{WS!XFbAbrv!g(3dy*C#LUC z0!)r$ZT5LVZM6@gFCKWgTi~{yEIFVkIAltqUEv=Yj%I}U3lV0-`5O^O1o?9j(7+jE z5%BP%CL)ipqedb@VVqi`7$n3dqL;9vW+KQij0U0@LOCLwIuzOqwR~NohG6c`*2teW1^a|nY|xZ$>L!e;a?j+~e*`~t5=Z2lEgm<5_Xy82 zb#H{bL>HNIg6(xO+$>z`eow<;Z*Utwov^J7lvVGTvfI*HC;{QL$SSECqAL_CBG^^t z%>(0RNXk~@;_8dt3h2>JZW9iZaJzE0wRBb+NsBnp_O()V*4H7Boa1ae;1H{PrwuGC zSFU(0PApyUn^e~%rd_eCs()%&ciT@Ezg-<(KJLUkU*w*trW+-|DNFdDss-EDz--Mv z>xj{GX|50dP})VI87H5nc>I=n`|1NoL1q-1S{}Zt+GO0L#=Wrl$7P}tc;85EPk^h9#>h!7;vh8>|b5$#__D`3+Ssbk?S{Z## z3bXiv`WUO*QUR7U0yQ1@RI+~ah>BrOdPhSTVY7vHMm@rYKCh*k(@GPrjoshnE=YIG zS>nzZ5v*|=B%#V*8!^gFW>8FJGd%dgNTDGMgO9SEmcXF_>w-W+h7cD8gNyc%@ScJy zQ*_*ZQy=+!5gRm5w6!%VR@{23-AJZj^{B&P+SyAboN5u09C0iL+}ICxCMni|DgcYt ze77f|`C!uT7+2t6xd+t9tOwS5S6n?6MkU1%lhFWHXKALYN5EcF0XyAlfDp0sA3@VF zQBOLl%Y+%0iaI>&2#4^$GX!f(63!P4?ZK90Uw-m%j5E${P|sVqdYp8B4Hhy-3BLK^O=viltOF#N7HOA zJ=VR*mde&L-1S)H5=x{>`V1QvF8C8`>?ineTnFj?RR(UdY$5u|HGs@|cMY0z$!Xdy z%Q4zPMM;i3E1UC&P`Acmh`V2m(E0G8IJ(ybr=+S8YIROXW@W)e4wB9OWHsE9dg3w` z^~6&ec)%_-yuqbzwo?uHsdOq10v>FDyZeE3!b{hl^&Wn{)3Gk4Ug-M3i)d?Iac}=o zY8?F2sRPgO#!dQs`(2K@BM#mNs0iQltURQNKEmVGxd{*38>@{PBrHE8JpdEFs#9D0 zfqPF4g#*kzb#YtANo+@+VHxfUWGaz4n^y>ke&craMUupb>Y<47-LV2hM|TIzBN@dz zPlTS@^@nEA>-RyywinfWM|IKEcSUhg)qg~Ji0`|gI`i-2Raq7amiciO--kza7T)JT zA56Au;5Z#b&m6T!?nbz$co))Mj^j%PBcYYq%ivFp26maVH%IitFj$}Z6SCnAANaZh z56I$wQ^kCS*c^l?M^`?WVRq!8UAJ)_k=C_lQ!Jp1%?Ft zMgvVqJZ2ku8WeZc%(`37g(#6mqAD34mwJ15Db+HLsaHNO_#|oR$4nvk_t$x(AEUN< z5|}X(LZfeYFj|-m&&We>)TlO)zkdph4;|=O)&QvTc0Po61H<2L_`<<$RIW7=>g#@w zBR90sdc^lJxglV;_zixbb$LRh#|Bq!V}UMwg8XkimtHcnTjFcCK=@K@{!NjS)7?4q?qj>YD! zZI)`q#M7rRDh){%B2-v^1B$D2EMm)(+-0RG^E2fM=D0Zz6-Es5Owz_0(X{{ON&jIcR8XMWAftbsZmrg|(^cpZ*`!=V;^QMx7znYb-e$*IyjYx(|C#^993@$s>W7(jI(n zmz>!QbY@!ptJQhuC0YIKMrnFWc}mXHthS|A){wGRbAi))KE%DSLNsOKlJ__Hm!rxC z?WjbcIkJ^XvgV=d^4mwENt7w*5J9=V!d=b7%`v3 z9|0p4{9a67us{u)Fmejcz8SDUUBnfNtUH$y38p>cBMaeYOt%Gk$!#SHaVg-wJ%zX& z`_~Vnc;*9Zevc)Ai)Vs!a3pP^-v!P|s!{X1q#Hs^dP4p}5GNVatM3CPooexg6HGf% zGX#57240=;hPtKdwYloHf)js&0tW&JY$a}Ou;Y9HPpucP1m>Xfi@9E+nHvfLHl5_n zym23UWKDM}%})y7XT9P5l>*|npl@D%37;Lqw=+vdJ@94mQT3)2kPaDw^f@u??Y^Ps z@Us`T2%9$8KY#z<3(;#yVKd_E$hP{@P09Ylu`OWbq~xG)^DmJm)!hww7`10hqt0lQ z5z;Y%zr`>E>su>5WQ67TF__gJ5Q)Ijy*A}{gMlf8iw6mh_+{W7Z|>u_PH}9(j0W>O zC~FPzOSJS&nVZJX#;qm3wd%`>s>Ru-70ZvSx$%xuHd~V%6`u=VIX*ZL*PgG&U|iba z9Z!i;zcCm!m@V>aFeE+_`{04t-K#wk67Il5@wcD>ISDON`{;pN5+BjsLlPf(i0;BE z-S{V#@U6Hfn((do7TK?Q_pJms{{ejQxA=h^@wc=AUGcZ1fvxiGOM5p41UhpdvP2O;%n*C7fcR81?N)ywi|NRKMj?k!O)eBqWSJ;QrMOHKp-=Z|4_6?$Al`T)}+^SD3D97`DX&0H>o)xA!m@w6?)Nf-1G?j#-) zd6^ezIO7dE^0t}u`kE5U$rBjGLvr)X`^FKwAN0HFlwxOso|u&RhoUTMyp!^iG(2pH z7kKOzMq1|U^zQnajs=E|(P}TyDkK%{X*-<|8;siHimFJ>OU~o#K#j#vVdojU-0j)2 zr4c)s;fQO4Dr-$HD3-9f+|dDfl075l7Sms;FS*&S@M1r&w9OXS(%CdRS)}FNwVGko z#*)i~&FX2Srr->x*T&Tn<0S7*LZW|5M>SnnO?F*uoNK#9DPeSwIMImcl$^GBEj!<0 z&mJR_%l_uIAGY9}de6c=Zk(7nog+12SYrqs$I%%Tz@5|6TMw^}Ttf)v z2zW%bW)i$>D?nS>t&wosll)||iFzs!_EAF*L1&>H{=fM8=HN)bb=}yu)4{}cCbn(c zwyg=LV@_<_X2-TM(ZtCF6U@y%XP>(F?5g|wZ`D_;x~jX@xB7YC_d(!VisQb=oZCmF zt%KK^b0}fxY@oxq!qw8f_RjQAdO|rxDjH*7=AX9sL40Q`bqGDbOSCvz=ch8SEUSeB z#DuQ8r%5R1P}r^9u^S;H7t2uRaDIKgh;#h?D?!FBi1i#LyFI!DQ?RWjZBuHqOfyuV ze2qPQg!>wV04W6HyE3XPi{uS{)!?81D)pSLT%^@Pw5IX=3t z@{JU`xvEPC`8CxttIOPRPK%s9wYffUPO(8ugroS|;!O!=GfeAwtJ~rk`C?;pJ*im_ zvZL7!i_PORjlFzTr@A{nb`iEbU4h*O&K!YtsTxmXP^*&2+*EJim#t#04ec}upsr0P zPC3p&`_0(Bu8(Z(6W%ekM9rq`EA(Pi+x^J7%*v72KX#M?7THiL^3 zctOo`i{~@Es6s>uFvQD@+$BjSir^`qGd$432>c-@J|RLl?KX=ZRVPXxOOhci{}=+TXPWw^27%z#8dq|P0H$!SaBu}32M+-u29j?+S^QH6V^QEq*RrKd7aLUSwv zg$=gp+$#XdXIy60f>`7E-R&A}E;^=G7(E$*PMo``m0CMH;zA@Cpp}x~ouh@?v@%)! zo7L_$v?apT%$525;_Av`>&Xqe3l$rdXXL%y+gLW0A9VC}xSIHQf&UvqfUFHnsd?(Y zEe9xz;1G}gbRD(!&^e#|wh3RT!6LuexfMUmj6U=%9#_Q9ABOI#3oa4ybHd_lsj?7+ z12#*BUv05X7|z=gmze4UycuB`X_-=9vi|RtHWpfZ*}=g-z)|oDV*MqCgF{TN?hU;9(6Q zAp@vtpOZ*hAKC{#z%Uohyhi>_*PHm9M)P~nlpA{SIfDF9vf`c$HM1~w4VtbvOkQN3 zIsWPmpWeX1R%FLI;XXJ~Cq9sLyP#v4)Soi* zpHCr=Z3g(W2k*P#Q>ph^kHPFxAeTHFVV^C%5xs&rC%|oA^HqQ4-GH@$hMg=#N-F?g zKP@&%%(D=ZB!K#GpiLd1F1C3Jv=1z*YE@{CIB-lR3;jqJ4&(<3G!T?#44vwR zXz0{Au_+czUA?X72A8>vz-ZpMD`Sg}y*|XZs*L+9JQ@_q(woP)V&9_u*Ch5gIEi^H zgldQo6dj$YuplUON=2B?RvUFC`(1>^FB<8gUjW{AJ5yAc` z(3xeS_NF3&qhV8OBA(=+S~TDy1IQoSoD|VD5;n6g^c0Q_4dTuP$2O(u)B?B0l zi{MlN{#uCi4u>&l3X{W^$5Dz2_fCZ}s0foc=>Y3)0CMWUFK~?Oe3Gp6YuOIyy(lkIUm-qDYw)zH%XO=zGE9B?%$W{$xsOh>??}PCD1I(lw`~Vo z?djb(s8{3MF!-MYeuFQ^oue_159WjBK(r3I4t;|iD*5{#gYbsx6b7769eKg#;MDsR za2>FF?RS41!0vav9Hj`+FKVMsbiVHol^ZRFpYwg-GYgV3Q0~BsGEnIN6}=rTD#PK1 zAzCQu88iCC^v23NSNAh{NDk{mcC|0@nJ+q)(vb2N72_}EEk<;@OtBz^ zpBI%{R_%qhvqa-|qc+vRQLF!ox?gHdWr3$fN~FCeyM@|vVKFU8MBXXy3lPH``O>$&{x|a&~6EfnUOpxm_dOnLZqZ(1%@YGo-7AHqQjGf~72H zB#wx@5N73s!0#{$W#eQNNu@5mEUmTda_JtmV!}l3x?4F=;Z0v`>5H4jc2BNZvohZxDLqqExK5p3c-k~6dL?FaWh*g_TUfNwddjeLv|(FcUFGg`+V_W8 zU)f60uQwk*C?310#2`#wbe&Om5A$qCuYqeHxEhj3j~{MQnKVDjDJXW>DD>iBEUHPh zZqWa3Ps8|_Y|--Ta*UynKGH#yv5Ps9w|Md>PKl(oX5aaH(zru^uvEE{m`|Je#$yuGJPTQ8|~Z7voHvz888v z0XaQ!H_W#=jp-sCE;-C9NOY7e%!le9gRyD0O@#K~_2~Jpi#VF3=C`p*Nt8A?Cm)V# z@V1(^Fb%jflk{kqCZDl>oV>F5<`vwyVjtE+drI`6l1w*86okKdCAq4rJkocn)8km z726rYGc{T4!`2y?jB)f_$||TPVYv@3(6a*`U(MHs4(%cnFnoBY4_8h$F!s^u-8=Sw zR}%^jyPFBnk+pPS_qmJnqpGJgPSozJ3^^>AS8Y$oPD#YGaN)`^ns_g@ zN9-@@`48OEJbrsUS5vnh_N46S-BO-fqN!5~$E%=gQKM08gWV<3#L z=#79Cc$e%X;B%oGRX9+3O_YS9Ksl+!{0g@8mtUAr#2c>>Pwj7zcm^hWEin2Kka#W^NUC$U zdI7wxlY6$4&cp;Y{ks!(WbFtB6Cew0j^aI!+70P*TP*{zb6qV1sdL{P{~_Rbv?;|0 z*Sfd2oPal@gk$&gjPM!(V2M%QDBCK+)1-RnGpsIQ;b;}F9@|jKPi}y#`P7{;#&9zV zu%Ez!l_Db+S7-Id(fE%iO8|FgfEt;ywlvd$$GQM5hXVT*!dM3;Y-dLZ36XN9jwfqC zR|h;B&)hE*`NnS{5z`4E*+ZQ1{r+6^j$agwY?9^{2_QNl@~R$+#&fg6oomZBY5-(L zyEff6*6zvI?YLFRZqo<}A9`DQXAy>Nmd-00X%FiB>U$cf1mT;&$$JHwGb7%9EW|;X zc4R&HO_CiXr-HuBy_y>?E2c^+4xCCqG|{MS_(9DR{O@ick3hZ)FOiArh(VA01Yv3Y z5BLY4g)Ea$ho4izHK{X$Z9{x`zY{m*yPgOhgy3d$Q zASzgMj!^a7NDw5;ISc0qa5t2!4|}3yGb3spo;Ni~=?<7menbrCQ-*pZ!X+SyDvLTu z!q~VIR{dn^OLV97z02iB&C%E1J(Lx}ir9@{P(uSNmi6Fm%=zOGEnU(3n3U7|gU2&4 zYc_|~l!0VGoBuqSH_t@PyE=pSdsF7U4vBd++#N?~&J*|x5h9x+lurWm3qR_=7Vsj5 zkKiv5h-`*nw1|y>ryIZ_G_Y|VjgIgT(v=yJO#~`)Fw{R0HWLuK1dy&l1(D&H7s54u zgYNhan@JN|{mFJE1u{1PlEr~kI7gHGAWC#|!cg}=!9F{tfV|{V(+RuLb&%Cn*qC2Ph{2gk%7rlN_m{9|1zQE+OwUaLi-iT)#o{PK3#$ zizJYNTu6at3?Oy%BP>MM9N0{{&>RWin7=TIsYq!w;6xjU$q(W{KY|G5{{?x+iF3q; zengIV=RtDB1`hRoURn4KocFUxz!ipo$2lV82f3r0V}#0oesL;9HZiCQOOf7@uqiYV zCMh7aAYj+hNW*fWYJ$O6Ba^nf5-LrM(wTD@l3uQ_(m%>7hn9_t%&ZCZkd{k(*|fg3 zVZb*7wgN|F0j}^l*gqXQlQHy28@R|1vPU;34c)qcyn{nzlY&~G4dtH*n?e^s0e}ny z0an_;4swtP&XFHKNFV(OEp+Py@-6_USQ3hNH1sVPwgO#5kqk5>2oN$Cv6TQeF@U~% z19j1lh(h^)Lb|3wS73@T)dRlY0zAk;5>P(#ut#GTW&7O+W&8XEqfp@FBJdMoAq#nt zLi<9a!3N$I#2B=sm2+7>V{0Om5#6cz>t8WiT8lb+tg>4 zKI6KqKsowhC7*7QCH>fQ*E&b7<|M-(wuhVHNyrWKkUAa`|lp*iLbuoJe zJ028BsBuvcMlnDCC9r|U4Nmm8+Rgw99yv@IMNPwE^AKAE>bmCbe))s59Kd5N3D@35}b%w@9+J?*FI`%}3$ zDe718T=D={nWLQ`3Blo_jBe4>a1mUq8K7fbMs#B+&#Ow*JPzHJ@rQyq3>6E$ODQ>h zUebikgd!aOC?*0X2nJMiL&A`+q+Cq!#WL_eibO_d{b;Mtlf3wOlK*M;ChBDF^WXJh zyt=`EpJ654$tVd4tN{cFMpBADrVwO>5Udp#a$g8Yrri0K1}+`h`Q-MDKCN%(hqW++ zQf_A&E1(u4qkd%iUGsWf33z$^eS5z}?8mG=N*pjnv0<=i$dST`)wy9HVU25RCiV1m zH$Y_eKA~+Y%A=np!IP|vI*`7Zqe<7&FxJF(?W+6j7*xvI;nj90P3&~IxRu&`UVEpP328DsI7yiV0btWKTO z1@u{rEEq{9mDIkQ5OA3~YT`Op(lhB#<<+=>$rA*%bV|MOo5&TvLfuViGy|mC{Y2^Q z#%5Apily7fcNCtdF+0r*zf5~x&*Pj-rbqF$+lAl1w44rmO&db|#&Zc=MQx#IOR@NA z4?T%9RzDY{OPD|r*1zgOfK)c(QyBf;-D0Uj6)uu#7IA!4sDDX}-C1X9q4@Yl|Nq`X1< z!aODVN0Qy4!r~2hz>d`N+D;Q1+?R4X&1bHg+7OrV=Xjozo=woMt)7PRHCvKXj~VD9 zT7k*$8LH1vmcm(#bN*3VThsnYh|$NY{t|r@rB>WaVIp8elM=UpJde^{B4|rf7|3*~ zo%0wNZZXn6jv;_rxahugMFeeS--Ut7DrdPg&p+{M3|>;B97lxspCe*Y5fi&87%(t( z%zq}7sGGQ2S^Q5UBVA98NCPd{FW6$;qJ!$l6k}RII6P5YL7UulJSp56o`D?CEawOD zTF7?hbdHX8H}wv4hfh%S^&ivbV$~82%?i|t#tG8=>kqJi)yCH;7KR}cT+&nC#j>v1 z)qp=aI)U#`ucY96JpLq+RP8w7t3%2#tD^PTJAhkDv>gPok|Lk%VLr;8FSjJoPcr_( z2VbKJDIJsAB@R5{1jE0F-ngI%CIZT{B5Uwlp?-_GO@xx4*b6YCo=sgP0kBrBkVj^8 zvYVL;^Rj>;ruJ|GkHDl{>BU6dcdyP-N$m=Se zN~&sh>-f+mCzP}xCB@mXBSAvrq`0H3I;>Y?{!z!1g)x|Lm!}ptOVdzSCz{ZaTvE@N zDby!yS+*9enVk|(F3qC&Yc2K4`p}l%Xx-}vYPPg+WOifM;bgpY9blb$tSpS@h{L6bE00aUHHZl`hJr*~n1W zX{Gy&~E z+RSlmj<2k+C|me9PDZLn)`G#2xx|Gke2D8f4)Th<{O7icc309OE)_#W!gV6D*AK2R z?J`a-Q*vicFS{nNtE|eTTy=&YI|ddJKNeV{dkE9a zL>nwQzrvdMKh$m2#ZNQ-a$3u3uPMLJXdryO`C5d0wN8gb?3FOPf}Pg_TOQ+sqcHso zndUk&6##8j7E-e+F?Th14jxm_StpW1d=P+dPERr6?_csdSiM&gvl{M2f!-+=!`knc zY|2w)+xS4xl7-#Nd&VbrjBK-yE7B6dw*%U)b=|Sn#4bQDoBK@mi{o&F)cgBx zy=D3j%(=tW_rL6D3E8}I%P{VS%w>q1}N$80pDomm4rJGb53Ta?bhf z-|djyvGpuHmtK!~vrg*en3gfdn& z^`w1|h@q>F5|F(&e*YF~zUnh>z>iJoMZ)cQ%L3WwllY3*C-|(V`cc)qI6%T)m zjQSjvF-D5=7eOWGZ5bu4X%CDJ&jazL?0oMlaon6jw(=V&cfKs@`DQBvB>fIt>OWA3 zkBIJN(484@_F&XA-k5wmcyO6BjuQyk_V45;E^Q5YJtTOyAj#Vf%sg)-<%B~$ZkVwq z=-)L@!nW_=6Q$Q8FKp`mLjs31XF_oO7#jWj@_RG~YeQ-*@a{Buk+jYE+J}zSmo>X0 zdxA@*?XpO?`jKEZR|o36s5-Gq!Gvhx*>iAhh=Yz^LyB&C74y@3_&MEhvz=3Hg5w#5 zn%lpH={xQeMVfLu(8V!#Iz zcp&A4wi`>y7Vmk8L%cTi==u8yB-C2l?y%<1?mb(XaCE< zNA(x%nK;lI9_T|R{ezT}3Q8tcJE$)Et zcx&V!NueX+@HbO2kO)d@81xgnnsyCBP)je4`s7O_!+}xm_q6Ay8#vWo;XHL@!|?Py zi=2L^6Z)HT2_Rl*_p!fx-3RRdrdXH}IMJ7Va-A?fX%=MvP&uf1nEo4XY3Mm&N@0Dh zB-F8zWyr~fw5EV1K(15u@Yw)xqe^+gH)v!>#9F#?dBiRZ?7VO?#~yox@&m5<)B(ks z=W?XLKSgL(qm#gmnbJm%^v?w33$K|>{J*b%X8XYXVbZYx2|YiAWS+@2mSd^tNf)_$t!8)8D%x z9x>;-M+Y_>zc8xZDoC&s&squ1FlS^p(#dqm-7*u)=w8xkjtPA;b9jLrX7hz%A{t*$ zjngDLL}A77WLCVlRdUs4D68bze&?Ba56Hxho$aY~%}cmm$GL8xnr~Ey71p>rC|F!V z6ZFzEXC8^tK^Jcp8#uE0^fgWr*x>*g9x&lfHZx9NDC@0uQ>`nmv`=0|5NE%jGJMUx ztI9QN@nXoE41h|A4j>u+xd!L($Fz53cw@6mjbR#THy0Dxgcnv%lN-U`;hXy~F~Mh$ zZzqSRB3K1-D`?{e!7jQmqtOTb)u7Z!F4?{Q-me$5HJ2r{{<_f zGZ79{?%&`+CK5lb*Nbo28*9}oE6DoMPtj}nh}*w=u3C4^KN^^2w(rFsSU~KA;hx}b zEQlbpt^PAF1?}5_U(pxh;OsQG&SEIsDy8(eivhm$u&R@jZ17hy-2b4RQpp!e*ql7ZmXZ zD{Er}U4;!c@dnFlobd)yG9{9ng_P!^Hd$8Ko@P*c7nzA@@AU*fDP z74OmU#kKPr=BVd_)~$fse~FCH z^aJHbkUtmHtEsFpW3SRk_WX&7Gb&=EyyI0>f)$UXkYF;Ubv2~&6m6VrVg~xAn;I4f zCC<*$4R#39#{jgFD?MR)@o6aqXpYhoMbo4ZWl#}bQvCz@BtbQaqHLe$sid?=kJe{(0QS~7{336FG|eB$ETh{1f~fy+jOKhTCds~b!z4br%)o| zq+10-%)Y_piarT8{N%|@lju%+THpP6Tu*m<#19B~Ut|U&8ufD#pO-g=W*%lC9I}P@ ze#jq-IMtm2xK)Md>t{B5)_{P2)7^s)F|!ZK6fi``L+6<}uYxFoz}I9($Uy@&{vixu z1&?3eMTkR`um6+PUo4!kEC)dit=RCVQ`j)S|C|s)B7{~mh?<4bIcs?rF$Kz9^Dn>{ zZmRx|R7hk0Vzptyz7j%bGkaz=s}=}vT?Sh0B~nX{TYmR(u_~dbJ)@S3-_G$=Sr>Jx z|LZo#xV4_)x_g%9&SO{+g0HLscl|HT^~Xz}v59uaF#pTQU-YNbdF)zK%MZ5OGP9dk zq8s*2XY3wQZrL9AF@x^a^`g)^RhFE1)!8B*(R0sJ7xorC-L)gf5r`+NyorQ*)Zsn# zjvDv6I`kG)%?%yVZz;7_ve;~2S7LrF=)?L|ql+6?+QzK8d`q5Mg&V7>kyYoy1*P%|I zHFSio#~%PA}g& z=``2#=!olpQXCB>aGC1Q)J7UqBwm~8QGLUI+Vw-x^2SSkvTAU zlNk7jr+y?He)?Xl6w?3Ucwqc%=`|@b;oG!|&2DXd;!hcw zZQD}bw&%b%y;Ap@@ZR-#vOf;KIe{)}MFRc^$~RaAOuF9}{?>mt6ORi=AB)CAreG7# zfum>?kBdMjKtDA3jG9O`5l{G*Y%)wserF;c^34>FRbtp3&n4@dDqaDmNjN&Yc*@YX zvVuF{0_*Oynvyy70e^$IkeIbl&0D&)NJl865!oFX8T72I(lHDu2t~-~UMQ4tAxpXF zeRb{@yYQmVBDqh7&1VSlJ}29oz%z-+1OvU(=kAnr%?42wD0$J2H7K<}9YpZ2Ae$cRrS%Kc_>hv$FX4JF8@3g{NDFx!uNaU^V^ddUYo3-%m*Q=9|Sm8ntwt>+?={Nai zIhbd#&0FrR*uKS=z+l6-qU|wH_d{OUDQXtVSFjwy03#{>$Zv%g9|@*#OZz)Cavcn2 zqP!pYh1i_5PvP3S6jz7){yKcaX=K+nmJ;W&HBcm*`G0GL0i;EI0K09uG~FI0S_a1 z;t7)01ZAfkQ%PgIS@DSOj86Syuf^|#%!g|FgRAXvPS*XaQgtB~9fHh82HZ9z78y@A zdSGw3{~KjqfMJ)%e0KGIf2u40$(38i(cQw;(Zv4$x&X(k{kJRk;%1ue5qZA16xt?v z^Ty{9v=_i;a6$OZgwzqo$$%Zyy*)w04v61))!s~`Or zfl7EJ^)Rd5XHAu83(7+!b5J;zY+SDXD#Yt0$ti)nNKWrBoS0*3Vq z{7GrQ35pw2xR6%sRhAOQd(J`>ArBqS7_y*ux+#UT*XAl6`p#eYUHtl|Azs6w_4z&7 z`QW-IOJ1l?JNC=)M~1Eg(=a;)>W+IarS|(T+VO~**U$>3D6|{a(`h6Uu~==dJf8{Z zL;(iXGRrPsmi4>J1fALpl;|E!Jf^qNX;hShvR_+$dxzvHY{APR=hS0ibPfS1o%Z7^-Z(C#em$sKD>qkFu>_)g&-H|=~W$vZal0mMFueg!EVE@8kJP7bg8GF)oJRFH}hAfTQ6Yhmuy564 z6a8_oMvjoh%NRNvA0Sn&N)S!lae05U49SPbu&-$x!GydAz&{`=(f@JvNZt0rl)&o0KDNttw~>3P$0|V}m!Yc; zj@clPkwbWwEs1W~A7NidI@`14zEJcX3?upg4>&>Xx4P-yCla_jpZ9vkePia?>guAh z=yEt0v%H$x@;mVovb>-DwQB^{Z&7ZshsID*pA&+|P$CkBO`vH*vsj-N>9kKJ^NT~> zy1eq{2Ld6&F!8*yvHwq$P0P;l>QckRg!(LYgdFW_ZPn)INN0)dBZDlcq6jHLhEH=6A&=kGR-X5jY_@p}c|N1!GM{0w`Bb>H*%wve{qz9sd+`nK$MdAg4%mCya4TeQYV4#UnDf7M zKj0L#ZXGBx`(&J`^sx}r$_J#j%sX|GXW=9h!`dJ?<;`4uOM!6NoZ9;{%@)Jng{UuV zwFo(3y}hY3zEoWw&R~qUPSmPsy5s09iiN}MrR#B}j{gYnk?g?Y7RDR8GGHm{2#<8S zEr7|hT0)?E^-U()n1&>a5E;of1*1Ewix9TU`YFIkR^@P!{7-N`->5F?dsu^337^msBy>- zltU!@J=H*%y{p$QUpqtEc|HnXhB#GdRsy0TNnOCOF~Y+{683E&4xI5$T=Yp<_h^X3 z_S+G!^H;=`=p)!|zQ6^o?u_T|)VD=3-{5pd!IqH|$zQ6DZtjfrVNYQuSls|%NFU>7 z4){aaOL4~-Y7wO4I71uW+?4NvgsUHk4rT$j!%b8B(m|?VxVMeuM=A^c0U43s&oJRn z_C@#P#xiG;Udn*}TW-KRE-f!SYQNF1{tJ-NKDZ@!i?aay-rh*AHh$ExfSP_t8L4g} zns0c}NeE##(m=U+Km`lPpxFf`SU*w;(r6MKgcn*K6!M17Y@6sFwA+7?z|{LtMh;%6JWaay)BqDc<(?Hi!?dXofFiz zzk78Db*D0JJBxXPFj-|v7*Tf0q0oQXU5Fi@2M_+EL^IoH{d)U(p#YHo`9hKTpJ_sx zp4+B4*4TIdc;cTMTsE?Zj0$RF9p8$D2T%%ZvgVpQI8n$P3MA6F6EJkx7U&Ld!1sQj zf?e~c;)_~NAfa}%3_*6^|Ld7DkCZBwRNCR=fR)q9+f!SYmDb1m$h{HR9*nikX(Y9b zcUB}Ezl;d3zoMj~C@V62o786sFa)wsq)lF@Dxxx>hVO!)#1=b|KY?&(z5@9*n!9Nv z<7mX5KucpUH8QzvnnNJLM3!x}QvI^9DQO?V%FP=EhH&bg*hV;%r|B-O#nlq(A6FLW zK_KywaaL%~7Z+BhQ?8Sya?x+(jM7Orj69cvH6t0GHO86wRUwMyYytlc7O9jIOhH5D{t4sH>6vMbV{PHQ&z zA-?{yaPRSnsnk!Z%E`nT-xXtq$Z~mQVe6%~RG zn;AaM);H8O>JVy+bEChq5yw_T2Z&b1u7FCd_x*lVN5W@#o||pixa0e0XMRxaAA2o- z*-O%wct1-PQ1h@39F#^)Yshlil9gRa#E~k#Ud>ZAm0i{lj6coXN0zVGDH?{8TV8K8 zpk;G27>z4)W5?3j1S+I%7+S?SRkeSMRbaOcVbbrMdcK&CxR^%bHXKiYNKZA;uPNhO z+FLp@rBe1yP4dQ~FeHn|OvB|)^Lb&}AkA`)pLxDOKNy#%GO>EoFLlX)+OTHd>b7Zm zqZ^-5#Zl>2i#D5uI3H$gqx`ObL4{gIZaTZ=QW(7wZONQa3)NI}GThtw^65GKbdI0S zgx6Y8?~^*i)X_gSUgbefhwy1o|8S)d%XjMQ*L(XC(J{gYp)VjXdJof`sIgdHoAJeR z!OzQfF3ZIEdVXfH=9s)2`Fd}M*lEFFePc8pW1lettpL^+n#Ya*t6A2a;LmEfx*`w%SEY}cWm@~&y>}S<4AnlJv(>B1dH z;KLOs_$U&@^H+g&R3f8-RlNZYlt8#PeoJ^cSE}V!D?#hQv$sY? z#w)Rzeutm|bSshQ3lMlSbovYSsQ)F+Lx*85#ByZLJ+3j%Rd{f5C}1}{bZWpKPnr(y zj&am~70Y}s^SMYF=#F;ustoMf2b=*wN_cK-5awi9#R715b~KtdIU$BbV!N`lnL^Ov) zzCr>-NrxjoZ~&+`KHoTkKXrteXlPH!G<#0l6#*CBR;#xmYx!QE$*1-3s4if;y;3*N ztZ+EJu6do`_pSbuOb#8)lYaZzx7PpcTmKW8T+PDj|0C+>L%}@}U8Ho7kv5e-WSo<$ zr`@;2z#zoPW^=!rOZ+)v2>cS?3~|5l5%B?ZFO|34$n1R}V}DWkk()X^ngM7-U8wDS zpDDZl(X`O}_vCvYm@$gy0x(hmyLACAY#OV0ucW<4wID6hJa|cCO>-X>T78?1m$L(+ zC^g_xnS#U$rh+9s9qV7B{-yR4WoW9`G~7@tF?K!kss<$wL)~FuHs|b7wU}4NBms;8 zmqM&vj!tQn@DA!(lH(Wv*^(=oN^9J4jE1l|EOY5JH&!%SZ2IP=N9`{soY_I?$^Z~g zjE|Pp%RxbEulFQFLe4Ey?_r*NHP#^P@)A2c?JD~DK;8uB@TDRCVH11O20c&7pJ1Lb z>VxA`)Gw|PNx*f!+W~AgeXUM#`xNzmg$1d0B5Za3Thy=iDeB+;6!mw8nzen3`Zxa{ zQUB`>b&;Q@g~0~aB}TSXi2bZ$rCU@cqyD6X9D^Gr7r)}j<|i9aLNp;E0YSZ&Z2kKX+&q1_GnI@u zW$uqYnU%dU;9IS$kZ2u|>Av`jqr9nRpPQ1-{4A@Xh*zFJ$~dPDKdar#g!#N#qm5q* z*GaaG#z^3Op{b+de)mu6*VP9|4&n(R9;wc~SS^*#1Ma&G8_>FD-641^dmcP7?#k2V8_$dMimsZxzziAW_<#*mSOqBjK;K5C|ZWeqMq)0anK zQ`9eMVYD3RxW7d(2j?t^`i2qlzt0ikXny^epr&+jff<8JSVQd&idTv)kBg6-f7AN} zZlV`sZ7l8(x4Um3ryHgjCzt#&6Xf0z9jd({uN%B!l*}k{ws1|xC4$+%DA8Ulajr5c zYDxeyAa@{p_z_D27^8AQ{K})B`Iy*x(yT^fK^V0D8;KjTD-{nyqjJxKBvsbKzz|7G zxcnVFgUk@?t}>fY07Uv9hmSbczGnn(2+eYUrCDGNwsvIdoU;@8?T2{ayEJ)7wnq2L zo6h?6l-Q@V|B*bR>;{R;89e))$gmq`WMov+89C&68k=CtaZ(y8thV65(DS2F`vggaZusB(8x1O=vQ4W!CcP^6_D z?~f_GNEz(P%t%I&m~8c*wle8%S&8Y`tn^N44{?gJ<>vABo2NA0b*I?+UZ>5H)|hN5 z7+FvpNYtZ$Jatsi@yfL&H`MK?72P-#mfba0TiX_W)M{8>A5KAd8;fCV3(Sm}nj**k znTzJj&7kCYR-Z(RFB$YjeFSURb=Bh54ZWRCI{gY4J zDX&YJrj7fnqv}|if!^YQ&jTZ~I$9*9=*=0r3-dQ?(;@p7x$R*-*A1xH(%K!NggT0= z-v4BZ-=vWvtXf@`*R+P-8%8HoL1Ug9OU}#;7iqpQQe+r0e5R)p(l<%$cbl+O8r?Zl z`nON}K&Q#H!raiT3quFb zqTviEqX~-GRA$<55lGz#UG}r!f7I3yL>3+O8De!SWmfYZMq>kw)-rya&(ey+0)G%0Q`s4`u=X%e51B?i?#c#8&s0k3xI5YcCS{FaYiXQ|c*Ek+T z&bapG)B0=SE*wG(u(kX8VW2z}jKd)05bULmX+w|^FNi}7 zqR^4ZhuPvp>VwIjiGSizb)$6AdS4PH@2yaH<5D}m^0cc-jkfF#h3^N(awsAcJ>71G z7%^blm$`66xHcT+^ZiCDUUGx87YN%!4-ec+V_$0wk^yUTZotkMF&x|+mq6C2vH;MAE?kbmw4LEE!}{k!5ovGUz#F-9!6xuhGao6 z2rnQb=_oDGuJyeV;^%))gP*Z&L^!`_aqxGv6At?Wr`RWw+hl=n&<_~~Kq$cwrG56C ze!V?-|AhSbh*F+a(ZB!E-0G|qMU?vlS0A4)nP2|ln*4W&;{Ol>{>>#$YfUK$CXaE# zDuFh^-YulTk;bB-3xAgV?9&dO3!Y!8PkOyyq0apcmNYwn?aO*Diu3a17M{?RPF`Q{ zc*^lR-r9Ve-OcL*(;s$6(b0F3g5h3bD@e<}Y>$v>>H0D71E~~mW5>0M3GZ>b@4%_U z(Zx!`C@J@>T9M^JsFH7?c_ij68(U7EH-Wx`!(hw`4+#vp$3)L}#J%$CZru5I2Rn&* z42h`yuc{nJcfRqJSh``Qlp3{5B@m@Vj+mmcH$`j!kne%Q_JcU5Ou9LePqv(yLkOP+ zlY!F@6YHJdnjYz|rFGm4_dn&fods?W2HR^vsWH@p%w6jF^uY-mdTYh|_()oM)<+tK z$%FT)Yp*FK&!vOxra5bfjYqmTbuWbko!t>kt&f)D+L)foKv(vk5kE&dGTFrbl%8d{ zCJp1p&>!iJ*CZ)?QXVNLJi5rcEfjyKy?Y=l8r;hNCLu08%95!edako7ne(sAFu->G zLX^5A%Ic`y_+dF3GB^WO!lXo5a(NGY%0}^}F)OGjk*I82E%juQaqKqSYdMV+_)zTK zA0#+?jgg z7&=?Ypdm!M8QqSz zdn%T&Kg{GdG}prk8=I;%m|w*1$CgK?VmJ);u5NLbom+?sUts4kr%7t7v!XD| zk8yfuvw4s8N+TOuWG#heLFXF`5s+OkCans_Q%=Nw+9EXdRsLrNTkwh**6Nu|YoWt4 z>>BO{r8B=OMBF}9jc!J8a z+=K)MDZoT?IN!6t1j))Qf-*aT|2sAt7Bl5KgLyp5F_S0)jikO?BG3^d=}-KWRov%Ys#p3`)fpZFO{->UYP3}63)qVxJp z51#S~TJ}GO_Z0u|HY!`2xc!eGQL3C9p#<8PpM6hH8WD8@JP$KcG{fflFcicZD{CMW z>`RD6Tr!vmWqb*1l|e(7>^gV*XD?_5*10lC7F${0C+7vs`2?~*qR`LFV!SFEo2iHW z{`%@=TK;;<>2Kg)Ot5a4Wi0bOLyQGyAr#WG{MBoJ(PK3>RFJ=9WHN0M?PqHU-DNFW zEJ9?sS%1J7&^Jly+Osb!HY+#w!xG2a2WDyIy zTKb?N&n+Og|C`Qhn6A#`IiJ+gxv3CWUoQ*Wou_G@#XtvBZy`}aY${WF(7+}WC9E2^ zzU+D#sfT8|aRyZ*pv!WqBcph&Khqj~2aDFJQ>HJuegqfUCf1et3fpW>tF0!AEQJ@xygvg-pbx9d&}Ogkeyi(5+VvowldNs+1a_0NXlLrDIt^!zvEN4n|Iy% ze1FHoMqezn^U#FQ2BkFlCk;+P6`N})y zkM`KLiK!OXhbU!~Ec(a!6gJI?l4>@IzA!2GDZD=EuPCxBP7+^&3po}9CRxj#GAcx?jGg!S+M^;8 zp&pFqy88O~Emx-AWs<04lU^~Xz%Ui(4{!zTU3V;#n-jA6HT?42FMK*fQ1$gA%K#Gn zPf!wfebRfzPYTJ`Xg-kbR=9EnUK6@-e6Xi2!iLD!JIN-Z9${ptn1$4 zd#J*x$ioBk2(RmeH#ad6(|*V&Yp+&`lcSwdWNELVJ3>jczRNo39ex=snl#RacFKpP zooYxw^hgs^@afKsr-TPqS%WkmGJ}P33zazA)!6JtreN#NlV;viu~gZ|2^$Zq#C_~z zI)|VTf}QfdO{QcHzkNYkA(4sGiGpi4u>Nq5o9T{9+$K-^Sr%YQBm3lT4>IZL*j;m> zXOB-4yjQ71T}!cNK>+-M5T3s$!(sv|vGpHE^{d^Dxs=M(7xYOk_wTju z@b7=yHNAY5IK*I#I!aC#p1mnQkqHhS(%W0-@wP87bOz3dd)A>lH#eRhLPpO1Q#3ux zz-8+oswj&}%wZ_XZ%aPlpO)oi|N4@DU{YoYzs-R}>jmj2u6Icy=mp$rZK^wW=eVn> z5|%k>dCLlzvC1Brrk9+)&^2V~)@ypFeyZXwcjN00|5=r16RC$O&PMc`_Fq*aeY$7& zjThWsri0MD^Np@4cbx?v7| z=x3X~@@Zc4&KZA|X_eo3ZDx#vd>};A_~xj9;K*~H-nGEZjkjq9jm7`y>UokMwrpT! zsK}5i+Y`=LJwzVDn@@pmzPNH>kg!Z7L=WGPJco+s?T16M;~$6wIH{)m6+66J1U{~< zbp-fyaD)`;q0`bc>XEH{ywi^v7kmoh#_t)%>l@KeMY9>~w<@brn-zWt)j8aX(#S>H$OuSz!4Q0J#R6s>5^hE653N>ObIii|o@ zj!vTxx0iC5YqSf?I)&%#T>Dx)SjH+Zggl^sGZ^kkZ$3L%bM;P|hLsT>F-?DkABFEG ze_HmIyS9K>W3K+qVB|is6BBZe0k-d*^}OVW`eT=*DptKmOZd&DB?D|eI)25 zKFxb}J6QpRNncGXubWi5B(wNtyJxA)DjdT`q$>?>_Es-G8cAV)D_T=Be)1Ks+UkL^ ztrMRp)P0iY>EbRf5se(Ei6@jmT31A#i#Mb@Udx)b!nWt!mm5?OiC@(N_AlvXz;6e< ze2b@Uo%nvNK#u=SRMr*Xt7>1rf3qO41fU)KjD5D}7Mej@rKl$CXy#Ex<>A<;h}XNQ zXRB%7$;p|#FwCNmKi&8V{zTzMpIRvO{NkPBZj|{x-U?lVDTWh)ig@I6Aw4V=ORq#I6-_mX!^|H z>w^0a^BIf{el4V@qn9Jn?RzhvtM1urM-eolRN;b{xYg{)KqUh|RH3FdDRnZE_l%B? zwp!YwF~%eD@TFi(jzK*v#vhOk1yWs!w}%9BP=;ub<(06Z8`c z^od7!=DoY1ted4N^+=$GK!hgnlvcWye0>}h%+>P2JW zb$S0K-)G+MO64vy#@X6RO%PqUIQmkTW6)0N#k*YR^R5}Udcu7Zz8DG#=l4BUAD!=v zx}|n;aimdd}co_bYS8@C!*td1i*wlZZmXJ&Q9%tfAhUQNF!#im#Z4=qHHbDb=Ys1*80 zpWP-IV_AkT+SrpH6L?-wLnt;S3!hTb@jORdlA;+n$yQsA$7~dsJ z&K}&D2s+YD`*_`6N%zC?VtCG6K?ZMn2Dd3S|6x2Q&G@hIrI=5%#GxC@Pc5DzN}K~k z*4TEzPu*T3xh2(Wd`rVLw;Eq>^HfRfsq9E=u{RBYy2Ojpc?QO$Q44@4P)6I^Jd~-=j&pJ4c`|V*p3mTuy7a1Xv3s1^=`;9t+Vk=JP z_Sf#3Xn&pLa7KG&1tFhC9=Y7s@MEhikMT*?{>$6YPG!4)n{ zukh=S5B~aNYosf;HPXe!(hBL}X8VV@Tz`%0yFI1gvPOE?a`AHW{Xmhx8i34SPB5m6 zfKzJDV1-!Ve?_?iA$?t4lJo~X28Afj#n$2Xtst)R!qJX-jLDXTB5S%u%s$7&lk@%| zlo2qcIt}Yi=y_c4L&GGm9cyNAywUuO_dSu*%lBGfxonj@qVC%p6}MWR1ROTYhOw8eVYeowPH645ReV&eFC zW7|dwALe$H6~iVS8qe<#EH3L)oP=V;8Ed-wPc1Vq-`q%aQBCyDuE0-FQq?b`kaMZb zXNZq#h}A<}p9s5~Q@unl=xCKOrE^v8I3qzY5hb(CEFCrxRkcQ{`oOK_$=x=TJ@B}LmD_Q!I9*aZs9XatsUvY2M0dqd=wXxNbA1xzKe#!uuO?# z-d&riqr-YWY34?NmgdVCiK%<{jIjL&{obg<8D|^VgsY0}j7U8h zd}xkl79BKAIpfh%N#Ll{-p4U95a~d5hy`Vj-!`p~chxtcyV{Lh>a#F<{B)0r9o4`k zW~6|OsCxkEHN2EAmYx?1h-U|L|9OtaJ2K!#=G>wZA0&~Ho=$0zm@J(EM_<0X=+!g& ziZPthn9iqkd!Cv6ItK~sSHTk+qSkFI?OWPt-?tdHq*fC|S-fkw9XV^xvvM3;XV=;( z$tQiv&JDCU^}o2PCd(l%rlPooj~Nh1U_GEGE^t6pxPl%S3I4_ZKRvO#2h!cv!^_du zbI;>@q>Vfju)V98W;R6Bn#&@<{D z`RyT-$uV**RXqHC1mIXG2+&aw99H-qC}8iTw_ksZiP&i0vL9BmZ*e>O=fVHJ?O%Qm z*`aLU?MF0qX$@rsc^zGDbpw+tB-(xHUMN(2Ur+H{Gh;Ks8FvzP}Dt~zkOCfah8|sJtKe)K7hrE?ez&vK!tLH zdsJ9ee9IC>sGm%$K!LPbt`hCwt4AHwfg>0ic%nkrfXZ+Im253-Y(0K2qA;`6dm$i< z8`Za68VnQ_3ZuqNlyP*!+E#=LxlzHT9t&na&p^(%J(*$xD%6i2H}W^Wkf30^7l99x zK<){^H{VXM7AP!SxyO6~8rgbkAb+ofx17Um{orN+41l*A1}1=V0y|f@z(5H0a9w(!GHDS;)0MXaX@OUJ0T><{N=VMRz1{4O|_> zm3d`17e_Z+Ef-5`TL+{IsI#7bNrsW9aP%Q4I9*VKnRg^p2~x$C%)bVVey`^)-ifkk z0GtH%mtzO8OaeDh-WPh~1gejQY^~g*fJ+FJ4od$=K!sj7iJSZHX3QVDgZ#LsR_Z{* zdqKO}*&tS=aZ@#|oNTSV6fCX1kRIR7-$2E+K5|Ug5j0<8Fld1K^&_A{qm*%j)&E1q z7#+FU3gE_g&}L!jggX&;OKWFK zds{9?H#;01FG8EJ@;OjOB&ZGBt9Y1z3e9oG&DA>V;g0nDTU}6OP5pQsusMFIX)Rzj zh^ueVwzWfgxLSU5%lC&NWB6j9)N7#j7oZsVFk*uzD)g==Zum#(i<~wQc`P=PUtBa6AAyI1|vqceZZoXxwa_?Q7kCX@6x=RRqUDH)jXvJ1e7a z9B#6_i>>RnN-*6hC<9v$TQ`gkEociWw9X*)0IDYkjU2H<^%Cdd-0{q1M{5Or>YInGoCUP$<++B^H`Bgc}OAS_!2>Z3beguA{iASPOzs z9@MxSmbY&LH}6j~2dI@DEOB@iH}Qwf9jJg%a~@b+)7wAb{$$((ngsO5HZ}>_54e-~ z_5L=L3%xgs8u$P2tC<>1?o}a2L5!;0}6!h z7h{21)VP6|kz?oXF_!)7;@tKY)we(a+u9O6fQMaW!)#b#mYjZ#yuD?GjcoU8?FzDZJX<_K7(0BhU`uheKR*S!1s>P<@bLbZ>LKFcJeme#1 a!mXC-F0ecRgGqxwHL$29$ppS)VE+f9CJS5u diff --git a/pom.xml b/pom.xml index 15d68243b..191ced648 100644 --- a/pom.xml +++ b/pom.xml @@ -606,11 +606,6 @@ xerces xercesImpl 2.11.0 - - - com.collaborne - xliff-core-1.2 - 1.1 com.adobe.xmp diff --git a/source/net/yacy/utils/translation/TranslatorXliff.java b/source/net/yacy/utils/translation/TranslatorXliff.java index d0ccc423c..e44628709 100644 --- a/source/net/yacy/utils/translation/TranslatorXliff.java +++ b/source/net/yacy/utils/translation/TranslatorXliff.java @@ -29,27 +29,22 @@ package net.yacy.utils.translation; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.TreeMap; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.events.XMLEvent; import net.yacy.cora.util.ConcurrentLog; import net.yacy.data.Translator; import net.yacy.search.Switchboard; -import org.oasis.xliff.core_12.Body; -import org.oasis.xliff.core_12.Target; -import org.oasis.xliff.core_12.TransUnit; -import org.oasis.xliff.core_12.Xliff; /** * Wordlist based translator @@ -88,55 +83,70 @@ public class TranslatorXliff extends Translator { * ..... * */ - Xliff xliffTranslation; - try (FileInputStream fis = new FileInputStream(xliffFile)){ // try-with-resource to close inputstream - JAXBContext ctx = JAXBContext.newInstance(org.oasis.xliff.core_12.Xliff.class); - Unmarshaller un = ctx.createUnmarshaller(); - Object obj = un.unmarshal(fis); - if (obj instanceof org.oasis.xliff.core_12.Xliff) { - xliffTranslation = (org.oasis.xliff.core_12.Xliff) obj; - } else { - return null; - } - List xlfFileList = xliffTranslation.getAnyAndFile(); - for (Object xlfobj : xlfFileList) { - org.oasis.xliff.core_12.File xlfFileNode = (org.oasis.xliff.core_12.File) xlfobj; - Map translationList; //current Translation Table (maintaining input order) - String forFile = xlfFileNode.getOriginal(); - if (lngLists.containsKey(forFile)) { - translationList = lngLists.get(forFile); - } else { - translationList = new LinkedHashMap(); //current Translation Table (maintaining input order) - lngLists.put(forFile, translationList); - } + try (FileInputStream fis = new FileInputStream(xliffFile)) { // try-with-resource to close inputstream + + XMLInputFactory factory = XMLInputFactory.newInstance(); + XMLStreamReader xmlreader = factory.createXMLStreamReader(fis); + + Map translationList = null; //current Translation Table (maintaining input order) + String source = null; + String target = null; + String state = null; + while (xmlreader.hasNext()) { + int eventtype = xmlreader.next(); + + if (eventtype == XMLEvent.START_ELEMENT) { + String ename = xmlreader.getLocalName(); - Body xlfBody = xlfFileNode.getBody(); - List xlfTransunitList = xlfBody.getGroupOrTransUnitOrBinUnit(); - for (Object xlfTransunit : xlfTransunitList) { - if (xlfTransunit instanceof TransUnit) { - String source = ((TransUnit) xlfTransunit).getSource().getContent().get(0).toString(); - Target target = ((TransUnit) xlfTransunit).getTarget(); - if (target != null) { - if ("translated".equals(target.getState())) { - List targetContentList = target.getContent(); - String targetContent = targetContentList.get(0).toString(); - translationList.put(source, targetContent); + // setup for 'file' section (get or add translationlist for this file) + if (ename.equalsIgnoreCase("file")) { + String forFile = xmlreader.getAttributeValue(null, "original"); + if (lngLists.containsKey(forFile)) { + translationList = lngLists.get(forFile); + } else { + translationList = new LinkedHashMap(); //current Translation Table (maintaining input order) + lngLists.put(forFile, translationList); + } + source = null; + target = null; + } else if (ename.equalsIgnoreCase("trans-unit")) { // prepare for trans-unit + source = null; + target = null; + } else if (ename.equalsIgnoreCase("source")) { // get source text + source = xmlreader.getElementText(); + } else if (ename.equalsIgnoreCase("target")) { // get target text + state = xmlreader.getAttributeValue(null, "state"); + target = xmlreader.getElementText(); // TODO: in full blown xliff, target may contain sub-xml elements (but we use only text) + } + } else if (eventtype == XMLEvent.END_ELEMENT) { + String ename = xmlreader.getLocalName(); + + // store source/target on finish of trans-unit + if (ename.equalsIgnoreCase("trans-unit") && translationList != null) { + if (source != null) { + if (target != null) { + if ("translated".equals(state)) { + translationList.put(source, target); + } else { + translationList.put(source, null); + } } else { translationList.put(source, null); } - } else { - translationList.put(source, null); + source = null; } + target = null; + } + // on file end-tag make sure nothing is added (on error in xml) + if (ename.equalsIgnoreCase("file")) { + translationList = null; } } } - } catch (JAXBException je) { - ConcurrentLog.warn("TRANSLATOR", je.getMessage()); - } catch (FileNotFoundException ex) { - ConcurrentLog.warn("TRANSLATOR", "File not found: " + xliffFile.getAbsolutePath()); - } catch (IOException ex) { - ConcurrentLog.warn("TRANSLATOR", ex.getMessage()); + xmlreader.close(); + } catch (IOException | XMLStreamException ex) { + ConcurrentLog.warn("TRANSLATOR", "error reading " + xliffFile.getAbsolutePath() + " -> " + ex.getMessage()); } return lngLists; } From f0317d67158d442794f5d01d695678799db4e487 Mon Sep 17 00:00:00 2001 From: Sergey Stepanov Date: Sun, 28 Aug 2016 23:46:05 +0300 Subject: [PATCH 06/33] to add Russian synonyms requires health checks --- htroot/DictionaryLoader_p.java | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/htroot/DictionaryLoader_p.java b/htroot/DictionaryLoader_p.java index 58e4feafb..87c6e6d6c 100644 --- a/htroot/DictionaryLoader_p.java +++ b/htroot/DictionaryLoader_p.java @@ -49,6 +49,8 @@ public class DictionaryLoader_p { final File synonym_de_production = new File(synonyms_path, synonym_de_default.getName()); final File synonym_en_default = new File(new File(new File(sb.appPath, "addon"), "synonyms"), "mobythesaurus_en_yacy"); final File synonym_en_production = new File(synonyms_path, synonym_en_default.getName()); + final File synonym_ru_default = new File(new File(new File(sb.appPath, "addon"), "synonyms"), "thesaurus_ru_yacy"); + final File synonym_ru_production = new File(synonyms_path, synonym_ru_default.getName()); /* * distinguish the following cases: * - dictionary file was not loaded -> actions: load the file @@ -70,7 +72,7 @@ public class DictionaryLoader_p { // check here only if there is no possibility synonym libraries have been activated/deactivated prop.put("syn0Status", synonym_de_production.exists() ? 1 : 0); prop.put("syn1Status", synonym_en_production.exists() ? 1 : 0); - + prop.put("syn2Status", synonym_ru_production.exists() ? 1 : 0); return prop; } @@ -322,11 +324,25 @@ public class DictionaryLoader_p { } SynonymLibrary.init(synonyms_path); } - + + if (post.containsKey("syn0Deactivate")) { + synonym_de_production.delete(); + SynonymLibrary.init(synonyms_path); + } + + if (post.containsKey("syn2Activate")) { + try { + FileUtils.copy(new FileInputStream(synonym_ru_default), synonym_ru_production); + } catch (IOException e) { + ConcurrentLog.logException(e); + } + SynonymLibrary.init(synonyms_path); + } if (post != null) { // check here if there is a possibility synonym libraries have been activated/deactivated prop.put("syn0Status", synonym_de_production.exists() ? 1 : 0); prop.put("syn1Status", synonym_en_production.exists() ? 1 : 0); + prop.put("syn2Status", synonym_ru_production.exists() ? 1 : 0); } // check status again From a9d0c64505244928699319e384f5d8d9b62f0b5b Mon Sep 17 00:00:00 2001 From: Sergey Stepanov Date: Sun, 28 Aug 2016 23:54:06 +0300 Subject: [PATCH 07/33] to add Russian synonyms requires health checks --- htroot/DictionaryLoader_p.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/htroot/DictionaryLoader_p.html b/htroot/DictionaryLoader_p.html index 95446429b..a37d8647f 100644 --- a/htroot/DictionaryLoader_p.html +++ b/htroot/DictionaryLoader_p.html @@ -231,6 +231,16 @@
Action
#(syn1Status)#::#(/syn1Status)#
+ +

Russian Thesaurus

+

The data was converted to the YaCy synonym file format and part of the YaCy distribution.

+ +
+
+
#(syn2Status)#
Deactivated
::
Activated
#(/syn2Status)#
+
Action
+
#(syn2Status)#::#(/syn2Status)#
+
#%env/templates/footer.template%# From f8d6543a239b3a8bda860154c8fe6915cfe9a6fa Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 28 Aug 2016 23:08:03 +0200 Subject: [PATCH 08/33] Rename class CreateTranslationMaster to TranslationManager and add additional routines and the capability to handle translation maps internally (to reduce complexity of handling translation maps for calling servelets) --- .classpath | 1 - htroot/Translator_p.java | 20 +-- ...onMasters.java => TranslationManager.java} | 115 +++++++++++++++--- 3 files changed, 108 insertions(+), 28 deletions(-) rename source/net/yacy/utils/translation/{CreateTranslationMasters.java => TranslationManager.java} (69%) diff --git a/.classpath b/.classpath index 6fc97e48f..ebace4e59 100644 --- a/.classpath +++ b/.classpath @@ -100,6 +100,5 @@ - diff --git a/htroot/Translator_p.java b/htroot/Translator_p.java index c29800274..e8d6998cd 100644 --- a/htroot/Translator_p.java +++ b/htroot/Translator_p.java @@ -29,7 +29,7 @@ import net.yacy.search.SwitchboardConstants; import net.yacy.server.serverObjects; import net.yacy.server.serverSwitch; import net.yacy.server.servletProperties; -import net.yacy.utils.translation.CreateTranslationMasters; +import net.yacy.utils.translation.TranslationManager; public class Translator_p { @@ -48,13 +48,13 @@ public class Translator_p { } File lngfile = new File(sb.getAppPath("locale.source", "locales"), langcfg + ".lng"); - CreateTranslationMasters ctm = new CreateTranslationMasters(/*new File ("locales","master.lng.xlf")*/); + TranslationManager localTransMgr = new TranslationManager(/*new File ("locales","master.lng.xlf")*/); File masterxlf = new File(sb.getAppPath("locale.source", "locales"), "master.lng.xlf"); - if (!masterxlf.exists()) ctm.createMasterTranslationLists(masterxlf); - Map> origTrans = ctm.joinMasterTranslationLists(masterxlf, lngfile); - final File locallngfile = ctm.getScratchFile(lngfile); - Map> localTrans = ctm.loadTranslationsLists(locallngfile); // TODO: this will read file twice + if (!masterxlf.exists()) localTransMgr.createMasterTranslationLists(masterxlf); + Map> origTrans = localTransMgr.joinMasterTranslationLists(masterxlf, lngfile); + final File locallngfile = localTransMgr.getScratchFile(lngfile); + Map> localTrans = localTransMgr.loadTranslationsLists(locallngfile); // TODO: this will read file twice int i = 0; if (origTrans.size() > 0) { String filename = origTrans.keySet().iterator().next(); @@ -114,7 +114,7 @@ public class Translator_p { if (i == textlistid && post != null) { if (editapproved) { // switch already translated in edit mode by copying to local translation // not saved here as not yet modified/approved - ctm.addTranslation(localTrans, filename, sourcetext, targettxt); + localTransMgr.addTranslation(localTrans, filename, sourcetext, targettxt); } else { String t = post.get("targettxt" + Integer.toString(textlistid)); // correct common partial html markup (part of text identification for words also used as html parameter) @@ -125,7 +125,7 @@ public class Translator_p { targettxt = t; // add changes to original (for display) and local (for save) origTextList.put(sourcetext, targettxt); - changed = ctm.addTranslation(localTrans, filename, sourcetext, targettxt); + changed = localTransMgr.addTranslation(localTrans, filename, sourcetext, targettxt); } } prop.putHTML("textlist_" + i + "_sourcetxt", sourcetext); @@ -138,7 +138,7 @@ public class Translator_p { changed = true; } if (changed) { - ctm.saveAsLngFile(langcfg, locallngfile, localTrans); + localTransMgr.saveAsLngFile(langcfg, locallngfile, localTrans); // adhoc translate this file // 1. get/calc the path final String htRootPath = env.getConfig(SwitchboardConstants.HTROOT_PATH, SwitchboardConstants.HTROOT_PATH_DEFAULT); @@ -147,7 +147,7 @@ public class Translator_p { // get absolute file by adding relative filename from translationlist final File sourceFile = new File(sourceDir, filename); final File destFile = new File(destDir, filename); - ctm.translateFile(sourceFile, destFile, origTextList); // do the translation + localTransMgr.translateFile(sourceFile, destFile, origTextList); // do the translation } } prop.put("textlist", i); diff --git a/source/net/yacy/utils/translation/CreateTranslationMasters.java b/source/net/yacy/utils/translation/TranslationManager.java similarity index 69% rename from source/net/yacy/utils/translation/CreateTranslationMasters.java rename to source/net/yacy/utils/translation/TranslationManager.java index 9e08f1b2c..8cd1fd6f8 100644 --- a/source/net/yacy/utils/translation/CreateTranslationMasters.java +++ b/source/net/yacy/utils/translation/TranslationManager.java @@ -1,4 +1,4 @@ -// CreateTranslationMasters.java +// TranslationManager.java // ------------------------------------- // part of YACY // (C) by Michael Peter Christen; mc@yacy.net @@ -47,7 +47,30 @@ import net.yacy.data.Translator; * Also can join existing translation with master (currently ristrictive, * means only translation text exist in master are included in resultin Map */ -public class CreateTranslationMasters extends TranslatorXliff { +public class TranslationManager extends TranslatorXliff { + + protected Map> mainTransLists; // current translation entries for one language + + public TranslationManager() { + super(); + } + + public TranslationManager(final File langfile) { + mainTransLists = loadTranslationsLists(langfile); + } + + /** + * Add a translation text to the current map map + * + * @param relFileName relative filename the translation belongs to + * @param sourceLngTxt the english source text + * @param targetLngTxt the translated text + * @return true = if map was modified, otherwise false + */ + public boolean addTranslation(final String relFileName, final String sourceLngTxt, final String targetLngTxt) { + assert mainTransLists != null; + return addTranslation (mainTransLists, relFileName, sourceLngTxt, targetLngTxt); + } /** * Helper to add a translation text to the map @@ -79,6 +102,29 @@ public class CreateTranslationMasters extends TranslatorXliff { return modified; } + /** + * Get the translation list for a ui/html file + * @param filename relative path to htroot + * @return translation map or null + */ + public Map getTranslationForFile(String filename) { + return mainTransLists.get(filename); + } + + /** + * Get a translation target text + * @param filename of the translation + * @param source english source text + * @return translated text or null + */ + public String getTranslation (String filename, String source) { + Map tmp = mainTransLists.get(filename); + if (tmp != null) + return tmp.get(source); + else + return null; + } + /** * Create a master translation list by reading all translation files * If a masterOutputFile exists, content is preserved (loaded first) @@ -87,11 +133,10 @@ public class CreateTranslationMasters extends TranslatorXliff { * @throws IOException */ public void createMasterTranslationLists(File masterOutputFile) throws IOException { - Map> xliffTrans; if (masterOutputFile.exists()) // if file exists, conserve existing master content (may be updated by external tool) - xliffTrans = loadTranslationsListsFromXliff(masterOutputFile); + mainTransLists = loadTranslationsListsFromXliff(masterOutputFile); else - xliffTrans = new TreeMap>(); + mainTransLists = new TreeMap>(); List lngFiles = Translator.langFiles(new File("locales")); for (String filename : lngFiles) { @@ -130,7 +175,7 @@ public class CreateTranslationMasters extends TranslatorXliff { // it is possible that intentionally empty translation is given // in this case xliff target is missing (=null) if (origVal != null && !origVal.isEmpty()) { // if translation exists - addTranslation(xliffTrans, transfilename, sourcetxt, null); // add to master, set target text null + addTranslation(transfilename, sourcetxt, null); // add to master, set target text null } } } @@ -140,11 +185,14 @@ public class CreateTranslationMasters extends TranslatorXliff { } } // save as xliff file w/o language code - saveAsXliff(null, masterOutputFile, xliffTrans); + saveAsXliff(null, masterOutputFile, mainTransLists); } /** - * Joins translation master (xliff) and existing translation (lng) + * Joins translation master (xliff) and existing translation (lng). + * Only texts existing in master are included from the lngfile, + * the resulting map includes all keys from master with the matching translation + * from lngfile. * * @param xlifmaster master (with en text to be translated) * @param lngfile existing translation @@ -154,7 +202,7 @@ public class CreateTranslationMasters extends TranslatorXliff { public Map> joinMasterTranslationLists(File xlifmaster, File lngfile) throws IOException { final String filename = lngfile.getName(); - Map> xliffTrans = loadTranslationsListsFromXliff(xlifmaster); + mainTransLists = loadTranslationsListsFromXliff(xlifmaster); // load translation list ConcurrentLog.info("TRANSLATOR", "join into master translation file " + filename); Map> origTrans = loadTranslationsLists(lngfile); @@ -162,19 +210,51 @@ public class CreateTranslationMasters extends TranslatorXliff { for (String transfilename : origTrans.keySet()) { // get translation filename // compare translation list Map origList = origTrans.get(transfilename); - Map masterList = xliffTrans.get(transfilename); + Map masterList = mainTransLists.get(transfilename); for (String sourcetxt : origList.keySet()) { if ((masterList != null) && (masterList.isEmpty() || masterList.containsKey(sourcetxt))) { // only if included in master (as all languages are in there but checked for occuance String origVal = origList.get(sourcetxt); // it is possible that intentionally empty translation is given // in this case xliff target is missing (=null) if (origVal != null && !origVal.isEmpty()) { - addTranslation(xliffTrans, transfilename, sourcetxt, origVal); + addTranslation(transfilename, sourcetxt, origVal); } } } } - return xliffTrans; + return mainTransLists; + } + + /** + * Stores the loaded translations to a .lng file + * @param lng + * @param f + * @return + */ + public boolean saveXliff(final String lng, File f) { + return this.saveAsXliff(lng, f, mainTransLists); + } + + /** + * Stores the loaded translations to a .xlf file + * @param lng + * @param f + * @return + */ + public boolean saveLng(final String lng, File f) { + return this.saveAsLngFile(lng, f, mainTransLists); + } + + /** + * Total number of loaded translation entries + * @return + */ + public int size() { + int i = 0; + for (Map trans : mainTransLists.values()) { + i += trans.size(); + } + return i; } /** @@ -183,12 +263,13 @@ public class CreateTranslationMasters extends TranslatorXliff { * @param args */ public static void main(String args[]) { - File outputdirectory = new File ("test/DATA"); - - CreateTranslationMasters ctm = new CreateTranslationMasters(); + File outputdirectory = new File("test/DATA"); + if (!outputdirectory.exists()) { + outputdirectory.mkdir(); + } + File xlfmaster = new File(outputdirectory, "master.lng.xlf"); + TranslationManager ctm = new TranslationManager(xlfmaster); try { - if (!outputdirectory.exists()) outputdirectory.mkdir(); - File xlfmaster = new File(outputdirectory, "master.lng.xlf"); ctm.createMasterTranslationLists(xlfmaster); // write the language neutral translation master as xliff List lngFiles = Translator.langFiles(new File("locales")); From 290ca9e91494e1c2cc644e65d61557f434d45c9f Mon Sep 17 00:00:00 2001 From: reger Date: Mon, 29 Aug 2016 01:27:09 +0200 Subject: [PATCH 09/33] make error msg part of html (allowing translation) instead of hardcoded text --- htroot/Translator_p.html | 2 +- htroot/Translator_p.java | 4 ++-- locales/master.lng.xlf | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/htroot/Translator_p.html b/htroot/Translator_p.html index 131af2999..f37527bea 100644 --- a/htroot/Translator_p.html +++ b/htroot/Translator_p.html @@ -17,7 +17,7 @@ -

Target Language: #[targetlang]#

#[errmsg]#

+

Target Language: #[targetlang]#

#(errmsg)#::

activate a different language here

#(/errmsg)# +   You can check your outgoing messages here +

+ + #{results}# + + +
+ + + + + + + + #(existing)#xxx:: + + + + #(/existing)# + + + + + + + +
File:#[filename]#Originator
English:#[source]#
existing#[target]#
Translation:#[target]##[peername]#
score #[score]#  + + +    + + +   Vote on this translation. If you vote positive the translation is added to your local translation list. +
+
+ + + #{/results}# +

+
+

+ + #%env/templates/footer.template%# + + diff --git a/htroot/TransNews_p.java b/htroot/TransNews_p.java new file mode 100644 index 000000000..829c0d488 --- /dev/null +++ b/htroot/TransNews_p.java @@ -0,0 +1,323 @@ +// TransNews_p.java +// +// This is a part of YaCy, a peer-to-peer based web search engine +// published on http://yacy.net +// +// This file is contributed by Burkhard Buelte +// +// $LastChangedDate$ +// $LastChangedRevision$ +// $LastChangedBy$ +// +// LICENSE +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import net.yacy.cora.protocol.RequestHeader; +import net.yacy.cora.sorting.ConcurrentScoreMap; +import net.yacy.cora.sorting.ScoreMap; +import net.yacy.cora.util.SpaceExceededException; +import net.yacy.peers.NewsDB; +import net.yacy.peers.NewsPool; +import net.yacy.search.Switchboard; +import net.yacy.server.serverObjects; +import net.yacy.server.serverSwitch; +import net.yacy.utils.crypt; +import net.yacy.utils.translation.TranslationManager; +import net.yacy.utils.translation.TranslatorXliff; + +public class TransNews_p { + + public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) { + final Switchboard sb = (Switchboard) env; + final serverObjects prop = new serverObjects(); + + String currentlang = sb.getConfig("locale.language", "default"); + prop.put("currentlang", currentlang); + TranslatorXliff transx = new TranslatorXliff(); + + File langFile = transx.getScratchFile(new File(currentlang + ".lng")); + TranslationManager trans = new TranslationManager(langFile); + prop.put("transsize", trans.size()); + + // read voting + if ((post != null) && post.containsKey("publishtranslation")) { + Map> localTrans = trans.loadTranslationsLists(langFile); + Iterator filenameit = localTrans.keySet().iterator(); + while (filenameit.hasNext()) { + String file = filenameit.next(); + Map tmptrans = localTrans.get(file); + for (String sourcetxt : tmptrans.keySet()) { + String targettxt = tmptrans.get(sourcetxt); + if (targettxt != null && !targettxt.isEmpty()) { + boolean sendit = true; + // check if already published (in newsPool) + Iterator it = sb.peers.newsPool.recordIterator(NewsPool.INCOMING_DB); + while (it.hasNext()) { + NewsDB.Record rtmp = it.next(); + if (rtmp == null) { + continue; + } + if (NewsPool.CATEGORY_TRANSLATION_ADD.equals(rtmp.category())) { + String tmplng = rtmp.attribute("language", null); + String tmpfile = rtmp.attribute("file", null); + String tmpsource = rtmp.attribute("source", null); + String tmptarget = rtmp.attribute("target", null); + + if (sb.peers.mySeed().hash.equals(rtmp.originator())) { + /* + if (tmplng != null && tmplng.equals(currentlang)) { + sendit = false; + break; + }*/ + if (tmpfile != null && tmpfile.equals(file)) { + sendit = false; + break; + } + if (tmpsource != null && tmpsource.equals(sourcetxt)) { + sendit = false; + break; + } + if (tmptarget != null && tmptarget.equals(targettxt)) { + sendit = false; + break; + } + } + // if news with file and source exist (maybe from other peer) - skip sending another msg (to avoid confusion) + if ((tmpfile != null && tmpfile.equals(file)) + && (tmpsource != null && tmpsource.equals(sourcetxt))) { + sendit = false; + break; + } + + } + } + if (sendit) { + final HashMap map = new HashMap(); + map.put("language", currentlang); + map.put("file", file); + map.put("source", sourcetxt); + map.put("target", targettxt); + sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_TRANSLATION_ADD, map); + } + } + } + } + } + String refid; + if ((post != null) && ((refid = post.get("voteNegative", null)) != null)) { + + // make new news message with voting + if (!sb.isRobinsonMode()) { + final HashMap map = new HashMap(); + map.put("language", currentlang); + map.put("file", crypt.simpleDecode(post.get("filename", ""))); + map.put("source", crypt.simpleDecode(post.get("source", ""))); + map.put("target", crypt.simpleDecode(post.get("target", ""))); + map.put("vote", "negative"); + map.put("refid", refid); + sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_TRANSLATION_VOTE_ADD, map); + try { + sb.peers.newsPool.moveOff(NewsPool.INCOMING_DB, refid); + } catch (IOException | SpaceExceededException ex) { + } + } + } + + if ((post != null) && ((refid = post.get("votePositive", null)) != null)) { + if (!sb.verifyAuthentication(header)) { + prop.authenticationRequired(); + return prop; + } + // add to local translation extension + if (trans.addTranslation(post.get("filename"), post.get("source"), post.get("target"))) { + File f = new File(currentlang + ".lng"); + f = trans.getScratchFile(f); + trans.saveLng(currentlang, f); + } + + // make new news message with voting + final HashMap map = new HashMap(); + + map.put("language", currentlang); + map.put("file", crypt.simpleDecode(post.get("filename", ""))); + map.put("source", crypt.simpleDecode(post.get("source", ""))); + map.put("target", crypt.simpleDecode(post.get("target", ""))); + map.put("vote", "positive"); + map.put("refid", refid); + sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_TRANSLATION_VOTE_ADD, map); + try { + sb.peers.newsPool.moveOff(NewsPool.INCOMING_DB, refid); + } catch (IOException | SpaceExceededException ex) { + } + } + + // create Translation voting list + final HashMap negativeHashes = new HashMap(); // a mapping from an url hash to Integer (count of votes) + final HashMap positiveHashes = new HashMap(); // a mapping from an url hash to Integer (count of votes) + accumulateVotes(sb, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); + final ScoreMap ranking = new ConcurrentScoreMap(); // score cluster for url hashes + final HashMap Translation = new HashMap(); // a mapping from an url hash to a kelondroRow.Entry with display properties + accumulateTranslations(sb, Translation, ranking, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); + + // read out translation-news array and create property entries + final Iterator k = ranking.keys(false); + int i = 0; + NewsDB.Record row; + String filename; + String source; + String target; + + while (k.hasNext()) { + String existingtarget = null; + refid = k.next(); + if (refid == null) { + continue; + } + + row = Translation.get(refid); + if (row == null) { + continue; + } + + String lang = row.attribute("language", null); + filename = row.attribute("file", null); + source = row.attribute("source", null); + target = row.attribute("target", null); + if ((lang == null) || (filename == null) || (source == null) || (target == null)) { + continue; + } + + existingtarget = trans.getTranslation(filename, source); + + boolean altexist = existingtarget != null && !target.isEmpty() && !existingtarget.isEmpty() && !existingtarget.equals(target); + + prop.put("results_" + i + "_refid", refid); + prop.put("results_" + i + "_url", filename); // url to local file + prop.put("results_" + i + "_targetlanguage", lang); + prop.put("results_" + i + "_filename", filename); + prop.putHTML("results_" + i + "_source", source); + prop.putHTML("results_" + i + "_target", target); + prop.put("results_" + i + "_existing", altexist); + prop.putHTML("results_" + i + "_existing_target", existingtarget); + prop.put("results_" + i + "_score", ranking.get(refid)); + prop.put("results_" + i + "_peername", sb.peers.get(row.originator()).getName()); + i++; + + if (i >= 50) { + break; + } + } + prop.put("results", i); + + return prop; + } + + private static void accumulateVotes(final Switchboard sb, final HashMap negativeHashes, final HashMap positiveHashes, final int dbtype) { + final int maxCount = Math.min(1000, sb.peers.newsPool.size(dbtype)); + NewsDB.Record newsrecord; + final Iterator recordIterator = sb.peers.newsPool.recordIterator(dbtype); + int j = 0; + while ((recordIterator.hasNext()) && (j++ < maxCount)) { + newsrecord = recordIterator.next(); + if (newsrecord == null) { + continue; + } + + if (newsrecord.category().equals(NewsPool.CATEGORY_TRANSLATION_VOTE_ADD)) { + final String refid = newsrecord.attribute("refid", ""); + final String vote = newsrecord.attribute("vote", ""); + final int factor = ((dbtype == NewsPool.OUTGOING_DB) || (dbtype == NewsPool.PUBLISHED_DB)) ? 2 : 1; + if (vote.equals("negative")) { + final Integer i = negativeHashes.get(refid); + if (i == null) { + negativeHashes.put(refid, Integer.valueOf(factor)); + } else { + negativeHashes.put(refid, Integer.valueOf(i.intValue() + factor)); + } + } + if (vote.equals("positive")) { + final Integer i = positiveHashes.get(refid); + if (i == null) { + positiveHashes.put(refid, Integer.valueOf(factor)); + } else { + positiveHashes.put(refid, Integer.valueOf(i.intValue() + factor)); + } + } + } + } + } + + private static void accumulateTranslations( + final Switchboard sb, + final HashMap translationmsg, final ScoreMap ranking, + final HashMap negativeHashes, final HashMap positiveHashes, final int dbtype) { + final int maxCount = Math.min(1000, sb.peers.newsPool.size(dbtype)); + NewsDB.Record newsrecord; + final Iterator recordIterator = sb.peers.newsPool.recordIterator(dbtype); + int j = 0; + String refid = ""; + String targetlanguage =""; + String filename=""; + String source=""; + String target=""; + + int score = 0; + Integer vote; + + while ((recordIterator.hasNext()) && (j++ < maxCount)) { + newsrecord = recordIterator.next(); + if (newsrecord == null) { + continue; + } + + if ((newsrecord.category().equals(NewsPool.CATEGORY_TRANSLATION_ADD)) + && ((sb.peers.get(newsrecord.originator())) != null)) { + refid = newsrecord.id(); + targetlanguage = newsrecord.attribute("language", ""); + filename = newsrecord.attribute("file", ""); + source = newsrecord.attribute("source", ""); + target = newsrecord.attribute("target", ""); + if (refid.isEmpty() || targetlanguage.isEmpty() || filename.isEmpty() || source.isEmpty() || target.isEmpty()) { + continue; + } + score = 0; + } + + // add/subtract votes and write record + + if ((vote = negativeHashes.get(refid)) != null) { + score -= vote.intValue(); + } + if ((vote = positiveHashes.get(refid)) != null) { + score += vote.intValue(); + } + // consider double-entries + if (translationmsg.containsKey(refid)) { + ranking.inc(refid, score); + } else { + ranking.set(refid, score); + translationmsg.put(refid, newsrecord); + } + + } + } +} diff --git a/htroot/Translator_p.html b/htroot/Translator_p.html index f37527bea..1025e6bc0 100644 --- a/htroot/Translator_p.html +++ b/htroot/Translator_p.html @@ -46,7 +46,7 @@ - +

Check for remote translation proposals and/or share your own added translations Translation News

#%env/templates/footer.template%# diff --git a/source/net/yacy/peers/NewsPool.java b/source/net/yacy/peers/NewsPool.java index 7d946359a..1c40ee086 100644 --- a/source/net/yacy/peers/NewsPool.java +++ b/source/net/yacy/peers/NewsPool.java @@ -206,6 +206,18 @@ public class NewsPool { */ private static final String CATEGORY_BLOG_DEL = "blog_del"; + /* ------------------------------------------------------------------------ + * TRANSLATION related CATEGORIES + * ------------------------------------------------------------------------ */ + /** + * a translation was added + */ + public static final String CATEGORY_TRANSLATION_ADD = "transadd"; + /** + * a vote on a translation + */ + public static final String CATEGORY_TRANSLATION_VOTE_ADD = "transavt"; + /* ======================================================================== * ARRAY of valid CATEGORIES * ======================================================================== */ @@ -250,7 +262,11 @@ public class NewsPool { // BLOG related CATEGORIES CATEGORY_BLOG_ADD, - CATEGORY_BLOG_DEL + CATEGORY_BLOG_DEL, + + // TRANSLATION related CATEGORIES + CATEGORY_TRANSLATION_ADD, + CATEGORY_TRANSLATION_VOTE_ADD }; private static final Set categories = new HashSet(); static { @@ -398,28 +414,35 @@ public class NewsPool { return pc; } + /** + * Check max keep duration depending on news category and return true if duration + * is exceeded + * + * @param seedDB + * @param record + * @return true if news should be removed + */ private static boolean automaticProcessP(final SeedDB seedDB, final NewsDB.Record record) { if (record == null) return false; if (record.category() == null) return true; + final long created = record.created().getTime(); - if ((System.currentTimeMillis() - created) > (6L * MILLISECONDS_PER_HOUR)) { - // remove everything after 1 day - return true; - } + final long duration = System.currentTimeMillis() - created; + if ((record.category().equals(CATEGORY_WIKI_UPDATE)) && - ((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) { + (duration > (3L * MILLISECONDS_PER_DAY))) { return true; } if ((record.category().equals(CATEGORY_BLOG_ADD)) && - ((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) { + (duration > (3L * MILLISECONDS_PER_DAY))) { return true; } if ((record.category().equals(CATEGORY_PROFILE_UPDATE)) && - ((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) { + (duration > (3L * MILLISECONDS_PER_DAY))) { return true; } if ((record.category().equals(CATEGORY_CRAWL_START)) && - ((System.currentTimeMillis() - created) > (3L * MILLISECONDS_PER_DAY))) { + (duration > (3L * MILLISECONDS_PER_DAY))) { final Seed seed = seedDB.get(record.originator()); if (seed == null) return true; try { @@ -428,6 +451,14 @@ public class NewsPool { return true; } } + if ((record.category().equals(CATEGORY_TRANSLATION_ADD) || record.category().equals(CATEGORY_TRANSLATION_VOTE_ADD)) + && (duration > (7L * MILLISECONDS_PER_DAY))) { + return true; + } + if (duration > MILLISECONDS_PER_DAY) { + // remove everything else after 1 day + return true; + } return false; } From 8c46cb8e8033db7b5b787958f3b9caa422751f0f Mon Sep 17 00:00:00 2001 From: reger Date: Mon, 29 Aug 2016 02:36:55 +0200 Subject: [PATCH 11/33] -fix Supporter log line (instead of System.out) -TransNews_p.html del debugging text left-over --- htroot/Supporter.java | 3 ++- htroot/TransNews_p.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/htroot/Supporter.java b/htroot/Supporter.java index 391a0c9a5..02f44197b 100644 --- a/htroot/Supporter.java +++ b/htroot/Supporter.java @@ -38,6 +38,7 @@ import net.yacy.cora.order.NaturalOrder; import net.yacy.cora.protocol.RequestHeader; import net.yacy.cora.sorting.ConcurrentScoreMap; import net.yacy.cora.sorting.ScoreMap; +import net.yacy.cora.util.ConcurrentLog; import net.yacy.kelondro.index.Row; import net.yacy.kelondro.index.Row.Entry; import net.yacy.peers.NewsDB; @@ -258,7 +259,7 @@ public class Supporter { urlhash = null; } if (urlhash==null) { - System.out.println("Supporter: bad url '" + url + "' from news record " + record.toString()); + ConcurrentLog.info("Supporter", "bad url '" + url + "' from news record " + record.toString()); continue; } if ((vote = negativeHashes.get(urlhash)) != null) { diff --git a/htroot/TransNews_p.html b/htroot/TransNews_p.html index 7a154c9dc..e2efd38f6 100644 --- a/htroot/TransNews_p.html +++ b/htroot/TransNews_p.html @@ -28,7 +28,7 @@ English:#[source]# - #(existing)#xxx:: + #(existing)#:: existing#[target]# From 02129a04dd4afd26685a317b65cdd434205fb36b Mon Sep 17 00:00:00 2001 From: reger Date: Mon, 29 Aug 2016 22:10:21 +0200 Subject: [PATCH 12/33] upd master.lng (add TransNews_p.html) --- locales/master.lng.xlf | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/locales/master.lng.xlf b/locales/master.lng.xlf index f64231d5b..560321f72 100644 --- a/locales/master.lng.xlf +++ b/locales/master.lng.xlf @@ -8621,6 +8621,65 @@ + + + + Translation News for Language + + + Translation News + + + You can share your local addition to translations and distribute it to other peers. + + + The remote peer can vote on your translation and add it to the own local translation. + + + entries available + + + "Publish" + + + You can check your outgoing messages + + + >here< + + + File: + + + >Originator< + + + English: + + + >existing< + + + Translation: + + + >score + + + negative vote + + + positive vote + + + Vote on this translation. + + + If you vote positive the translation is added to your local translation list. + + + + From 5e72d37f0a36672a4858765a56413a7691176082 Mon Sep 17 00:00:00 2001 From: reger Date: Tue, 30 Aug 2016 00:06:42 +0200 Subject: [PATCH 13/33] TransNews_p: add ad-hoc translation of target file on positive vote (additon to local translation) + errmsg on language=default --- htroot/TransNews_p.html | 1 + htroot/TransNews_p.java | 59 +++++++++++------ .../utils/translation/TranslationManager.java | 65 ++++++++++--------- 3 files changed, 76 insertions(+), 49 deletions(-) diff --git a/htroot/TransNews_p.html b/htroot/TransNews_p.html index e2efd38f6..d5b755c02 100644 --- a/htroot/TransNews_p.html +++ b/htroot/TransNews_p.html @@ -17,6 +17,7 @@   You can check your outgoing messages here

+ #(errmsg)#::

Please activate a different language here

#(/errmsg)# #{results}# diff --git a/htroot/TransNews_p.java b/htroot/TransNews_p.java index 829c0d488..5a2ab53aa 100644 --- a/htroot/TransNews_p.java +++ b/htroot/TransNews_p.java @@ -52,15 +52,28 @@ public class TransNews_p { String currentlang = sb.getConfig("locale.language", "default"); prop.put("currentlang", currentlang); - TranslatorXliff transx = new TranslatorXliff(); - File langFile = transx.getScratchFile(new File(currentlang + ".lng")); - TranslationManager trans = new TranslationManager(langFile); - prop.put("transsize", trans.size()); + if ("default".equals(currentlang) || "browser".equals(currentlang)) { + prop.put("errmsg", 1); // msg: activate diff lng + prop.put("transsize", 0); + return prop; + } else { + prop.put("errmsg", 0); + } + + TranslationManager transMgr = new TranslationManager(); + File locallangFile = transMgr.getScratchFile(new File(currentlang + ".lng")); + Map> localTrans = transMgr.loadTranslationsLists(locallangFile); + // calculate size of local translations list + int size = 0; + for (Map lst : localTrans.values()) { + size += lst.size(); + } + prop.put("transsize", size); + // read voting if ((post != null) && post.containsKey("publishtranslation")) { - Map> localTrans = trans.loadTranslationsLists(langFile); Iterator filenameit = localTrans.keySet().iterator(); while (filenameit.hasNext()) { String file = filenameit.next(); @@ -77,7 +90,7 @@ public class TransNews_p { continue; } if (NewsPool.CATEGORY_TRANSLATION_ADD.equals(rtmp.category())) { - String tmplng = rtmp.attribute("language", null); + //String tmplng = rtmp.attribute("language", null); String tmpfile = rtmp.attribute("file", null); String tmpsource = rtmp.attribute("source", null); String tmptarget = rtmp.attribute("target", null); @@ -143,22 +156,22 @@ public class TransNews_p { } if ((post != null) && ((refid = post.get("votePositive", null)) != null)) { - if (!sb.verifyAuthentication(header)) { - prop.authenticationRequired(); - return prop; - } - // add to local translation extension - if (trans.addTranslation(post.get("filename"), post.get("source"), post.get("target"))) { - File f = new File(currentlang + ".lng"); - f = trans.getScratchFile(f); - trans.saveLng(currentlang, f); - } + + final String filename = post.get("filename"); + + File lngfile = new File(sb.getAppPath("locale.source", "locales"), currentlang + ".lng"); + transMgr = new TranslationManager(lngfile); // load full language for check if entry is new (globally) + if (transMgr.addTranslation(filename, post.get("source"), post.get("target"))) { + // add to local translation extension + transMgr.addTranslation(localTrans, filename, post.get("source"), post.get("target")); + transMgr.saveAsLngFile(currentlang, locallangFile, localTrans); // save local-trans to local-file + transMgr.translateFile(filename); // ad-hoc translate file with new/added text + } // TODO: shall we post voting if translation is not new ? // make new news message with voting final HashMap map = new HashMap(); - map.put("language", currentlang); - map.put("file", crypt.simpleDecode(post.get("filename", ""))); + map.put("file", crypt.simpleDecode(filename)); map.put("source", crypt.simpleDecode(post.get("source", ""))); map.put("target", crypt.simpleDecode(post.get("target", ""))); map.put("vote", "positive"); @@ -168,6 +181,7 @@ public class TransNews_p { sb.peers.newsPool.moveOff(NewsPool.INCOMING_DB, refid); } catch (IOException | SpaceExceededException ex) { } + } // create Translation voting list @@ -187,7 +201,7 @@ public class TransNews_p { String target; while (k.hasNext()) { - String existingtarget = null; + refid = k.next(); if (refid == null) { continue; @@ -206,7 +220,12 @@ public class TransNews_p { continue; } - existingtarget = trans.getTranslation(filename, source); + + String existingtarget = null; //transMgr.getTranslation(filename, source); + Map tmpMap = localTrans.get(filename); + if (tmpMap != null) { + existingtarget = tmpMap.get(source); + } boolean altexist = existingtarget != null && !target.isEmpty() && !existingtarget.isEmpty() && !existingtarget.equals(target); diff --git a/source/net/yacy/utils/translation/TranslationManager.java b/source/net/yacy/utils/translation/TranslationManager.java index 8cd1fd6f8..afc2fba67 100644 --- a/source/net/yacy/utils/translation/TranslationManager.java +++ b/source/net/yacy/utils/translation/TranslationManager.java @@ -40,6 +40,8 @@ import java.util.Map; import java.util.TreeMap; import net.yacy.cora.util.ConcurrentLog; import net.yacy.data.Translator; +import net.yacy.search.Switchboard; +import net.yacy.search.SwitchboardConstants; /** * Utility to create a translation master file from all existing translation @@ -50,6 +52,7 @@ import net.yacy.data.Translator; public class TranslationManager extends TranslatorXliff { protected Map> mainTransLists; // current translation entries for one language + protected String loadedLng; // language loaded in mainTransLists (2-letter code) public TranslationManager() { super(); @@ -57,6 +60,10 @@ public class TranslationManager extends TranslatorXliff { public TranslationManager(final File langfile) { mainTransLists = loadTranslationsLists(langfile); + int pos = langfile.getName().indexOf('.'); + if (pos >= 0) { + loadedLng = langfile.getName().substring(0, pos); + } } /** @@ -125,6 +132,35 @@ public class TranslationManager extends TranslatorXliff { return null; } + /** + * Translates one file. The relFilepath is the file name as given in the + * translation source lists. The source (english) file is expected under + * htroot path. The destination file is under DATA/LOCALE and calculated + * using the language of loaded data. + * + * @param relFilepath file name releative to htroot + * @return true on success + */ + public boolean translateFile(String relFilepath) { + assert loadedLng != null; + assert mainTransLists != null; + + boolean result = false; + if (mainTransLists.containsKey(relFilepath)) { + Switchboard sb = Switchboard.getSwitchboard(); + if (sb != null) { + final String htRootPath = sb.getConfig(SwitchboardConstants.HTROOT_PATH, SwitchboardConstants.HTROOT_PATH_DEFAULT); + final File sourceDir = new File(sb.getAppPath(), htRootPath); + final File destDir = new File(sb.getDataPath("locale.translated_html", "DATA/LOCALE/htroot"), loadedLng); + // get absolute file by adding relative filename + final File sourceFile = new File(sourceDir, relFilepath); + final File destFile = new File(destDir, relFilepath); + result = translateFile(sourceFile, destFile, mainTransLists.get(relFilepath)); // do the translation + } + } + return result; + } + /** * Create a master translation list by reading all translation files * If a masterOutputFile exists, content is preserved (loaded first) @@ -256,33 +292,4 @@ public class TranslationManager extends TranslatorXliff { } return i; } - - /** - * for testing to create on master and joined translation results for all lang's - * - * @param args - */ - public static void main(String args[]) { - File outputdirectory = new File("test/DATA"); - if (!outputdirectory.exists()) { - outputdirectory.mkdir(); - } - File xlfmaster = new File(outputdirectory, "master.lng.xlf"); - TranslationManager ctm = new TranslationManager(xlfmaster); - try { - ctm.createMasterTranslationLists(xlfmaster); // write the language neutral translation master as xliff - - List lngFiles = Translator.langFiles(new File("locales")); - for (String filename : lngFiles) { - Map> lngmaster = ctm.joinMasterTranslationLists(xlfmaster, new File("locales", filename)); // create individual language translation files from master - File xlftmp = new File(outputdirectory, filename + ".xlf"); - System.out.println("output new master translation file " + xlftmp.toString() + " and " + filename); - ctm.saveAsXliff(filename.substring(0, 2), xlftmp, lngmaster); - ctm.saveAsLngFile(filename.substring(0, 2), new File(outputdirectory, filename), lngmaster); - } - } catch (IOException ex) { - ConcurrentLog.logException(ex); - } - ConcurrentLog.shutdown(); - } } From ec8dd9501476cb1de18625fb37e1b8f380c16502 Mon Sep 17 00:00:00 2001 From: reger Date: Tue, 30 Aug 2016 01:34:32 +0200 Subject: [PATCH 14/33] fix deactivation of Russian Thesaurus --- htroot/DictionaryLoader_p.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/htroot/DictionaryLoader_p.java b/htroot/DictionaryLoader_p.java index 87c6e6d6c..9b455abaf 100644 --- a/htroot/DictionaryLoader_p.java +++ b/htroot/DictionaryLoader_p.java @@ -45,11 +45,11 @@ public class DictionaryLoader_p { final serverObjects prop = new serverObjects(); // return variable that accumulates replacements final File synonyms_path = new File(sb.dictionariesPath, LibraryProvider.path_to_synonym_dictionaries); - final File synonym_de_default = new File(new File(new File(sb.appPath, "addon"), "synonyms"), "openthesaurus_de_yacy"); + final File synonym_de_default = new File(sb.appPath, "addon/synonyms/openthesaurus_de_yacy"); final File synonym_de_production = new File(synonyms_path, synonym_de_default.getName()); - final File synonym_en_default = new File(new File(new File(sb.appPath, "addon"), "synonyms"), "mobythesaurus_en_yacy"); + final File synonym_en_default = new File(sb.appPath, "addon/synonyms/mobythesaurus_en_yacy"); final File synonym_en_production = new File(synonyms_path, synonym_en_default.getName()); - final File synonym_ru_default = new File(new File(new File(sb.appPath, "addon"), "synonyms"), "thesaurus_ru_yacy"); + final File synonym_ru_default = new File(sb.appPath, "addon/synonyms/thesaurus_ru_yacy"); final File synonym_ru_production = new File(synonyms_path, synonym_ru_default.getName()); /* * distinguish the following cases: @@ -325,8 +325,8 @@ public class DictionaryLoader_p { SynonymLibrary.init(synonyms_path); } - if (post.containsKey("syn0Deactivate")) { - synonym_de_production.delete(); + if (post.containsKey("syn2Deactivate")) { + synonym_ru_production.delete(); SynonymLibrary.init(synonyms_path); } From e0354a9cf9616a9ae8fee82fd5328b860b9cef84 Mon Sep 17 00:00:00 2001 From: reger Date: Tue, 30 Aug 2016 02:01:17 +0200 Subject: [PATCH 15/33] master.lng: add language names (ConfigBasic.html) --- locales/master.lng.xlf | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/locales/master.lng.xlf b/locales/master.lng.xlf index 560321f72..683fe7228 100644 --- a/locales/master.lng.xlf +++ b/locales/master.lng.xlf @@ -1069,6 +1069,27 @@ Select a language for the interface + + Deutsch + + + Fran&ccedil;ais + + + &#27721;&#35821;/&#28450;&#35486 + + + &#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081; + + + &#1059;&#1082;&#1088;&#1072;&#1111;&#1085;&#1089;&#1100;&#1082;&#1072; + + + &#2361;&#2367;&#2344;&#2381;&#2342;&#2368; + + + &#26085;&#26412;&#35486; + Use Case: what do you want to do with YaCy: From 4386e84b55f983472b61373d6309b43d93f14c8e Mon Sep 17 00:00:00 2001 From: reger Date: Wed, 31 Aug 2016 02:24:30 +0200 Subject: [PATCH 16/33] correct NewPool rentention calculation (was still clearing everything after one day) --- source/net/yacy/peers/NewsPool.java | 70 ++++++++++++++++------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/source/net/yacy/peers/NewsPool.java b/source/net/yacy/peers/NewsPool.java index 1c40ee086..1f13232f8 100644 --- a/source/net/yacy/peers/NewsPool.java +++ b/source/net/yacy/peers/NewsPool.java @@ -423,41 +423,47 @@ public class NewsPool { * @return true if news should be removed */ private static boolean automaticProcessP(final SeedDB seedDB, final NewsDB.Record record) { - if (record == null) return false; - if (record.category() == null) return true; - - final long created = record.created().getTime(); - final long duration = System.currentTimeMillis() - created; - - if ((record.category().equals(CATEGORY_WIKI_UPDATE)) && - (duration > (3L * MILLISECONDS_PER_DAY))) { - return true; - } - if ((record.category().equals(CATEGORY_BLOG_ADD)) && - (duration > (3L * MILLISECONDS_PER_DAY))) { - return true; - } - if ((record.category().equals(CATEGORY_PROFILE_UPDATE)) && - (duration > (3L * MILLISECONDS_PER_DAY))) { - return true; - } - if ((record.category().equals(CATEGORY_CRAWL_START)) && - (duration > (3L * MILLISECONDS_PER_DAY))) { - final Seed seed = seedDB.get(record.originator()); - if (seed == null) return true; - try { - return (Integer.parseInt(seed.get(Seed.ISPEED, "-")) < 10); - } catch (final NumberFormatException ee) { - return true; - } + if (record == null) { + return false; } - if ((record.category().equals(CATEGORY_TRANSLATION_ADD) || record.category().equals(CATEGORY_TRANSLATION_VOTE_ADD)) - && (duration > (7L * MILLISECONDS_PER_DAY))) { + if (record.category() == null) { return true; } - if (duration > MILLISECONDS_PER_DAY) { - // remove everything else after 1 day - return true; + + final long created = record.created().getTime(); + final long duration = System.currentTimeMillis() - created; + + String cat = record.category(); + switch (cat) { + case CATEGORY_WIKI_UPDATE: + case CATEGORY_BLOG_ADD: + case CATEGORY_PROFILE_UPDATE: + if (duration > (3L * MILLISECONDS_PER_DAY)) { + return true; + } + break; + case CATEGORY_CRAWL_START: + if (duration > (3L * MILLISECONDS_PER_DAY)) { + final Seed seed = seedDB.get(record.originator()); + if (seed == null) return true; // TODO: shall we keep for 3 days without sender ? + try { + return (Integer.parseInt(seed.get(Seed.ISPEED, "-")) < 10); // TODO: should we keep longer as 3 days if peer is still/currently crawling (after 3 days) ? + } catch (final NumberFormatException ee) { + return true; + } + } + break; + case CATEGORY_TRANSLATION_ADD: + case CATEGORY_TRANSLATION_VOTE_ADD: + if (duration > (7L * MILLISECONDS_PER_DAY)) { + return true; + } + break; + default: + if (duration > MILLISECONDS_PER_DAY) { + // remove everything else after 1 day + return true; + } } return false; } From de663be48b1fcd722c51490b1836d41c7235ec5c Mon Sep 17 00:00:00 2001 From: reger Date: Wed, 31 Aug 2016 02:36:59 +0200 Subject: [PATCH 17/33] skip resolving of host "" in hello servlet --- htroot/yacy/hello.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htroot/yacy/hello.java b/htroot/yacy/hello.java index 6c5becde0..87e172e80 100644 --- a/htroot/yacy/hello.java +++ b/htroot/yacy/hello.java @@ -57,12 +57,13 @@ public final class hello { final serverObjects prop = new serverObjects(); final long start = System.currentTimeMillis(); prop.put("message", "none"); - final String clientip = header.get(HeaderFramework.CONNECTION_PROP_CLIENTIP, ""); // read an artificial header addendum + String clientip = header.get(HeaderFramework.CONNECTION_PROP_CLIENTIP); // read an artificial header addendum //ConcurrentLog.info("**hello-DEBUG**", "client request from = " + clientip); final InetAddress ias = Domains.dnsResolve(clientip); long time = System.currentTimeMillis(); final long time_dnsResolve = System.currentTimeMillis() - time; if (ias == null) { + if (clientip == null) clientip = ""; Network.log.info("hello/server: failed contacting seed; clientip not resolvable (clientip=" + clientip + ", time_dnsResolve=" + time_dnsResolve + ")"); prop.put("message", "cannot resolve your IP from your reported location " + clientip); return prop; From 7c0f1106a6d2d9de0aded56f783d0eb065c575c9 Mon Sep 17 00:00:00 2001 From: reger Date: Thu, 1 Sep 2016 20:33:28 +0200 Subject: [PATCH 18/33] upd master.lng for RankingSolr_p.html (add Filter Query txt) --- locales/master.lng.xlf | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/locales/master.lng.xlf b/locales/master.lng.xlf index 683fe7228..47d038e9a 100644 --- a/locales/master.lng.xlf +++ b/locales/master.lng.xlf @@ -6821,6 +6821,30 @@ "Re-Set to default" + + >Filter Query< + + + The Filter Query is attached to every query. + + + Use this to statically add a selection criteria to reduce the set of results. + + + Example: "http_unique_b:true AND www_unique_b:true" will filter out all results where urls appear also with/without http(s) and/or with/without 'www.' prefix. + + + To find appropriate fields for this query, see the + + + YaCy Solr Schema + + + Warning: bad expressions here will cause that you don't have any search result! + + + "Set Filter Query" + >Boost Query< From f3f478448bab001849d3d621c159a2a0de8758cc Mon Sep 17 00:00:00 2001 From: luccioman Date: Fri, 2 Sep 2016 11:22:39 +0200 Subject: [PATCH 19/33] Explicitely set YaCy data folder when starting in MacOS bundle --- addon/YaCy.app/Contents/Info.plist | 2 +- addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh | 8 ++++++++ build.xml | 3 ++- startYACY.sh | 17 +++++++++++++++-- 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100755 addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh diff --git a/addon/YaCy.app/Contents/Info.plist b/addon/YaCy.app/Contents/Info.plist index 69804aa9f..5f772f193 100644 --- a/addon/YaCy.app/Contents/Info.plist +++ b/addon/YaCy.app/Contents/Info.plist @@ -19,7 +19,7 @@ CFBundleAllowMixedLocalizations true CFBundleExecutable -startYACY.sh +startYACYMacOS.sh CFBundleDevelopmentRegion English CFBundlePackageType diff --git a/addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh b/addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh new file mode 100755 index 000000000..456ddea4e --- /dev/null +++ b/addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +# Launcher for YaCy in a MacOS bundle : +# rely on the generic startYACY.sh, but specifies the user home relative path for YaCy data +# This data directory is set in conforming to OS X File System Programming Guide +# see : https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html + +"`dirname $0`"/startYACY.sh -startup "'Library/Application Support/net.yacy.YaCy'" diff --git a/build.xml b/build.xml index 879296ef4..14acd7b66 100644 --- a/build.xml +++ b/build.xml @@ -764,7 +764,8 @@ - + + diff --git a/startYACY.sh b/startYACY.sh index 8763b9086..567cc5e89 100755 --- a/startYACY.sh +++ b/startYACY.sh @@ -40,6 +40,7 @@ Options -l, --logging save the output of YaCy to yacy.log -d, --debug show the output of YaCy on the console -p, --print-out only print the command, which would be executed to start YaCy + -start, -startup [data-path] start YaCy using the specified data folder path, relative to the current user home -g, --gui start a gui for YaCy USAGE } @@ -101,6 +102,10 @@ for option in $options;do -t|--tail-log) TAILLOG=1 ;; + -start|-startup) + STARTUP=1 + isparameter=1 + ;; -g|--gui) GUI=1 isparameter=1 @@ -111,7 +116,11 @@ for option in $options;do isparameter=1; continue else - parameter="$parameter $option" + if [ $parameter ];then + parameter="$parameter $option" + else + parameter="$option" + fi fi fi #parameter or option? done @@ -189,7 +198,11 @@ for N in lib/*.jar; do CLASSPATH="$CLASSPATH$N:"; done CLASSPATH=".:$CLASSPATH" cmdline="$JAVA $JAVA_ARGS -classpath $CLASSPATH net.yacy.yacy"; -if [ $GUI -eq 1 ] #gui + +if [ $STARTUP -eq 1 ] #startup +then + cmdline="$cmdline -startup $parameter" +elif [ $GUI -eq 1 ] #gui then cmdline="$cmdline -gui $parameter" fi From 1dc4306058a3354f61435d403561829590fc4e9d Mon Sep 17 00:00:00 2001 From: luccioman Date: Fri, 2 Sep 2016 11:23:02 +0200 Subject: [PATCH 20/33] Fixed indentation for better readability. --- source/net/yacy/yacy.java | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/source/net/yacy/yacy.java b/source/net/yacy/yacy.java index db7bbce51..44803ec6e 100644 --- a/source/net/yacy/yacy.java +++ b/source/net/yacy/yacy.java @@ -713,13 +713,17 @@ public final class yacy { //System.out.print("args=["); for (int i = 0; i < args.length; i++) System.out.print(args[i] + ", "); System.out.println("]"); if ((args.length >= 1) && (args[0].toLowerCase().equals("-startup") || args[0].equals("-start"))) { // normal start-up of yacy - if (args.length > 1) dataRoot = new File(System.getProperty("user.home").replace('\\', '/'), args[1]); - preReadSavedConfigandInit(dataRoot); + if (args.length > 1) { + dataRoot = new File(System.getProperty("user.home").replace('\\', '/'), args[1]); + } + preReadSavedConfigandInit(dataRoot); startup(dataRoot, applicationRoot, startupMemFree, startupMemTotal, false); } else if (args.length >= 1 && args[0].toLowerCase().equals("-gui")) { // start-up of yacy with gui - if (args.length > 1) dataRoot = new File(System.getProperty("user.home").replace('\\', '/'), args[1]); - preReadSavedConfigandInit(dataRoot); + if (args.length > 1) { + dataRoot = new File(System.getProperty("user.home").replace('\\', '/'), args[1]); + } + preReadSavedConfigandInit(dataRoot); startup(dataRoot, applicationRoot, startupMemFree, startupMemTotal, true); } else if ((args.length >= 1) && ((args[0].toLowerCase().equals("-shutdown")) || (args[0].equals("-stop")))) { // normal shutdown of yacy @@ -732,7 +736,7 @@ public final class yacy { } else if ((args.length >= 1) && (args[0].toLowerCase().equals("-version"))) { // show yacy version System.out.println(copyright); - } else if ((args.length > 1) && (args[0].toLowerCase().equals("-config"))) { + } else if ((args.length > 1) && (args[0].toLowerCase().equals("-config"))) { // set config parameter. Special handling of adminAccount=user:pwd (generates md5 encoded password) // on Windows parameter should be enclosed in doublequotes to accept = sign (e.g. -config "port=8090" "port.ssl=8043") File f = new File (dataRoot,"DATA/SETTINGS/"); @@ -778,9 +782,11 @@ public final class yacy { } System.out.println(); } - } else { - if (args.length == 1) applicationRoot= new File(args[0]); - preReadSavedConfigandInit(dataRoot); + } else { + if (args.length == 1) { + applicationRoot= new File(args[0]); + } + preReadSavedConfigandInit(dataRoot); startup(dataRoot, applicationRoot, startupMemFree, startupMemTotal, false); } } finally { From 24b87412921c6e247d28e7fbe19b388d4124e71e Mon Sep 17 00:00:00 2001 From: luccioman Date: Fri, 2 Sep 2016 11:55:46 +0200 Subject: [PATCH 21/33] Fix for startup option - Var initialization - Declaration in getopt --- startYACY.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/startYACY.sh b/startYACY.sh index 567cc5e89..5e52a9292 100755 --- a/startYACY.sh +++ b/startYACY.sh @@ -40,7 +40,7 @@ Options -l, --logging save the output of YaCy to yacy.log -d, --debug show the output of YaCy on the console -p, --print-out only print the command, which would be executed to start YaCy - -start, -startup [data-path] start YaCy using the specified data folder path, relative to the current user home + --start, --startup [data-path] start YaCy using the specified data folder path, relative to the current user home -g, --gui start a gui for YaCy USAGE } @@ -57,7 +57,7 @@ then options="`getopt hdlptg: $*`" else - options="`getopt -n YaCy -o h,d,l,p,t,g -l help,debug,logging,print-out,tail-log,gui -- $@`" + options="`getopt -n YaCy -o h,d,l,p,t,g -l help,debug,logging,print-out,tail-log,gui,start,startup -- $@`" fi if [ $? -ne 0 ];then @@ -72,6 +72,7 @@ LOGGING=0 DEBUG=0 PRINTONLY=0 TAILLOG=0 +STARTUP=0 GUI=0 for option in $options;do if [ $isparameter -ne 1 ];then #option @@ -202,8 +203,7 @@ cmdline="$JAVA $JAVA_ARGS -classpath $CLASSPATH net.yacy.yacy"; if [ $STARTUP -eq 1 ] #startup then cmdline="$cmdline -startup $parameter" -elif [ $GUI -eq 1 ] #gui -then +elif [ $GUI -eq 1 ];then #gui cmdline="$cmdline -gui $parameter" fi if [ $DEBUG -eq 1 ] #debug From 6801673a07cb2740318c5ec1ac7b9bbe930d521c Mon Sep 17 00:00:00 2001 From: reger Date: Sat, 3 Sep 2016 03:37:40 +0200 Subject: [PATCH 22/33] apply postranking media search boost only on media queries --- source/net/yacy/search/query/SearchEvent.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/source/net/yacy/search/query/SearchEvent.java b/source/net/yacy/search/query/SearchEvent.java index 6585a5060..11a322a4f 100644 --- a/source/net/yacy/search/query/SearchEvent.java +++ b/source/net/yacy/search/query/SearchEvent.java @@ -1439,10 +1439,19 @@ public final class SearchEvent { long r = 0; // for media search: prefer pages with many links - r += rentry.limage() << this.query.ranking.coeff_cathasimage; - r += rentry.laudio() << this.query.ranking.coeff_cathasaudio; - r += rentry.lvideo() << this.query.ranking.coeff_cathasvideo; - r += rentry.lapp() << this.query.ranking.coeff_cathasapp; + switch (this.query.contentdom) { + case IMAGE: + r += rentry.limage() << this.query.ranking.coeff_cathasimage; + break; + case AUDIO: + r += rentry.laudio() << this.query.ranking.coeff_cathasaudio; + break; + case VIDEO: + r += rentry.lvideo() << this.query.ranking.coeff_cathasvideo; + break; + case APP: + r += rentry.lapp() << this.query.ranking.coeff_cathasapp; + } // apply citation count //System.out.println("POSTRANKING CITATION: references = " + rentry.referencesCount() + ", inbound = " + rentry.llocal() + ", outbound = " + rentry.lother()); From 421a6e3a95ee7db257a79c35c5bb3bf6067d676b Mon Sep 17 00:00:00 2001 From: luccioman Date: Sat, 3 Sep 2016 14:46:58 +0200 Subject: [PATCH 23/33] Fixed options processing for Mac OS - getopt is BSD style and does not support long options - fixed typing error inparameter value extracting for all platforms --- addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh | 2 +- startYACY.sh | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh b/addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh index 456ddea4e..692ec913f 100755 --- a/addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh +++ b/addon/YaCy.app/Contents/MacOS/startYACYMacOS.sh @@ -5,4 +5,4 @@ # This data directory is set in conforming to OS X File System Programming Guide # see : https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html -"`dirname $0`"/startYACY.sh -startup "'Library/Application Support/net.yacy.YaCy'" +"`dirname $0`"/startYACY.sh -s "'Library/Application Support/net.yacy.YaCy'" diff --git a/startYACY.sh b/startYACY.sh index 5e52a9292..f935ae2e3 100755 --- a/startYACY.sh +++ b/startYACY.sh @@ -40,7 +40,7 @@ Options -l, --logging save the output of YaCy to yacy.log -d, --debug show the output of YaCy on the console -p, --print-out only print the command, which would be executed to start YaCy - --start, --startup [data-path] start YaCy using the specified data folder path, relative to the current user home + -s, --startup [data-path] start YaCy using the specified data folder path, relative to the current user home -g, --gui start a gui for YaCy USAGE } @@ -48,16 +48,16 @@ USAGE #startup YaCy cd "`dirname $0`" -if [ $OS = "OpenBSD" ] +if [ $OS = "OpenBSD" ] || [ $OS = "Darwin" ] then if [ $(echo $@ | grep -o "\-\-" | wc -l) -ne 0 ] then echo "WARNING: Unfortunately this script does not support long options in $OS." fi - options="`getopt hdlptg: $*`" + options="`getopt hdlptsg: $*`" else - options="`getopt -n YaCy -o h,d,l,p,t,g -l help,debug,logging,print-out,tail-log,gui,start,startup -- $@`" + options="`getopt -n YaCy -o h,d,l,p,t,s,g -l help,debug,logging,print-out,tail-log,startup,gui -- $@`" fi if [ $? -ne 0 ];then @@ -103,7 +103,7 @@ for option in $options;do -t|--tail-log) TAILLOG=1 ;; - -start|-startup) + -s|-startup) STARTUP=1 isparameter=1 ;; @@ -113,7 +113,7 @@ for option in $options;do ;; esac #case option else #parameter - if [ x$option = "--" ];then #option / parameter separator + if [ $option = "--" ];then #option / parameter separator isparameter=1; continue else From 8255e91c994a6bc85817dd4943f7e78df34b640d Mon Sep 17 00:00:00 2001 From: luccioman Date: Sat, 3 Sep 2016 15:21:02 +0200 Subject: [PATCH 24/33] Fixed serverClassLoader.findClass method htroot is a supposed to be a subfolder of appPath and not of dataPath, as assumed in other places where htroot is loaded. This issue was not visible when dataPath and appPath are equals. --- source/net/yacy/server/serverClassLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/net/yacy/server/serverClassLoader.java b/source/net/yacy/server/serverClassLoader.java index 9412dc4be..cd627a22c 100644 --- a/source/net/yacy/server/serverClassLoader.java +++ b/source/net/yacy/server/serverClassLoader.java @@ -64,7 +64,7 @@ public final class serverClassLoader extends ClassLoader { @Override protected Class findClass(String classname) throws ClassNotFoundException { // construct path to htroot for a servletname - File cpath = new File (Switchboard.getSwitchboard().getDataPath(SwitchboardConstants.HTROOT_PATH, SwitchboardConstants.HTROOT_PATH_DEFAULT),classname+".class"); + File cpath = new File (Switchboard.getSwitchboard().getAppPath(SwitchboardConstants.HTROOT_PATH, SwitchboardConstants.HTROOT_PATH_DEFAULT),classname+".class"); return loadClass(cpath); } From cc2d9dd3f17eba482c9baf002c87abaa93371423 Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 4 Sep 2016 00:09:45 +0200 Subject: [PATCH 25/33] reactivate the use of included-in-topwords boost in postRanking + changed the postRanking to add one score only if word appears more as one time. + getTopics() unused code block rem'd (save performace)-> routine needs rework ! --- source/net/yacy/search/query/SearchEvent.java | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/source/net/yacy/search/query/SearchEvent.java b/source/net/yacy/search/query/SearchEvent.java index 11a322a4f..579267968 100644 --- a/source/net/yacy/search/query/SearchEvent.java +++ b/source/net/yacy/search/query/SearchEvent.java @@ -1428,7 +1428,11 @@ public final class SearchEvent { */ public void addResult(URIMetadataNode resultEntry, final float score) { if (resultEntry == null) return; - final long ranking = ((long) (score * 128.f)) + postRanking(resultEntry, new ConcurrentScoreMap() /*this.snippetProcess.rankingProcess.getTopicNavigator(10)*/); + final long ranking = ((long) (score * 128.f)) + postRanking(resultEntry, this.ref /*this.getTopicNavigator(MAX_TOPWORDS)*/); + // TODO: above was originally using (see below), but getTopicNavigator returns this.ref and possibliy alters this.ref on first call (this.ref.size < 2 -> this.ref.clear) + // TODO: verify and straighten the use of addTopic, getTopic and getTopicNavigator and related score calculation + // final long ranking = ((long) (score * 128.f)) + postRanking(resultEntry, this.getTopicNavigator(MAX_TOPWORDS)); + resultEntry.setScore(ranking); // update the score of resultEntry for access by search interface / api this.resultList.put(new ReverseElement(resultEntry, ranking)); // remove smallest in case of overflow if (pollImmediately) this.resultList.poll(); // prevent re-ranking in case there is only a single index source which has already ranked entries. @@ -1467,24 +1471,27 @@ public final class SearchEvent { final String urlstring = rentry.url().toNormalform(true); final String[] urlcomps = MultiProtocolURL.urlComps(urlstring); final String[] descrcomps = MultiProtocolURL.splitpattern.split(rentry.title().toLowerCase()); - for (final String urlcomp : urlcomps) { + + // apply query-in-result matching + final QueryGoal.NormalizedWords urlcompmap = new QueryGoal.NormalizedWords(urlcomps); + final QueryGoal.NormalizedWords descrcompmap = new QueryGoal.NormalizedWords(descrcomps); + // the token map is used (instead of urlcomps/descrcomps) to determine appearance in url/title and eliminate double occurances + // (example Title="News News News News News News - today is party -- News News News News News News" to add one score instead of 12 * score !) + for (final String urlcomp : urlcompmap) { int tc = topwords.get(urlcomp); if (tc > 0) r += Math.max(1, tc) << this.query.ranking.coeff_urlcompintoplist; } - for (final String descrcomp : descrcomps) { + for (final String descrcomp : descrcompmap) { int tc = topwords.get(descrcomp); if (tc > 0) r += Math.max(1, tc) << this.query.ranking.coeff_descrcompintoplist; } - // apply query-in-result matching - final QueryGoal.NormalizedWords urlcomph = new QueryGoal.NormalizedWords(urlcomps); - final QueryGoal.NormalizedWords descrcomph = new QueryGoal.NormalizedWords(descrcomps); final Iterator shi = this.query.getQueryGoal().getIncludeWords(); String queryword; while (shi.hasNext()) { queryword = shi.next(); - if (urlcomph.contains(queryword)) r += 256 << this.query.ranking.coeff_appurl; - if (descrcomph.contains(queryword)) r += 256 << this.query.ranking.coeff_app_dc_title; + if (urlcompmap.contains(queryword)) r += 256 << this.query.ranking.coeff_appurl; + if (descrcompmap.contains(queryword)) r += 256 << this.query.ranking.coeff_app_dc_title; } return r; } @@ -1827,14 +1834,24 @@ public final class SearchEvent { // this is only available if execQuery() was called before return this.localSearchInclusion; } - + + /** + * create a list of words that had been computed by statistics over all + * words that appeared in the url or the description of all urls + * + * @param maxcount max number of topwords to return + * @param maxtime max time allowed to use + * @return + */ public ScoreMap getTopics(final int maxcount, final long maxtime) { - // create a list of words that had been computed by statistics over all - // words that appeared in the url or the description of all urls final ScoreMap result = new ConcurrentScoreMap(); if ( this.ref.sizeSmaller(2) ) { this.ref.clear(); // navigators with one entry are not useful } + /* ---------------------------------- start of rem (2016-09-03) + // TODO: result map is not used currently, verify if it should and use or delete this code block + // TODO: as it is not used now - in favour of performance this code block is rem'ed (2016-09-03) + final Map counts = new HashMap(); final Iterator i = this.ref.keys(false); String word; @@ -1860,6 +1877,7 @@ public final class SearchEvent { result.set(ce.getKey(), (int) (((double) maxcount) * (ce.getValue() - min) / (max - min))); } } + /* ------------------------------------ end of rem (2016-09-03) */ return this.ref; } From 47391678e7bc130cfc6d5fb76337b4ef81a64b8d Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 4 Sep 2016 01:00:28 +0200 Subject: [PATCH 26/33] TranslationNews: take out limitation to send only one text per translated file (to avoid need of repeated publish button hits) --- htroot/TransNews_p.java | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/htroot/TransNews_p.java b/htroot/TransNews_p.java index 5a2ab53aa..468d92adc 100644 --- a/htroot/TransNews_p.java +++ b/htroot/TransNews_p.java @@ -42,7 +42,6 @@ import net.yacy.server.serverObjects; import net.yacy.server.serverSwitch; import net.yacy.utils.crypt; import net.yacy.utils.translation.TranslationManager; -import net.yacy.utils.translation.TranslatorXliff; public class TransNews_p { @@ -90,32 +89,13 @@ public class TransNews_p { continue; } if (NewsPool.CATEGORY_TRANSLATION_ADD.equals(rtmp.category())) { - //String tmplng = rtmp.attribute("language", null); + String tmplng = rtmp.attribute("language", null); String tmpfile = rtmp.attribute("file", null); String tmpsource = rtmp.attribute("source", null); - String tmptarget = rtmp.attribute("target", null); + //String tmptarget = rtmp.attribute("target", null); - if (sb.peers.mySeed().hash.equals(rtmp.originator())) { - /* - if (tmplng != null && tmplng.equals(currentlang)) { - sendit = false; - break; - }*/ - if (tmpfile != null && tmpfile.equals(file)) { - sendit = false; - break; - } - if (tmpsource != null && tmpsource.equals(sourcetxt)) { - sendit = false; - break; - } - if (tmptarget != null && tmptarget.equals(targettxt)) { - sendit = false; - break; - } - } // if news with file and source exist (maybe from other peer) - skip sending another msg (to avoid confusion) - if ((tmpfile != null && tmpfile.equals(file)) + if ((tmplng != null && tmplng.equals(currentlang)) && (tmpfile != null && tmpfile.equals(file)) && (tmpsource != null && tmpsource.equals(sourcetxt))) { sendit = false; break; From a2777903d64f662cc6eb00d884e859cfc9a93c5e Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 4 Sep 2016 02:29:04 +0200 Subject: [PATCH 27/33] include translation news service in status submenue + display translation proposal news only for current language (in TransNews servlet) --- htroot/News.html | 4 ++++ htroot/TransNews_p.html | 2 +- htroot/TransNews_p.java | 1 + .../env/templates/submenuComputation.template | 1 + locales/master.lng.xlf | 18 ++++++++++++++++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/htroot/News.html b/htroot/News.html index 1c2b83507..320717d3d 100644 --- a/htroot/News.html +++ b/htroot/News.html @@ -43,6 +43,10 @@ A change in the personal profile will create a news entry. You can see recently made changes of profile entries on the Network page, where that profile change is visualized with a '*' beside the 'P' (profile) - selector. +
  • + Publishing of added or modified translation for the user interface. Other peers may include it in their local translation list. + To publish a translation, use the integrated translation editor to add a translation and publish it afterwards. +
  • More news services will follow. diff --git a/htroot/TransNews_p.html b/htroot/TransNews_p.html index d5b755c02..7e69deedb 100644 --- a/htroot/TransNews_p.html +++ b/htroot/TransNews_p.html @@ -6,7 +6,7 @@ #%env/templates/header.template%# - + #%env/templates/submenuComputation.template%#

    Translation News for Language #[currentlang]#

    diff --git a/htroot/TransNews_p.java b/htroot/TransNews_p.java index 468d92adc..d570cf363 100644 --- a/htroot/TransNews_p.java +++ b/htroot/TransNews_p.java @@ -200,6 +200,7 @@ public class TransNews_p { continue; } + if (!lang.equals(currentlang)) continue; String existingtarget = null; //transMgr.getTranslation(filename, source); Map tmpMap = localTrans.get(filename); diff --git a/htroot/env/templates/submenuComputation.template b/htroot/env/templates/submenuComputation.template index b6062bcf8..5a112d2ce 100644 --- a/htroot/env/templates/submenuComputation.template +++ b/htroot/env/templates/submenuComputation.template @@ -33,6 +33,7 @@ diff --git a/locales/master.lng.xlf b/locales/master.lng.xlf index 47d038e9a..fec0c7550 100644 --- a/locales/master.lng.xlf +++ b/locales/master.lng.xlf @@ -5966,6 +5966,21 @@ profile entries on the Network page, where that profile change is visualized with a '*' beside the 'P' (profile) - selector. + + Publishing of added or modified translation for the user interface. + + + Other peers may include it in their local translation list. + + + To publish a translation, use the integrated + + + translation editor + + + to add a translation and publish it afterwards. + Above you can see four menues: @@ -10124,6 +10139,9 @@ >Local Peer Wiki< + + UI Translations + From f34b493ab639e2a28320c8435f4290cf53b56ca3 Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 4 Sep 2016 03:05:25 +0200 Subject: [PATCH 28/33] fix fr.lng (missing quotes) broke sentence appart to reduce inclusion of coding tags in translation --- locales/fr.lng | 10 ++++++++-- locales/master.lng.xlf | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/locales/fr.lng b/locales/fr.lng index 912f628f0..9d81c49c6 100644 --- a/locales/fr.lng +++ b/locales/fr.lng @@ -1212,8 +1212,14 @@ Category==Categorie Received==Reçu Distributed==Distribué Attributes==Attributs -"#(page)#::Process Selected News::Delete Selected News::Abort Publication of Selected News::Delete Selected News#(/page)#"==#(page)#::Traiter les nouvelles sélectionnées::Supprimer les nouvelles sélectionnées::Annuler la publication des nouvelles sélectionnées::Supprimer les nouvelles sélectionées#(/page)# -"#(page)#::Process All News::Delete All News::Abort Publication of All News::Delete All News#(/page)#"==#(page)#::Traiter toutes les nouvelles::Supprimer toutes les nouvelles::Annuler la publication de toutes les nouvelles::Supprimer toutes les nouvelles#(/page)# +Process Selected News==Traiter les nouvelles sélectionnées +Delete Selected News==Supprimer les nouvelles sélectionnées +Abort Publication of Selected News==Annuler la publication des nouvelles sélectionnées +Delete Selected News==Supprimer les nouvelles sélectionées +Process All News==Traiter toutes les nouvelles +Delete All News==Supprimer toutes les nouvelles +Abort Publication of All News==Annuler la publication de toutes les nouvelles +Delete All News==Supprimer toutes les nouvelles #----------------------------- #File: Performance_p.html diff --git a/locales/master.lng.xlf b/locales/master.lng.xlf index fec0c7550..e31b52804 100644 --- a/locales/master.lng.xlf +++ b/locales/master.lng.xlf @@ -6023,6 +6023,24 @@ Attributes + + Process Selected News + + + Delete Selected News + + + Abort Publication of Selected News + + + Process All News + + + Delete All News + + + Abort Publication of All News + "#(page)#::Process Selected News::Delete Selected News::Abort Publication of Selected News::Delete Selected News#(/page)#" From ebf818ad9597009a68b6f7a925b42a2a6f821bd4 Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 4 Sep 2016 06:42:48 +0200 Subject: [PATCH 29/33] log a error on aborted news publish (due to duplicate news.id) + change printed err msg to log entry in PeerAction.processPeerArrival --- htroot/TransNews_p.java | 6 +++--- source/net/yacy/peers/NewsPool.java | 2 ++ source/net/yacy/peers/PeerActions.java | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/htroot/TransNews_p.java b/htroot/TransNews_p.java index d570cf363..98c432afd 100644 --- a/htroot/TransNews_p.java +++ b/htroot/TransNews_p.java @@ -169,8 +169,8 @@ public class TransNews_p { final HashMap positiveHashes = new HashMap(); // a mapping from an url hash to Integer (count of votes) accumulateVotes(sb, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); final ScoreMap ranking = new ConcurrentScoreMap(); // score cluster for url hashes - final HashMap Translation = new HashMap(); // a mapping from an url hash to a kelondroRow.Entry with display properties - accumulateTranslations(sb, Translation, ranking, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); + final HashMap translation = new HashMap(); // a mapping from an url hash to a kelondroRow.Entry with display properties + accumulateTranslations(sb, translation, ranking, negativeHashes, positiveHashes, NewsPool.INCOMING_DB); // read out translation-news array and create property entries final Iterator k = ranking.keys(false); @@ -187,7 +187,7 @@ public class TransNews_p { continue; } - row = Translation.get(refid); + row = translation.get(refid); if (row == null) { continue; } diff --git a/source/net/yacy/peers/NewsPool.java b/source/net/yacy/peers/NewsPool.java index 1f13232f8..a16a8bcce 100644 --- a/source/net/yacy/peers/NewsPool.java +++ b/source/net/yacy/peers/NewsPool.java @@ -325,6 +325,8 @@ public class NewsPool { if (this.newsDB.get(record.id()) == null) { this.incomingNews.push(record); // we want to see our own news.. this.outgoingNews.push(record); // .. and put it on the publishing list + } else { + ConcurrentLog.info("NewsPool", "publishing of news aborted, news with same id (time + originator) exists id=" + record.id()); } } catch (final Exception e) { ConcurrentLog.logException(e); diff --git a/source/net/yacy/peers/PeerActions.java b/source/net/yacy/peers/PeerActions.java index b249d5c95..46bb263aa 100644 --- a/source/net/yacy/peers/PeerActions.java +++ b/source/net/yacy/peers/PeerActions.java @@ -30,7 +30,6 @@ import net.yacy.cora.document.encoding.ASCII; import net.yacy.cora.document.feed.RSSMessage; import net.yacy.cora.storage.ConcurrentARC; import net.yacy.kelondro.util.MapTools; -import net.yacy.peers.operation.yacyVersion; public class PeerActions { @@ -261,7 +260,7 @@ public class PeerActions { final String cre1 = MapTools.string2map(decodedString, ",").get("cre"); final String cre2 = MapTools.string2map(record.toString(), ",").get("cre"); if ((cre1 == null) || (cre2 == null) || (!(cre1.equals(cre2)))) { - System.out.println("### ERROR - cre are not equal: cre1=" + cre1 + ", cre2=" + cre2); + Network.log.warn("processPeerArrival: ### ERROR - message creation date verification not equal: cre1=" + cre1 + ", cre2=" + cre2); return; } try { From 39dd24469387a84933277e7580b77aea46cf0f57 Mon Sep 17 00:00:00 2001 From: reger Date: Sun, 4 Sep 2016 22:18:07 +0200 Subject: [PATCH 30/33] fix ConcurrentScoreMap.set() calculation of totalCount() + test case --- .../yacy/cora/sorting/ConcurrentScoreMap.java | 5 ++- .../cora/sorting/ConcurrentScoreMapTest.java | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 test/java/net/yacy/cora/sorting/ConcurrentScoreMapTest.java diff --git a/source/net/yacy/cora/sorting/ConcurrentScoreMap.java b/source/net/yacy/cora/sorting/ConcurrentScoreMap.java index 95b2ce3b8..77304e576 100644 --- a/source/net/yacy/cora/sorting/ConcurrentScoreMap.java +++ b/source/net/yacy/cora/sorting/ConcurrentScoreMap.java @@ -133,7 +133,10 @@ public class ConcurrentScoreMap extends AbstractScoreMap implements ScoreM if (obj == null) return; // use atomic operations - this.map.putIfAbsent(obj, new AtomicLong(0)); + final AtomicLong old = this.map.putIfAbsent(obj, new AtomicLong(0)); + // adjust overall counter if value replaced + if (old != null) this.gcount -= old.longValue(); // must use old befor setting a new value (it's a object reference) + this.map.get(obj).set(newScore); // increase overall counter diff --git a/test/java/net/yacy/cora/sorting/ConcurrentScoreMapTest.java b/test/java/net/yacy/cora/sorting/ConcurrentScoreMapTest.java new file mode 100644 index 000000000..443a5436a --- /dev/null +++ b/test/java/net/yacy/cora/sorting/ConcurrentScoreMapTest.java @@ -0,0 +1,34 @@ + +package net.yacy.cora.sorting; + +import java.util.Iterator; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + + +public class ConcurrentScoreMapTest { + + /** + * Test of totalCount method, of class ConcurrentScoreMap. + */ + @Test + public void testTotalCount() { + final ConcurrentScoreMap csm = new ConcurrentScoreMap(); + csm.set("first", 10); + csm.set("second", 5); + csm.set("third", 13); + + csm.set("first", 100); + + final Iterator it = csm.keys(true); + long sum = 0; + while (it.hasNext()) { + String x = it.next(); + long val = csm.get(x); + sum += val; + } + + assertEquals(sum, csm.totalCount()); + } + +} From 51c077f49351f144c71cdefc5ea237bad16622f6 Mon Sep 17 00:00:00 2001 From: reger Date: Mon, 5 Sep 2016 00:07:01 +0200 Subject: [PATCH 31/33] adjust the getTopics() and getTopicNavigator() to current useage - move the maxcount limit restriction completely to getTopicNavigator (as there not used in getTopics) - let search servlet use getTopics by default (w/o RWI connected check, as of now, Topics are available w/o any additional index interaction) --- htroot/yacy/search.java | 4 +- source/net/yacy/search/query/SearchEvent.java | 52 +++++++++++++++---- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/htroot/yacy/search.java b/htroot/yacy/search.java index 0c831c5dd..c208e4282 100644 --- a/htroot/yacy/search.java +++ b/htroot/yacy/search.java @@ -380,7 +380,9 @@ public final class search { // prepare reference hints final long timer = System.currentTimeMillis(); - final ScoreMap topicNavigator = sb.index.connectedRWI() ? theSearch.getTopics(5, 100) : new ConcurrentScoreMap(); + //final ScoreMap topicNavigator = sb.index.connectedRWI() ? theSearch.getTopics(5, 100) : new ConcurrentScoreMap(); + final ScoreMap topicNavigator = theSearch.getTopics(); // as there is currently no index interaction in getTopics(), we can use it by default + final StringBuilder refstr = new StringBuilder(6000); final Iterator navigatorIterator = topicNavigator.keys(false); int i = 0; diff --git a/source/net/yacy/search/query/SearchEvent.java b/source/net/yacy/search/query/SearchEvent.java index 579267968..a291451d4 100644 --- a/source/net/yacy/search/query/SearchEvent.java +++ b/source/net/yacy/search/query/SearchEvent.java @@ -1329,10 +1329,32 @@ public final class SearchEvent { public long getSnippetComputationTime() { return this.snippetComputationAllTime; } - - public ScoreMap getTopicNavigator(final int count ) { + + /** + * Get topics in a ScoreMap if config allows topic navigator + * (the topics are filtered by badwords, stopwords and words included in the query) + * + * @param count max number of topics returned + * @return ScoreMap with max number of topics or null if + */ + public ScoreMap getTopicNavigator(final int count) { if (this.topicNavigatorCount > 0 && count >= 0) { //topicNavigatorCount set during init, 0=no nav - return this.getTopics(count != 0 ? count : this.topicNavigatorCount, 500); + if (!this.ref.sizeSmaller(2)) { + ScoreMap result; + int ic = count != 0 ? count : this.topicNavigatorCount; + + if (this.ref.size() <= ic) { // size matches return map directly + result = this.getTopics(/*ic, 500*/); + } else { // collect top most count topics + result = new ConcurrentScoreMap(); + Iterator it = this.getTopics(/*ic, 500*/).keys(false); + while (ic-- > 0 && it.hasNext()) { + String word = it.next(); + result.set(word, this.ref.get(word)); + } + } + return result; + } } return null; } @@ -1836,21 +1858,20 @@ public final class SearchEvent { } /** - * create a list of words that had been computed by statistics over all + * Return the list of words that had been computed by statistics over all * words that appeared in the url or the description of all urls * - * @param maxcount max number of topwords to return - * @param maxtime max time allowed to use - * @return + * @return ScoreMap */ - public ScoreMap getTopics(final int maxcount, final long maxtime) { + public ScoreMap getTopics(/* final int maxcount, final long maxtime */) { + /* ---------------------------------- start of rem (2016-09-03) + // TODO: result map is not used currently, verify if it should and use or delete this code block + // TODO: as it is not used now - in favour of performance this code block is rem'ed (2016-09-03) + final ScoreMap result = new ConcurrentScoreMap(); if ( this.ref.sizeSmaller(2) ) { this.ref.clear(); // navigators with one entry are not useful } - /* ---------------------------------- start of rem (2016-09-03) - // TODO: result map is not used currently, verify if it should and use or delete this code block - // TODO: as it is not used now - in favour of performance this code block is rem'ed (2016-09-03) final Map counts = new HashMap(); final Iterator i = this.ref.keys(false); @@ -1883,6 +1904,11 @@ public final class SearchEvent { private final static Pattern lettermatch = Pattern.compile("[a-z]+"); + /** + * Collects topics in a ScoreMap for words not included in the query words. + * Words are also filtered by badword blacklist and stopword list. + * @param words + */ public void addTopic(final String[] words) { String word; for ( final String w : words ) { @@ -1899,6 +1925,10 @@ public final class SearchEvent { } } + /** + * Ad title words to this searchEvent's topic score map + * @param resultEntry + */ protected void addTopics(final URIMetadataNode resultEntry) { // take out relevant information for reference computation if ((resultEntry.url() == null) || (resultEntry.title() == null)) return; From e310ec5f702154c215139a0e0fc35db2155a3637 Mon Sep 17 00:00:00 2001 From: reger Date: Tue, 6 Sep 2016 00:05:59 +0200 Subject: [PATCH 32/33] fix posInText ranking calculation to score 0 on no position info + fix Word posInText calc in Tokenizer to start with 1 + test case --- source/net/yacy/document/Tokenizer.java | 2 +- .../yacy/search/ranking/ReferenceOrder.java | 4 +- .../java/net/yacy/document/TokenizerTest.java | 39 +++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 test/java/net/yacy/document/TokenizerTest.java diff --git a/source/net/yacy/document/Tokenizer.java b/source/net/yacy/document/Tokenizer.java index ff2e94bff..ca5591795 100644 --- a/source/net/yacy/document/Tokenizer.java +++ b/source/net/yacy/document/Tokenizer.java @@ -170,7 +170,7 @@ public class Tokenizer { wsp.inc(); } else { // word does not yet exist, create new word entry - wordHandle = wordHandleCount++; + wordHandle = ++wordHandleCount; // let start pos with 1 wsp = new Word(wordHandle, wordInSentenceCounter, /* sentences.size() + */ 100); wsp.flags = this.RESULT_FLAGS.clone(); this.words.put(word.toLowerCase(), wsp); diff --git a/source/net/yacy/search/ranking/ReferenceOrder.java b/source/net/yacy/search/ranking/ReferenceOrder.java index fd6cf5be8..7e7376cbc 100644 --- a/source/net/yacy/search/ranking/ReferenceOrder.java +++ b/source/net/yacy/search/ranking/ReferenceOrder.java @@ -228,13 +228,13 @@ public class ReferenceOrder { assert this.ranking != null; final long tf = ((this.max.termFrequency() == this.min.termFrequency()) ? 0 : (((int)(((t.termFrequency()-this.min.termFrequency())*256.0)/(this.max.termFrequency() - this.min.termFrequency())))) << this.ranking.coeff_termfrequency); //System.out.println("tf(" + t.urlHash + ") = " + Math.floor(1000 * t.termFrequency()) + ", min = " + Math.floor(1000 * min.termFrequency()) + ", max = " + Math.floor(1000 * max.termFrequency()) + ", tf-normed = " + tf); - final int maxmaxpos = this.max.maxposition(); + final int maxmaxpos = this.max.maxposition(); // returns Integer.MIN_VALUE if positions empty final int minminpos = this.min.minposition(); final long r = ((256 - DigestURL.domLengthNormalized(t.urlhash())) << this.ranking.coeff_domlength) + ((this.max.urlcomps() == this.min.urlcomps() ) ? 0 : (256 - (((t.urlcomps() - this.min.urlcomps() ) << 8) / (this.max.urlcomps() - this.min.urlcomps()) )) << this.ranking.coeff_urlcomps) + ((this.max.urllength() == this.min.urllength() ) ? 0 : (256 - (((t.urllength() - this.min.urllength() ) << 8) / (this.max.urllength() - this.min.urllength()) )) << this.ranking.coeff_urllength) - + ((maxmaxpos == minminpos) ? 0 : (256 - (((t.minposition() - minminpos) << 8) / (maxmaxpos - minminpos))) << this.ranking.coeff_posintext) + + ((maxmaxpos == minminpos || maxmaxpos < 0) ? 0 : (256 - (((t.minposition() - minminpos) << 8) / (maxmaxpos - minminpos))) << this.ranking.coeff_posintext) + ((this.max.posofphrase() == this.min.posofphrase()) ? 0 : (256 - (((t.posofphrase() - this.min.posofphrase() ) << 8) / (this.max.posofphrase() - this.min.posofphrase()) )) << this.ranking.coeff_posofphrase) + ((this.max.posinphrase() == this.min.posinphrase()) ? 0 : (256 - (((t.posinphrase() - this.min.posinphrase() ) << 8) / (this.max.posinphrase() - this.min.posinphrase()) )) << this.ranking.coeff_posinphrase) + ((this.max.distance() == this.min.distance() ) ? 0 : (256 - (((t.distance() - this.min.distance() ) << 8) / (this.max.distance() - this.min.distance()) )) << this.ranking.coeff_worddistance) diff --git a/test/java/net/yacy/document/TokenizerTest.java b/test/java/net/yacy/document/TokenizerTest.java new file mode 100644 index 000000000..e54807105 --- /dev/null +++ b/test/java/net/yacy/document/TokenizerTest.java @@ -0,0 +1,39 @@ + +package net.yacy.document; + +import java.net.MalformedURLException; +import java.util.Map; +import net.yacy.cora.document.WordCache; +import net.yacy.kelondro.data.word.Word; +import org.junit.Test; +import static org.junit.Assert.*; + + +public class TokenizerTest { + + /** + * Test of words method, of class Tokenizer. + */ + @Test + public void testWords() throws MalformedURLException { + // pos = 1 2 3 4 5 6 7 8 9 10 // 1-letter words don't count + String text = "One word is not a sentence because words are just words."; + WordCache meaningLib = new WordCache(null); + boolean doAutotagging = false; + VocabularyScraper scraper = null; + + Tokenizer t = new Tokenizer(null, text, meaningLib, doAutotagging, scraper); + + Map words = t.words; + + // test extracted word information (position) + Word w = words.get("word"); + assertEquals("position of 'word' ", 2, w.posInText); + assertEquals("occurence of 'word' ", 1, w.occurrences()); + + w = words.get("words"); + assertEquals("position of 'words' ", 7, w.posInText); + assertEquals("occurence of 'words' ", 2, w.occurrences()); + } + +} From 120bf7e6e20c8b0cab4bcd656a626510d149a105 Mon Sep 17 00:00:00 2001 From: reger Date: Tue, 6 Sep 2016 03:18:02 +0200 Subject: [PATCH 33/33] implemented RWI WordReference to return the word position value (was always left empty) This is needed and enables existing word position ranking for RWI. The upcoming concurrency issue in word position min/max calculation were eliminated by iterator.hasHext check before next() access. --- .../kelondro/data/word/WordReferenceRow.java | 9 ++++++++- .../kelondro/data/word/WordReferenceVars.java | 17 ++++++++++++----- .../yacy/kelondro/rwi/AbstractReference.java | 16 ++++++++++++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/source/net/yacy/kelondro/data/word/WordReferenceRow.java b/source/net/yacy/kelondro/data/word/WordReferenceRow.java index 5575c06d7..7ec32879c 100644 --- a/source/net/yacy/kelondro/data/word/WordReferenceRow.java +++ b/source/net/yacy/kelondro/data/word/WordReferenceRow.java @@ -252,9 +252,16 @@ public final class WordReferenceRow extends AbstractReference implements WordRef return (0xff & this.entry.getColByte(col_hitcount)); } + /** + * First position of word in text + * @return Collection with one element + */ @Override public Collection positions() { - return new ArrayList(0); + int pos = (int) this.entry.getColLong(col_posintext); + ArrayList arr = new ArrayList(1); + arr.add(pos); + return arr; } @Override diff --git a/source/net/yacy/kelondro/data/word/WordReferenceVars.java b/source/net/yacy/kelondro/data/word/WordReferenceVars.java index af30c4db7..1cec8ad4e 100644 --- a/source/net/yacy/kelondro/data/word/WordReferenceVars.java +++ b/source/net/yacy/kelondro/data/word/WordReferenceVars.java @@ -60,10 +60,11 @@ public class WordReferenceVars extends AbstractReference implements WordReferenc public final byte[] urlHash; private String hostHash = null; private final char type; - private int hitcount, llocal, lother, phrasesintext, - posinphrase, posofphrase, - urlcomps, urllength, - wordsintext, wordsintitle; + private int hitcount, // how often appears this word in the text + llocal, lother, phrasesintext, + posinphrase, posofphrase, + urlcomps, urllength, + wordsintext, wordsintitle; private int virtualAge; private final Queue positions; private double termFrequency; @@ -210,6 +211,10 @@ public class WordReferenceVars extends AbstractReference implements WordReferenc return this.type; } + /** + * How often appears this word in the text + * @return + */ @Override public int hitcount() { return this.hitcount; @@ -259,7 +264,9 @@ public class WordReferenceVars extends AbstractReference implements WordReferenc this.hitcount, // how often appears this word in the text this.wordsintext, // total number of words this.phrasesintext, // total number of phrases - this.positions.isEmpty() ? 1 : this.positions.iterator().next(), // position of word in all words + + // TODO: positon 1 on empty positions may give high ranking scores for unknown pos (needs to be checked if 0 would be appropriate) + this.positions.isEmpty() ? -1 : this.positions.iterator().next(), // position of word in all words this.posinphrase, // position of word in its phrase this.posofphrase, // number of the phrase where word appears this.lastModified, // last-modified time of the document where word appears diff --git a/source/net/yacy/kelondro/rwi/AbstractReference.java b/source/net/yacy/kelondro/rwi/AbstractReference.java index 52c3193b0..e1097da85 100644 --- a/source/net/yacy/kelondro/rwi/AbstractReference.java +++ b/source/net/yacy/kelondro/rwi/AbstractReference.java @@ -63,9 +63,17 @@ public abstract class AbstractReference implements Reference { private static int max(Collection a) { if (a == null || a.isEmpty()) return Integer.MIN_VALUE; Iterator i = a.iterator(); + /* + expirienced concurrency issue with this short cut 2016-09-06 + on i.next w/o test of hasNext before + java.util.NoSuchElementException at java.util.concurrent.LinkedBlockingQueue$Itr.next(LinkedBlockingQueue.java:828) + if (a.size() == 1) return i.next(); if (a.size() == 2) return Math.max(i.next(), i.next()); int r = i.next(); + */ + int r = Integer.MIN_VALUE; + int s; while (i.hasNext()) { s = i.next(); @@ -77,9 +85,12 @@ public abstract class AbstractReference implements Reference { private static int min(Collection a) { if (a == null || a.isEmpty()) return Integer.MAX_VALUE; Iterator i = a.iterator(); + /* concurrency issue (see max()) if (a.size() == 1) return i.next(); if (a.size() == 2) return Math.min(i.next(), i.next()); int r = i.next(); + */ + int r = Integer.MAX_VALUE; int s; while (i.hasNext()) { s = i.next(); @@ -103,10 +114,11 @@ public abstract class AbstractReference implements Reference { if (positions().size() < 2) return 0; int d = 0; Iterator i = positions().iterator(); - int s0 = i.next(), s1; + // int s0 = i.next(), s1; // concurrency issue see max() + int s0 = -1, s1; while (i.hasNext()) { s1 = i.next(); - d += Math.abs(s0 - s1); + if (s0 > 0) d += Math.abs(s0 - s1); s0 = s1; } return d / (positions().size() - 1);