peony-extensions/0000775000175000017500000000000015156143275013040 5ustar fengfengpeony-extensions/common.json0000664000175000017500000000003315156143137015214 0ustar fengfeng{ "version": "3.2.2" } peony-extensions/peony-engrampa-menu-plugin/0000775000175000017500000000000015156143275020220 5ustar fengfengpeony-extensions/peony-engrampa-menu-plugin/peony-engrampa-menu-plugin_global.h0000664000175000017500000000221215156143137027063 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Meihong * */ #ifndef PEONYQTENGRAMPAMENUPLUGIN_GLOBAL_H #define PEONYQTENGRAMPAMENUPLUGIN_GLOBAL_H #include #if defined(PEONYENGRAMPAMENUPLUGIN_LIBRARY) # define PEONYQTENGRAMPAMENUPLUGINSHARED_EXPORT Q_DECL_EXPORT #else # define PEONYQTENGRAMPAMENUPLUGINSHARED_EXPORT Q_DECL_IMPORT #endif #endif // PEONYQTENGRAMPAMENUPLUGIN_GLOBAL_H peony-extensions/peony-engrampa-menu-plugin/translations/0000775000175000017500000000000015156143275022741 5ustar fengfengpeony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_zh_HK.ts0000664000175000017500000000466415156143275030272 0ustar fengfeng Peony::EngrampaMenuPlugin compress... 压缩... compress 壓縮 uncompress to current path 解壓縮到當前路徑 uncompress to specific path... 解壓縮到特定路徑... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension Peony-Qt 工程擴展 Engrampa Menu Extension Engrampa 功能表擴展 engrampa Menu Extension 归档菜单扩展 engrampa Menu Extension. 归档菜单扩展。 Compress 壓縮 Uncompress to current path 解壓縮到當前路徑 Uncompress to specific path... 解压缩到... peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_ug.ts0000664000175000017500000000373115156143137027671 0ustar fengfeng Peony::EngrampaMenuPlugin compress... 压缩... compress پىرېسلاش uncompress to current path مۇشۇ جايغا پىرېسلاش uncompress to specific path... غا پىرېسلاش.... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension پىرېسلاش قوشۇمچە سىستېما ھۆججىتى engrampa Menu Extension ئارخىپقا سېلىش تىزىملىكىنى كېڭەيتىش engrampa Menu Extension. 归档菜单扩展。 peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_ky_KG.ts0000664000175000017500000000442615156143275030267 0ustar fengfeng Peony::EngrampaMenuPlugin compress... кысуу... uncompress to current path ۇشۇل جايعا تىعىزدوو uncompress to specific path... عا تىعىزدوو.... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension تىعىزدوو قوشۇمچا ساامالىق ۅجۅتۉۉ Engrampa Menu Extension Engrampa تىزىمدىك كەڭەيتمەسى engrampa Menu Extension. меню кеңейтүү энграммасы. Compress تىعىزدوو Uncompress to current path Uncompress to specific path... peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_fr.ts0000664000175000017500000000473615156143275027676 0ustar fengfeng Peony::EngrampaMenuPlugin compress... 压缩... compress compresse uncompress to current path Décompresser sur le chemin actuel uncompress to specific path... décompresser sur un chemin spécifique... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension Peony-Qt engrampa Extension Engrampa Menu Extension engrampa Menu Extension engrampa Menu Extension engrampa Menu Extension. 归档菜单扩展。 Compress Uncompress to current path Uncompress to specific path... peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_bo_CN.ts0000664000175000017500000000521015156143275030233 0ustar fengfeng Peony::EngrampaMenuPlugin compress... 压缩... compress སྡུད་སྒྲིལ། uncompress to current path འདིར་ཇེ་ཉུང་དུ་བཏང་། uncompress to specific path... ཇེ་ཉུང་དུ་བཏང་། uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension བསྒར་ལྷུ་སྡུད་སྒྲིལ། Engrampa Menu Extension Engrampa Menu Extension engrampa Menu Extension 归档菜单扩展 engrampa Menu Extension. 归档菜单扩展。 Compress གནོན་བཙིར། Uncompress to current path འདིར་ཇེ་ཉུང་དུ་བཏང་། Uncompress to specific path... ཇེ་ཉུང་དུ་བཏང་། peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_ug_CN.ts0000664000175000017500000000440515156143275030253 0ustar fengfeng Peony::EngrampaMenuPlugin compress... قىسقىرا... uncompress to current path نۆۋەتتىكى يولنى قىستۇرماي uncompress to specific path... كونكېرت يولنى قىستۇرماي... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension مودەن-Qt engrampa Extension Engrampa Menu Extension engrampa Menu Extension. engrampa تىزىملىكىنى ئۇزارتىش. Compress Uncompress to current path Uncompress to specific path... peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_de.ts0000664000175000017500000000476715156143275027663 0ustar fengfeng Peony::EngrampaMenuPlugin compress... 压缩... compress komprimieren uncompress to current path Dekomprimieren Sie auf den aktuellen Pfad uncompress to specific path... Dekomprimieren Sie auf einen bestimmten Pfad... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension Pfingstrose-Qt engrampa Erweiterung Engrampa Menu Extension engrampa Menu Extension engrampa Menüerweiterung engrampa Menu Extension. 归档菜单扩展。 Compress Uncompress to current path Uncompress to specific path... peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_mn.ts0000664000175000017500000000525415156143275027675 0ustar fengfeng Peony::EngrampaMenuPlugin compress... ᠠᠪᠴᠢᠭᠤᠯᠬᠤ... compress ᠪᠠᠭᠠᠰᠬᠠ ᠃ uncompress to current path ᠡᠨᠡ ᠬᠡᠰᠡᠭ ᠤ᠋ᠨ ᠠᠪᠴᠢᠭᠤᠯᠤᠯ ᠢ᠋ ᠳᠠᠢᠯᠬᠤ uncompress to specific path... ᠠᠪᠴᠢᠭᠤᠯᠤᠯ ᠢ᠋ ᠳᠠᠢᠯᠬᠤ... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension ᠠᠪᠴᠢᠭᠤᠯᠬᠤ ᠤᠭᠯᠤᠷᠭ᠎ᠠ ᠲᠤᠨᠤᠭ Engrampa Menu Extension MATEᠰᠢᠷᠡᠬᠡᠨ ᠨᠢᠭᠤᠷ ᠤ᠋ᠨ ᠣᠷᠴᠢᠨ engrampa Menu Extension. ᠳᠠᠩᠰᠠᠨ ᠳ᠋ᠤ᠌ ᠤᠷᠤᠭᠤᠯᠬᠤ ᠲᠤᠪᠶᠤᠭ ᠤ᠋ᠨ ᠦᠷᠭᠡᠳᠭᠡᠯ. Compress ᠠᠪᠴᠢᠭᠤᠯᠬᠤ Uncompress to current path ᠡᠨᠳᠡ ᠬᠦᠷᠲᠡᠯ᠎ᠡ ᠠᠯᠭᠤᠷᠬᠠᠨ᠎ᠠ ᠃ Uncompress to specific path... ᠠᠭᠰᠢᠭᠠᠬᠤ ᠁ peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_zh_CN.ts0000664000175000017500000000462015156143275030260 0ustar fengfeng Peony::EngrampaMenuPlugin compress... 压缩... compress 压缩 uncompress to current path 解压缩到此处 uncompress to specific path... 解压缩到... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension 压缩插件 Engrampa Menu Extension 归档菜单扩展 engrampa Menu Extension 归档菜单扩展 engrampa Menu Extension. 归档菜单扩展。 Compress 压缩 Uncompress to current path 解压缩到此处 Uncompress to specific path... 解压缩到... peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_es.ts0000664000175000017500000000473615156143275027676 0ustar fengfeng Peony::EngrampaMenuPlugin compress... 压缩... compress comprimir uncompress to current path Descomprimir a la ruta actual uncompress to specific path... descomprimir a una ruta específica... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension Extensión de engrampa Peony-Qt Engrampa Menu Extension engrampa Menu Extension engrampa Extensión de menú engrampa Menu Extension. 归档菜单扩展。 Compress Uncompress to current path Uncompress to specific path... peony-extensions/peony-engrampa-menu-plugin/translations/peony-engrampa-menu_kk_KZ.ts0000664000175000017500000000442615156143275030274 0ustar fengfeng Peony::EngrampaMenuPlugin compress... сығымдағыш... uncompress to current path وسى جايغا نىعىزداۋ uncompress to specific path... عا نىعىزداۋ.... uncompress to specific path 解压缩到... Peony-Qt KArchive Menu Extension 文件压缩插件 KArchive Menu Extension. 压缩菜单扩展。 Peony-Qt engrampa Extension نىعىزداۋ قوسىمشا سەستيما حۇجاتى Engrampa Menu Extension Engrampa تٸزٸمدٸك كەڭەيتپەسى engrampa Menu Extension. engrampa мәзірін кеңейту. Compress رازينكەلەۋ Uncompress to current path Uncompress to specific path... peony-extensions/peony-engrampa-menu-plugin/engrampa-menu-plugin.cpp0000664000175000017500000001320115156143275024751 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Meihong * */ #include "engrampa-menu-plugin.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Peony; EngrampaMenuPlugin::EngrampaMenuPlugin(QObject *parent) : QObject (parent) { QTranslator *t = new QTranslator(this); qDebug()<<"system().name:"<load(":/translations/peony-engrampa-menu_"+QLocale::system().name()); QApplication::installTranslator(t); } QList EngrampaMenuPlugin::menuActions(Types types, const QString &uri, const QStringList &selectionUris) { QList actions; if (types == MenuPluginInterface::DirectoryView || types == MenuPluginInterface::DesktopWindow) { //fix uninstall engrampa still has options issue, link to bug#119964 if (! QFile::exists("/usr/bin/engrampa")) return actions; if (! selectionUris.isEmpty()) { QUrl url = selectionUris.first(); if (url.path() == QStandardPaths::writableLocation(QStandardPaths::HomeLocation)) { if (types == MenuPluginInterface::DesktopWindow) { return actions; } } auto info = FileInfo::fromUri(selectionUris.first()); //special type mountable, return qDebug()<<"info isVirtual:"<isVirtual()<mimeType(); if (selectionUris.first().startsWith("computer:///") || selectionUris.first().startsWith("trash:///") || selectionUris.first().startsWith("recent:///") || selectionUris.first().startsWith("filesafe:///") || info->isVirtual()) return actions; QFileInfo file(selectionUris.first()); QAction *compress = new QAction(QIcon::fromTheme("application-zip"), tr("Compress"), nullptr); actions<. * * Authors: Meihong * */ #ifndef ENGRAMPAMENUPLUGIN_H #define ENGRAMPAMENUPLUGIN_H #include "peony-engrampa-menu-plugin_global.h" #include namespace Peony { class PEONYQTENGRAMPAMENUPLUGINSHARED_EXPORT EngrampaMenuPlugin: public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit EngrampaMenuPlugin(QObject *parent = nullptr); PluginInterface::PluginType pluginType() override {return PluginInterface::MenuPlugin;} const QString name() override {return tr("Peony-Qt engrampa Extension");} const QString description() override {return tr("Engrampa Menu Extension");} const QIcon icon() override {return QIcon::fromTheme("application-zip");} void setEnable(bool enable) override {m_enable = enable;} bool isEnable() override {return m_enable;} QString testPlugin() override {return "test compress";} QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; bool is_compressed_file(QString file_name); private: bool m_enable; QStringList m_file_type_list = {"7z","ar","bz","bz2","cbz","deb", "ear","exe","gz","jar","tar","tar.7z", "tar.bz2","tar.gz","tar.lzma","rar","tar.xz","war","zip","lzma","xz","tgz", "odp", "ods", "otp", "ott", "ots", "odt", "iso"}; }; } #endif // ENGRAMPAMENUPLUGIN_H peony-extensions/README.md0000664000175000017500000000434515156143137014322 0ustar fengfeng# peony-extensions ## Getting Start - Make sure all build dependencies have been installed in your machine. You can reference debian/control for building depends. * Create *build* directory and change to it, then build with cmake. The follow commands will auto build extensions as library. ``` sh #cmake .. qmake .. make ``` - Put the generated libraries into &&[QT_INSTALL_LIBS]/peony-extensions. - Test the extension with peony. ## Current extensions - **terminal**, open terminal with right click menu. - **shared**, a share properties page in properties window. - ~~**admin**, open a directory or a file as root with menu.~~ - **parchives**, compress/uncompress files with menu. - **computer view**, a special view for displaying computer:///. - **send-to-device**, provides a directory menu action for sending selected files to mounted device. - **bluetooth**, provides a directory menu action for sneding selected files by bluetooth, depending on ukui-bluetooth. - **set-wallparper**, privides a directory menu action for set desktop background. ## binary compatibility Peony-Extensions strongly relies on Peony library, that means the binary files of those plugins might not compat with Peony when the library updated and some API changed. Sometimes the old plugins will not be loaded, or will make Peony crashed due to call an unmatch method. To avoid the binary compatibility problems, we add a version checkment in latest version both in this project and Peony. The plugins' version infomation are loaded from [common.json](common.json), this must match to the Peony VERSION marco. When peony extensions doesn't work or can't be build correctly, please consider using latest Peony library built locally, and rebuild this project again. ## Translations Unlike Peony, Peony Extensions translations job is relatively primitive and crude. The main idea is every plugin translates itself, and compile translations resources with code (qrc). When you are going to translate peony extensions (I created), here are the mainly steps. - use lupdate to generate .ts file for your language, the naming rules reference existing files. - use lrelease to publish .ts file to .qm file. - modify the project's .qrc file, add your .qm file into recources to be compiled. peony-extensions/man/0000775000175000017500000000000015156143275013613 5ustar fengfengpeony-extensions/man/peony-intelligent-data-management-service.10000664000175000017500000000215115156143275024021 0ustar fengfeng.\" Man page for peony-intelligent-data-management-service .\" Generated manually based on help output .TH PEONY-INTELLIGENT-DATA-MANAGEMENT-SERVICE 1 "February 2026" "peony-extensions" "User Commands" .SH NAME peony-intelligent-data-management-service \- Intelligent data management service for Peony file manager .SH SYNOPSIS .B peony-intelligent-data-management-service [\fIoptions\fR] .SH DESCRIPTION .B peony-intelligent-data-management-service is a background service that provides intelligent data management capabilities for the Peony file manager spaces, offering advanced file organization. .PP This service is typically started automatically by the session manager or D-Bus activation, but it can be controlled manually using the options below. .SH OPTIONS .TP .B \-h, \-\-help Displays help on commandline options. .TP .B \-\-help-all Displays help, including generic Qt options. .TP .B \-q, \-\-quit Exit the service. .SH AUTHOR This manual page was written for the Debian/Ubuntu system but may be used by others. .SH COPYRIGHT Copyright \(co 2024-2026 KylinSoft Co., Ltd. License GPL-3+. .SH SEE ALSO .BR peony (1) peony-extensions/peony-intelligent-plugin/0000775000175000017500000000000015156143275020002 5ustar fengfengpeony-extensions/peony-intelligent-plugin/peony-intelligent-plugin.pro0000664000175000017500000000010315156143275025460 0ustar fengfengTEMPLATE = subdirs SUBDIRS += \ peony-intelligent-search-view peony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/0000775000175000017500000000000015156143275025663 5ustar fengfengpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/searchresultmodel.cpp0000775000175000017500000016613215156143275032130 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #include "searchresultmodel.h" #include "QUrl" #include "searchresultmodel.h" #include #include #include #include #include #include #include #include #include #include "search-view.h" #include "file-rename-operation.h" #include "thumbnail/searchthumbnailmanager.h" #include #include QLocale locale = QLocale(QLocale::system().name()); QCollator comparer = QCollator(locale); using namespace Peony; SearchFileItemModel::SearchFileItemModel(QObject *parent) : QAbstractItemModel(parent) { qDBusRegisterMetaType>(); qRegisterMetaType>("QList"); QDBusConnection connection = QDBusConnection::sessionBus(); if (!connection.isConnected()) { qDebug() << "信号创建失败:" << connection.lastError().message(); return ; } // 连接信号 bool connected = connection.connect("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller", "searchfileReload", this,SLOT(addFileNameResults(QList,QString))); connected = connection.connect("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller", "searchfileReloadByBatch", this,SLOT(addFileNameResultsByBatch(QList,QString,bool))); connection.connect("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller", "searchContentReload", this,SLOT(addContentResults(QList,QString))); // connection.connect("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller", // "searchTextContentReload", this,SLOT(addTextContentResults(QList,QString))); auto settings = GlobalSettings::getInstance(); m_showFileExtension = settings->isExist(SHOW_FILE_EXTENSION)? settings->getValue(SHOW_FILE_EXTENSION).toBool(): true; connect(GlobalSettings::getInstance(), &GlobalSettings::valueChanged, this, [=] (const QString& key) { if (SHOW_FILE_EXTENSION == key) { m_showFileExtension= GlobalSettings::getInstance()->getValue(key).toBool(); beginResetModel(); endResetModel(); Q_EMIT expand(0); } }); m_maxConcurrentThumbnails = 100; // 同时最多处理100个缩略图任务 m_currentThumbnailTasks = 0; } SearchFileItemModel::~SearchFileItemModel() { m_fileResults.clear(); m_contentResults.clear(); for (auto& pair : m_iconMap) { if (!pair.second.isNull()) { pair.second.detach(); pair.second = QIcon(); } } m_iconMap.clear(); QPixmapCache::clear(); malloc_trim(0); } QModelIndex SearchFileItemModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); if (!parent.isValid()) { // 一级节点(分类节点) switch (row) { case 0: return createIndex(row, column, FILE_CATEGORY_ID); case 1: return createIndex(row, column, CONTENT_CATEGORY_ID); //case 2: return createIndex(row, column, TEXT_CONTENT_CATEGORY_ID); default: return QModelIndex(); } } // 二级节点(结果项) quintptr parentId = parent.internalId(); if (parentId == FILE_CATEGORY_ID) { if (row < m_fileResults.size()) { return createIndex(row, column, FILE_ITEM_BASE_ID + row); } } else if (parentId == CONTENT_CATEGORY_ID) { if (row < m_contentResults.size()) { return createIndex(row, column, CONTENT_ITEM_BASE_ID + row); } } // else if (parentId == TEXT_CONTENT_CATEGORY_ID) { // if (row < m_textContentResults.size()) { // return createIndex(row, column, TEXT_CONTENT_ITEM_BASE_ID + row); // } // } return QModelIndex(); } QModelIndex SearchFileItemModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); quintptr id = index.internalId(); // 一级节点没有父节点 if (id <= CONTENT_CATEGORY_ID) return QModelIndex(); // 文件项的子节点 if (isFileItem(id)) { return createIndex(0, 0, FILE_CATEGORY_ID); } // 内容项的子节点 else if (isContentItem(id)) { return createIndex(1, 0, CONTENT_CATEGORY_ID); } // else if(isTextContentItem(id)){ // return createIndex(2, 0, TEXT_CONTENT_CATEGORY_ID); // } return QModelIndex(); } int SearchFileItemModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { // 根节点 return 2; // 两个分类节点 } quintptr id = parent.internalId(); if (isFileCategory(id)) { return m_fileResults.size(); } else if (isContentCategory(id)) { return m_contentResults.size(); }/*else if (isTextContentCategory(id)) { return m_textContentResults.size(); }*/ return 0; // 结果项没有子节点 } int SearchFileItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 4; // 名称、修改日期、类型、大小 } QVariant SearchFileItemModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); quintptr id = index.internalId(); int column = index.column(); int row; QStringList item; int size =0; // 分类节点数据 if (isFileCategory(id)) { if (column == 0 && role == Qt::DisplayRole) { return tr("File Name Match"); } return QVariant(); } else if (isContentCategory(id)) { if (column == 0 && role == Qt::DisplayRole) { #ifdef ARCH_AVAILABLE_AI return tr("Intelligent Content Recognition Match"); #else return tr("Text Content"); #endif } return QVariant(); }/*else if(isTextContentCategory(id)){ if (column == 0 && role == Qt::DisplayRole) { return tr("Text Content"); } }*/ else if (isFileItem(id)) { row = id - FILE_ITEM_BASE_ID; item = m_fileResults[row]; size = m_fileResults.size(); } else if(isContentItem(id)){ row = id - CONTENT_ITEM_BASE_ID; item = m_contentResults[row]; size = m_contentResults.size(); } // else if(isTextContentItem(id)){ // row = id - TEXT_CONTENT_ITEM_BASE_ID; // item = m_textContentResults[row]; // size = m_textContentResults.size(); // } if (row >= 0 && row < size) { switch (role) { case Qt::DisplayRole: switch (column) { case 0: { if(!m_showFileExtension){ auto info = Peony::FileInfo::fromUri(item.value(0)); if (info->isDir()||(!item.value(4).contains("."))) { return QVariant(item.value(4)); } return QVariant(FileUtils::getBaseNameOfFile(item.value(4))); }else return QVariant(item.value(4)); } case 1: return item.value(1);//modifytime format case 2: return item.value(2);//filetype case 3: return item.value(3);//sizeformat } break; // case Qt::BackgroundRole: // if (item->type() == SearchFileItem::ContentItem) // return QColor(240, 245, 255); // break; case Qt::UserRole: return item.value(0);//uri break; case Qt::UserRole+1: return item.value(5);//filepath break; case Qt::UserRole+2: return item.value(7);//fileminetyle break; case Qt::UserRole+3: return item.value(8);//int size break; case Qt::UserRole+4: return item.value(9);//modifytime int break; case Qt::UserRole+5: return item.value(6);//iconname break; case Qt::DecorationRole: if (index.column() == 0) { if(!m_isAiavailable && isContentItem(id) && item.value(6)=="") return ""; QIcon icon = QIcon::fromTheme(item.value(6),QIcon::fromTheme("unknown")); auto it = m_iconMap.find(item.value(5)); if (it != m_iconMap.end()) { QIcon thumbnailIcon = it->second; // 获取原始指针但不转移所有权 return thumbnailIcon.isNull()?icon:thumbnailIcon; } return icon; } break; } } return QVariant(); } QVariant SearchFileItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return tr("Name"); case 1: return tr("Modified Date"); case 2: return tr("File Type"); case 3: return tr("File Size"); } } return QVariant(); } Qt::ItemFlags SearchFileItemModel::flags(const QModelIndex &index) const { if (index.isValid()) { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (index.column() == 0) { flags |= Qt::ItemIsDragEnabled; flags |= Qt::ItemIsEditable; } return flags; } } QMimeData* SearchFileItemModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QStringList uris; QStringList encodedUris; for (const QModelIndex &index : indexes) { if (index.isValid() && index.column() == 0) { QString uri = data(index, Qt::UserRole).toString(); // 假设 URI 存储在 UserRole 中 uris << uri; encodedUris << uri; } } QList urlList; for (const QString &uri : uris) { urlList << QUrl(uri); } mimeData->setUrls(urlList); auto encodedString = encodedUris.join(" "); mimeData->setData("peony-qt/encoded-uris", encodedString.toUtf8()); auto string = uris.join(" "); mimeData->setText(string); return mimeData; } void SearchFileItemModel::addFileNameResults(QList fileResults,QString clientid) { // QTimer::singleShot(500, this, [this, fileResults, clientid]() {//防止频繁更新导致UI界面显示空白异常 // if (!m_fileResults.isEmpty()) { // beginRemoveRows(index(0, 0), 0, m_fileResults.size() - 1); // m_fileResults.clear(); // endRemoveRows(); // } // if (fileResults.isEmpty() || (clientid!=m_clientId)){ // if(searchContentfinish /*&& searchTextContentFinish*/){ // Q_EMIT searchFinished(); // } // searchFilefinish = true; // return; // } // beginInsertRows(index(0, 0), 0, fileResults.size() - 1); // m_fileResults = fileResults; // searchFilefinish = true; // endInsertRows(); // if(searchContentfinish /*&& searchTextContentFinish*/){ // Q_EMIT searchFinished(); // } // Q_EMIT resizeColFitContent(); // }); } void SearchFileItemModel::addContentResults(QList contentResults,QString clientid) { if (!m_contentResults.isEmpty()) { beginRemoveRows(index(1, 0), 0, m_contentResults.size() - 1); m_contentResults.clear(); endRemoveRows(); } if (contentResults.isEmpty() || (clientid!=m_clientId)){ if(searchFilefinish /*&& searchTextContentFinish*/){ Q_EMIT searchFinished(); } searchContentfinish = true; QTimer::singleShot(100, this, [this]() { if (!m_isAiavailable) { addDefaultContent(); } }); return; } beginInsertRows(index(1, 0), 0, contentResults.size() - 1); m_contentResults = contentResults; searchContentfinish = true; endInsertRows(); if(searchFilefinish /*&& searchTextContentFinish*/){ Q_EMIT searchFinished(); } loadContentIcon(); Q_EMIT resizeColFitContent(); } void SearchFileItemModel::clearResult() { beginRemoveRows(index(0, 0), 0, m_fileResults.size() - 1); m_fileResults.clear(); endRemoveRows(); beginRemoveRows(index(1, 0), 0, m_contentResults.size() - 1); m_contentResults.clear(); endRemoveRows(); } void SearchFileItemModel::clearAll() { Q_EMIT layoutAboutToBeChanged(); m_fileResults.clear(); if(m_isAiavailable) m_contentResults.clear(); m_iconMap.clear(); Q_EMIT layoutChanged(); } void SearchFileItemModel::keywordchange(QString keyword){ m_keyword = keyword; clearAll(); } void SearchFileItemModel::loadContentIcon(){ QModelIndex sourceRoot = QModelIndex(); // 根节点索引 // 获取根节点的行数(子项数量) int Count = rowCount(sourceRoot); int i = int(CONTENT_CATEGORY_ID) -1; QModelIndex catIndex = index(i, 0, sourceRoot); int count = rowCount(catIndex); QModelIndex topLeft = index(0, 0, catIndex); QModelIndex bottomRight = index(count - 1,columnCount(catIndex) -1, catIndex); for (int j = 0; j < count; ++j) { QModelIndex childIndex = index(j, 0, catIndex); QString path = data(childIndex, Qt::UserRole+1).toString(); QString fileType = data(childIndex, Qt::UserRole+2).toString(); if(fileType.startsWith("text")){ continue; } searchThumbnailManager *manager = new searchThumbnailManager(nullptr); manager->setStrictedMode(true); manager->startPreview(path); connect(manager, &searchThumbnailManager::thumbnailFinished, this, [=](const QString &path, const QImage &image, const QString &errorMessage){ QMutexLocker locker(&m_dataMutex); QPixmap pixmap = QPixmap::fromImage(image); QIcon icon; icon.addPixmap(QPixmap::fromImage(image)); m_iconMap[path] = icon; Q_EMIT dataChanged(topLeft, bottomRight); manager->deleteLater(); }); } } bool SearchFileItemModel::isSortingEnabled() { return m_SortingEnabled; } void SearchFileItemModel::addFileNameResultsByBatch(QList fileResults, QString clientid, bool finish) { if (fileResults.isEmpty() || clientid != m_clientId) { if(finish) { searchFilefinish = true; if(searchContentfinish) { Q_EMIT searchFinished(); } m_SortingEnabled = true; } return; } // 批量插入数据 int startPos = m_fileResults.size(); int endPos = startPos + fileResults.size() - 1; beginInsertRows(index(0, 0), startPos, endPos); m_fileResults.append(fileResults); endInsertRows(); m_SortingEnabled = false; // 异步生成缩略图 if(finish) { m_SortingEnabled = true; if(searchContentfinish) { Q_EMIT searchFinished(); } searchFilefinish = true; } loadNextBatchIcons(fileResults); } void SearchFileItemModel::loadNextBatchIcons(QList infolist) { int batchCount = infolist.size(); if (batchCount == 0) return; // 将需要加载缩略图的文件路径添加到队列中 for (int i = 0; i < batchCount; ++i) { QString path = infolist[i][5]; QString fileType = infolist[i][7]; // 只对非文本文件进行缩略图加载 if (!fileType.startsWith("text")) { m_thumbnailQueue.append(path); } } // 开始处理缩略图队列 processThumbnailQueue(); } void SearchFileItemModel::processThumbnailQueue() { // 如果队列为空或已达到最大并发数,则返回 if (m_thumbnailQueue.isEmpty() || m_currentThumbnailTasks >= m_maxConcurrentThumbnails) { return; } // 计算本次可以处理的任务数 int tasksToStart = qMin(m_maxConcurrentThumbnails - m_currentThumbnailTasks, m_thumbnailQueue.size()); for (int i = 0; i < tasksToStart; ++i) { QString path = m_thumbnailQueue.takeFirst(); m_currentThumbnailTasks++; searchThumbnailManager *manager = new searchThumbnailManager(nullptr); manager->setStrictedMode(true); manager->startPreview(path); connect(manager, &searchThumbnailManager::thumbnailFinished, this, [=](const QString &path, const QImage &image, const QString &errorMessage) { if (!image.isNull()) { QMutexLocker locker(&m_dataMutex); QIcon icon; icon.addPixmap(QPixmap::fromImage(image)); m_iconMap[path] = icon; } // 减少当前任务计数 m_currentThumbnailTasks--; // 删除manager对象 manager->deleteLater(); // 触发界面更新(可以考虑批量更新) QModelIndex sourceRoot = QModelIndex(); QModelIndex catIndex = index(0, 0, sourceRoot); if (catIndex.isValid()) { int rowCountVal = rowCount(catIndex); if (rowCountVal > 0) { QModelIndex topLeft = index(0, 0, catIndex); QModelIndex bottomRight = index(rowCountVal - 1, columnCount(catIndex) - 1, catIndex); Q_EMIT dataChanged(topLeft, bottomRight); } } // // 继续处理队列中的其他任务 if (!m_thumbnailQueue.isEmpty()) { QTimer::singleShot(0, this, &SearchFileItemModel::processThumbnailQueue); } }); } } void SearchFileItemModel::updateSearchstatus(bool isFinish){ searchFilefinish = isFinish; searchContentfinish = isFinish; } void SearchFileItemModel::setClientId(QString clientid) { m_clientId = clientid; } void SearchFileItemModel::addDefaultContent() { if (!m_isAiavailable){ if(searchFilefinish){ Q_EMIT searchFinished(); } searchContentfinish = true; } QString Warning; #ifdef ARCH_AVAILABLE_AI Warning = tr("To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】Open \"AI Index\" "); #else if(isFileContentOn()){ return; } Warning = tr("To Obtain the Matching Results of Text, Pictures, Videos. Please Open \"File Content Index\""); #endif QStringList list; list<<""<<""<<""<<""<querySync(); QString displayName = info->displayName(); if (info->isDesktopFile()) { displayName = FileUtils::handleDesktopFileName(info->uri(), info->displayName()); } m_fileResults[i].replace(4, displayName); m_fileResults[i].replace(5, newPath); m_fileResults[i].replace(0, newUri); Q_EMIT dataChanged(topLeft, bottomRight, {Qt::UserRole+1, Qt::UserRole, Qt::DisplayRole}); return true; } } // 在内容结果中查找 for (int i = 0; i < m_contentResults.size(); ++i) { if (m_contentResults[i].value(5) == oldPath) { QModelIndex topLeft = index(i, 0, index(1, 0)); QModelIndex bottomRight = index(i, columnCount() - 1, index(1, 0)); auto info = Peony::FileInfo::fromUri(newUri); auto infoJob = new Peony::FileInfoJob(info); infoJob->querySync(); QString displayName = info->displayName(); if (info->isDesktopFile()) { displayName = FileUtils::handleDesktopFileName(info->uri(), info->displayName()); } m_contentResults[i].replace(4, displayName); m_contentResults[i].replace(5, newPath); m_contentResults[i].replace(0, newUri); Q_EMIT dataChanged(topLeft, bottomRight, {Qt::UserRole+1, Qt::UserRole, Qt::DisplayRole}); return true; } } return false; } bool SearchFileItemModel::hasDefaultContent() { return m_hasDefaultContent; } //QIcon SearchFileItemModel::getIcon(const QModelIndex &index) //{ // quintptr id = index.internalId(); // int row; // QStringList item; // if (isFileItem(id)) { // row = id - FILE_ITEM_BASE_ID; // item = m_fileResults[row]; // } // else if(isContentItem(id)){ // row = id - CONTENT_ITEM_BASE_ID; // item = m_contentResults[row]; // } // QIcon icon = QIcon::fromTheme(item.value(6),QIcon::fromTheme("unknown")); // QIcon thumbnailIcon = m_iconMap.value(id); // return thumbnailIcon.isNull()?icon:thumbnailIcon; //} //QMap SearchFileItemModel::getIconMap() //{ // return m_iconMap; //} SearchFilterProxyModel::SearchFilterProxyModel(QObject* parent) : QSortFilterProxyModel(parent) { auto settings = Peony::GlobalSettings::getInstance(); m_settings = settings; setDynamicSortFilter(false); } bool SearchFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); // 第一级项目总是显示且保持原始顺序 if (!sourceParent.isValid()) { return true; } // // 第二级项目应用过滤 if (!sourceParent.parent().isValid()) { auto sourcemodel = static_cast(sourceModel()); const QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent); const QModelIndex index1 = sourceModel()->index(sourceRow, 1, sourceParent); // 修改日期 const QModelIndex index2 = sourceModel()->index(sourceRow, 2, sourceParent); // 文件类型 const QModelIndex index3 = sourceModel()->index(sourceRow, 3, sourceParent); // 文件大小 QString displayName = sourceModel()->data(index0, Qt::DisplayRole).toString(); qint64 modifiedDate = sourceModel()->data(index1, Qt::UserRole+4).toLongLong(); QString fileType = sourceModel()->data(index2, Qt::UserRole+2).toString(); qint64 fileSize = sourceModel()->data(index3, Qt::UserRole+3).toLongLong(); if (!checkFileTypeFilter(fileType)) return false; if (! checkFileModifyTimeFilter(modifiedDate)) return false; bool isdir = fileType =="inode/directory"; if (! checkFileSizeOrTypeFilter(fileSize,isdir) ) return false; if (! checkFileNameFilter(displayName)) return false; quintptr id = index.internalId(); if(!needFileContent && sourcemodel->isContentItem(id)){ return false; } } return true; } void SearchFilterProxyModel::updateconditions(QString conditionsobj){ m_keywords.clear(); m_conditions.clear(); m_contains.clear(); needFileContent = true; m_keywordsContain = true; QJsonDocument doc = QJsonDocument::fromJson(conditionsobj.toUtf8()); QJsonObject jsonObj = doc.object(); int count = jsonObj["conditions count"].toInt(); for (int i = 0; i < count; i++) { auto conditionObj = jsonObj[QString::number(i)].toObject(); int type = conditionObj["condition type"].toInt(); bool contains = conditionObj["contains"].toBool(); if(type == int(Type::FileName)) { auto jsonArray = conditionObj["keywords"].toArray(); QStringList keywords; for (auto value : jsonArray) { if(!value.toString().isEmpty()) keywords << value.toString(); } m_keywords<rowCount(sourceRoot); ++i) { QModelIndex catIndex = sourceModel()->index(i, 0, sourceRoot); for (int j = 0; j < sourceModel()->rowCount(catIndex); ++j) { QModelIndex childIndex = sourceModel()->index(j, 0, catIndex); if (filterAcceptsRow(j, catIndex)) { QString uri = sourceModel()->data(childIndex, Qt::UserRole+1).toString(); if (!uri.isEmpty()) { urilist << uri; } } } } return urilist; } void SearchFilterProxyModel::update() { invalidateFilter(); } bool SearchFilterProxyModel::checkFileModifyTimeFilter(quint64 modifiedTime) const{ int ConditionType = int(Type::DateRange); int cur = m_conditions[ConditionType]; bool contains = m_contains[ConditionType]; if(!m_conditions.contains(ConditionType)) return true; if(cur==ALL_TYPE){ return contains; } auto time = QDateTime::currentMSecsSinceEpoch(); auto dateTime = QDateTime::fromMSecsSinceEpoch(time); //qDebug() << "cur QDateTime:" < 0 && size <= TINY_BASE) return contains; break; } case SMALL: //(16k-1M] { if(size > TINY_BASE && size <= SMALL_BASE) return contains; break; } case MEDIUM: //(1M-128M] { if(size > SMALL_BASE && size <= MEDIUM_BASE) return contains; break; } case BIG: //(128M-1G] { if(size > MEDIUM_BASE && size <= BIG_BASE) return contains; break; } case LARGE: //(1-4G] { if (size > BIG_BASE && size <= LARGE_BASE) return contains; break; } case GREAT: //>4G { if (size > LARGE_BASE) return contains; break; } default: break; } return !contains; } bool SearchFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { // 一级节点按原顺序倒序排列 Qt::SortOrder order = this->sortOrder(); bool resort = order == Qt::AscendingOrder; bool flag = left.row() > right.row(); if (!left.parent().isValid() && !right.parent().isValid()) {//固定一级节点排序 不让文件名匹配在内容匹配下面 return resort ? !flag:flag; } SearchFileItemModel *sourceRootModel = static_cast(sourceModel()); if(!sourceRootModel->isSortingEnabled()){ return left.row() < right.row(); } // 获取对应的SearchFileItemModel指针 if (left.isValid() && right.isValid()) { // 获取当前排序的列和顺序 int sortColumn = this->sortColumn(); // 获取对应的数据角色 int role ; switch (sortColumn) { case 0: // 名称列 role = Qt::DisplayRole; break; case 1: // 修改日期列 role = Qt::UserRole + 4; break; case 2: // 文件类型列 role = Qt::UserRole + 2; break; case 3: // 文件大小列 role = Qt::UserRole + 3; break; default: return QSortFilterProxyModel::lessThan(left, right); } QVariant leftData = sourceRootModel->data(left, role); QVariant rightData = sourceRootModel->data(right, role); // 根据数据类型进行比较 if (role == Qt::UserRole + 4 || role == Qt::UserRole + 3) { // 处理数值类型(qint64) qint64 leftValue = leftData.toLongLong(); qint64 rightValue = rightData.toLongLong(); if(leftValue == rightValue) goto default_sort; bool result = (leftValue > rightValue); return result; } else if (role == Qt::UserRole + 2) { // 文件类型使用字符串比较 QString leftDisplayType = leftData.toString(); QString rightDisplayType= rightData.toString(); if (leftDisplayType == rightDisplayType) { goto default_sort; } if (m_use_default_name_sort_order) { bool lesser = true; if(startWithChinese(leftDisplayType) && ! startWithChinese(rightDisplayType)) { // fix #103343 lesser = true; if (sortOrder() == Qt::AscendingOrder) return lesser; return !lesser; } else if(! startWithChinese(leftDisplayType) && startWithChinese(rightDisplayType)) { lesser = false; if (sortOrder() == Qt::AscendingOrder) return lesser; return !lesser; } else return comparer.compare(leftDisplayType, rightDisplayType) > 0; } return comparer.compare(leftDisplayType, rightDisplayType) > 0; }else if(role == Qt::DisplayRole){ //文件名 goto default_sort; }/*else { // 其他字符串类型比较 QString leftStr = leftData.toString(); QString rightStr = rightData.toString(); return QString::localeAwareCompare(leftStr, rightStr) < 0; }*/ default_sort: const QModelIndex index0 = sourceModel()->index(left.row(), 0, left.parent()); const QModelIndex index1 = sourceModel()->index(right.row(), 0, right.parent()); QString leftDisplayName = sourceRootModel->data(index0, Qt::DisplayRole).toString(); QString rightDisplayName = sourceRootModel->data(index1, Qt::DisplayRole).toString(); if (m_use_default_name_sort_order) { //fix chinese first sort wrong issue, link to bug#70836 //fix bug#97408,change indicator meanings bool lesser = true; if(startWithChinese(leftDisplayName) && ! startWithChinese(rightDisplayName)) { // fix #103343 lesser = true; if (sortOrder() == Qt::AscendingOrder) return lesser; return !lesser; } else if(!startWithChinese(leftDisplayName) && startWithChinese(rightDisplayName)) { lesser = false; if (sortOrder() == Qt::AscendingOrder) return lesser; return !lesser; } else return comparer.compare(leftDisplayName, rightDisplayName) > 0; } return comparer.compare(leftDisplayName, rightDisplayName) > 0; } return QSortFilterProxyModel::lessThan(left, right); } void SearchFilterProxyModel::checkSortSettings() { m_use_global_sort = m_settings->getValue(USE_GLOBAL_DEFAULT_SORTING).toBool(); if (m_use_global_sort) { //m_show_hidden = m_settings->isExist(SHOW_HIDDEN_PREFERENCE)? m_settings->getValue(SHOW_HIDDEN_PREFERENCE).toBool(): false; m_use_default_name_sort_order = m_settings->isExist(SORT_CHINESE_FIRST)? m_settings->getValue(SORT_CHINESE_FIRST).toBool(): false; m_folder_first = m_settings->isExist(SORT_FOLDER_FIRST)? m_settings->getValue(SORT_FOLDER_FIRST).toBool(): true; } else { auto info = Peony::FileInfo::fromUri(m_uri); auto fileMetaInfo = Peony::FileMetaInfo::fromUri(m_uri); if (fileMetaInfo) { //m_show_hidden = fileMetaInfo->getMetaInfoVariant(SHOW_HIDDEN_PREFERENCE).isValid()? fileMetaInfo->getMetaInfoVariant(SHOW_HIDDEN_PREFERENCE).toBool(): false; m_use_default_name_sort_order = fileMetaInfo->getMetaInfoVariant(SORT_CHINESE_FIRST).isValid()? fileMetaInfo->getMetaInfoVariant(SORT_CHINESE_FIRST).toBool(): true; m_folder_first = fileMetaInfo->getMetaInfoVariant(SORT_FOLDER_FIRST).isValid()? fileMetaInfo->getMetaInfoVariant(SORT_FOLDER_FIRST).toBool(): true; } else { m_use_default_name_sort_order = true; m_folder_first = true; } } } void SearchFilterProxyModel::setCurrenturi(QString uri) { m_uri = uri; } bool SearchFilterProxyModel::startWithChinese(const QString &displayName) const { //NOTE: a newly created file might could not get display name soon. if (displayName.isEmpty()) { return false; } auto firstStrUnicode = displayName.at(0).unicode(); return (firstStrUnicode <=0x9FA5 && firstStrUnicode >= 0x4E00); } bool SearchFilterProxyModel::checkFileSizeOrTypeFilter(quint64 size, bool isDir) const{ int ConditionType = int(Type::FileSize); int cur = m_conditions[ConditionType]; bool contains = m_contains[ConditionType]; if(!m_conditions.contains(ConditionType)) return true; if(cur==ALL_TYPE){ return contains; } if(isDir) return false; return checkFileSizeFilter(size); } bool SearchFilterProxyModel::checkFileNameFilter(const QString &displayName) const{ if(m_keywords.isEmpty()) return true; for (auto key:m_keywords) { if (displayName.contains(key, Qt::CaseInsensitive)) return m_keywordsContain; } return false; } bool SearchFilterProxyModel::checkFileContentFilter(const QString &displayName) const{ if(!needFileContent) return false; return true; } bool SearchFilterProxyModel::checkFileTypeFilter(QString type) const { int ConditionType = int(Type::FileType); int cur = m_conditions[ConditionType]; bool contains = m_contains[ConditionType]; if(!m_conditions.contains(ConditionType)) return true; if(cur==ALL_TYPE){ return contains; } switch (cur) { case FILE_FOLDER: { if (type == Folder_Type) return contains; break; } case PICTURE: { if (type.contains(Image_Type)) return contains; break; } case VIDEO: { if (type.contains(Video_Type)) return contains; break; } case TXT_FILE: { if (type.contains(Text_Type)) return contains; break; } case WPS_FILE: { if (type.contains(Wps_Type)) return contains; break; } case AUDIO: { if (type.contains(Audio_Type) || type.contains("application/x-smaf")) return contains; break; } case OTHERS: { //exclude classfied types, show the rest other types if (type != Folder_Type && ! type.contains(Image_Type) && ! type.contains(Video_Type) && ! type.contains(Text_Type) && !type.contains(Wps_Type) && ! type.contains(Audio_Type) && !type.contains("application/x-smaf")) return contains; break; } default: break; } return !contains; } bool SearchFilterProxyModel::hasChildren(const QModelIndex& parent) const { if (!parent.isValid()) { return sourceModel()->hasChildren(mapToSource(parent)); } if (!parent.parent().isValid()) { return true; // 第一级项目总是显示为有子项 } return QSortFilterProxyModel::hasChildren(parent); } void TreeviewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) return; auto clipedUris = Peony::ClipboardUtils::getInstance()->getCutFileUris(); if (!clipedUris.isEmpty()){ if (clipedUris.contains(index.data(Qt::UserRole).toString())) { painter->setOpacity(0.5); } else { painter->setOpacity(1.0); } } else { painter->setOpacity(1.0); } // 第一层级或非第0列使用默认绘制 if (!index.parent().isValid() || index.column() != 0) { QStyledItemDelegate::paint(painter, option, index); return; } QStyleOptionViewItem opt = option; opt.textElideMode = Qt::ElideRight; initStyleOption(&opt, index); // 保存原始内容 QString text = opt.text; QIcon icon = opt.icon; // 1. 先绘制默认背景和焦点状态(关键修改) const QWidget* widget = opt.widget; QStyle* style = widget ? widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget); // 2. 准备绘制参数 QFont defaultFont = opt.font; QFont boldFont = defaultFont; boldFont.setBold(true); QFontMetrics fmNormal(defaultFont); QFontMetrics fmBold(boldFont); int xPos = opt.rect.left(); int yPos = opt.rect.top(); int availableWidth = opt.rect.width(); // 3. 绘制图标(如果有) if (!icon.isNull()) { const int iconLeftMargin = 2; const int iconSize = qMin(opt.decorationSize.width(), opt.rect.height() - 4); opt.rect.adjust(-18, 0, 0, 0); QRect iconRect(opt.rect.left() + iconLeftMargin, opt.rect.top() + (opt.rect.height() - iconSize) / 2, iconSize, iconSize); icon.paint(painter, iconRect, Qt::AlignLeft | Qt::AlignVCenter); xPos = iconRect.right() + 4; availableWidth -= (xPos - opt.rect.left()); } // 获取正确的文本颜色(根据选中状态) QColor textColor = opt.state & QStyle::State_Selected ? opt.palette.highlightedText().color() : opt.palette.text().color(); // 4. 检查是否是HTML内容 bool isHtml = Qt::mightBeRichText(text); // 如果没有关键字且不是HTML,直接绘制文本(带Elide) if (m_keyword.isEmpty() && !isHtml) { painter->save(); painter->setPen(textColor); QString elidedText = fmNormal.elidedText(text, Qt::ElideRight, availableWidth); painter->drawText(QRect(xPos, yPos, availableWidth, opt.rect.height()), Qt::AlignLeft | Qt::AlignVCenter, elidedText); painter->restore(); return; } // 如果是HTML内容,使用文本文档绘制 if (isHtml) { QTextDocument doc; doc.setDefaultFont(defaultFont); doc.setHtml(text); doc.setDocumentMargin(0); doc.setTextWidth(-1); if (doc.idealWidth() > availableWidth) { QString plainText = doc.toPlainText(); QString elidedPlain = fmNormal.elidedText(plainText, Qt::ElideRight, availableWidth); doc.setPlainText(elidedPlain); } qreal docHeight = doc.size().height(); qreal yOffset = (opt.rect.height() - docHeight) / 2; yOffset = qMax(0.0, yOffset); painter->save(); painter->translate(xPos-10, yPos + yOffset); QRect clip(0, 0, availableWidth, opt.rect.height()); painter->setClipRect(clip); QAbstractTextDocumentLayout::PaintContext ctx; ctx.palette = opt.palette; // 强制使用正确的文本颜色 if (opt.state & QStyle::State_Selected) { ctx.palette.setColor(QPalette::Text, opt.palette.highlightedText().color()); } doc.documentLayout()->draw(painter, ctx); painter->restore(); return; } // 5. 有关键字时,分段绘制(高亮 + Elide处理) QRegularExpression re("(" + QRegularExpression::escape(m_keyword) + ")", QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatchIterator it = re.globalMatch(text); int lastPos = 0; bool isElided = false; while (it.hasNext() && !isElided) { QRegularExpressionMatch match = it.next(); int start = match.capturedStart(); int length = match.capturedLength(); // 绘制匹配前的普通文本 if (start > lastPos) { QString before = text.mid(lastPos, start - lastPos); int beforeWidth = fmNormal.horizontalAdvance(before); if (xPos + beforeWidth > opt.rect.right()) { QString remainingText = text.mid(lastPos); QString elidedText = fmNormal.elidedText(remainingText, Qt::ElideRight, opt.rect.right() - xPos); painter->setFont(defaultFont); painter->setPen(textColor); // 使用正确的文本颜色 painter->drawText(QRect(xPos, yPos, opt.rect.right() - xPos, opt.rect.height()), Qt::AlignLeft | Qt::AlignVCenter, elidedText); isElided = true; break; } painter->setFont(defaultFont); painter->setPen(textColor); // 使用正确的文本颜色 painter->drawText(QRect(xPos, yPos, beforeWidth, opt.rect.height()), Qt::AlignLeft | Qt::AlignVCenter, before); xPos += beforeWidth; } // 绘制高亮关键字 if (!isElided) { QString keyword = match.captured(); int keywordWidth = fmBold.horizontalAdvance(keyword); if (xPos + keywordWidth > opt.rect.right()) { QString remainingText = text.mid(lastPos); QString elidedText = fmNormal.elidedText(remainingText, Qt::ElideRight, opt.rect.right() - xPos); painter->setFont(defaultFont); painter->setPen(textColor); // 使用正确的文本颜色 painter->drawText(QRect(xPos, yPos, opt.rect.right() - xPos, opt.rect.height()), Qt::AlignLeft | Qt::AlignVCenter, elidedText); isElided = true; break; } // 绘制高亮背景 painter->fillRect(QRect(xPos, yPos + 2, keywordWidth, opt.rect.height() - 4), QColor(0, 255, 0)); // 绿色背景 // 绘制关键字文本 painter->setFont(defaultFont); painter->setPen(textColor); // 选中时用黑色,否则用默认颜色 painter->drawText(QRect(xPos, yPos, keywordWidth, opt.rect.height()), Qt::AlignLeft | Qt::AlignVCenter, keyword); xPos += keywordWidth; lastPos = start + length; } } // 6. 绘制剩余文本(如果未Elide) if (!isElided && lastPos < text.length()) { QString after = text.mid(lastPos); int afterWidth = fmNormal.horizontalAdvance(after); if (xPos + afterWidth > opt.rect.right()) { after = fmNormal.elidedText(after, Qt::ElideRight, opt.rect.right() - xPos); } painter->setFont(defaultFont); painter->setPen(textColor); // 使用正确的文本颜色 painter->drawText(QRect(xPos, yPos, opt.rect.right() - xPos, opt.rect.height()), Qt::AlignLeft | Qt::AlignVCenter, after); } painter->restore(); } QSize TreeviewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); QFontMetrics fm(opt.font); int height = qMax(40, fm.height()); int width = fm.horizontalAdvance(opt.text); if (index.column() == 0 && !opt.icon.isNull()) { width += opt.decorationSize.width() + 8; } return QSize(width, height); } QWidget *TreeviewDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { TextEdit *edit = new TextEdit(parent); edit->setAcceptRichText(false); //edit->setContextMenuPolicy(Qt::CustomContextMenu); edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); edit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); edit->setWordWrapMode(QTextOption::NoWrap); edit->blockSignals(true); auto displayString = index.data(Qt::DisplayRole).toString(); auto displayName = index.data(Qt::UserRole + 1).toString(); auto uri = index.data(Qt::UserRole).toString(); auto suffix = displayName.remove(displayString); auto fsType = FileUtils::getFsTypeFromFile(uri); auto info = FileInfo::fromUri(uri); int32_t maxLength = 255; if (info->isDesktopFile()) { suffix = ".desktop"; } if (FileUtils::isFuseFileSystem(uri)) { fsType = "fuse.kyfs"; } if (fsType.contains("ext")) { maxLength = 255 - suffix.toLocal8Bit().length(); edit->setMaxLengthLimit(maxLength); } else if (fsType == "ecryptfs") { maxLength = 143 - suffix.toLocal8Bit().length(); edit->setMaxLengthLimit(maxLength); } else if (fsType == "udf") { maxLength = 254 - suffix.toLocal8Bit().length(); edit->setMaxLengthLimit(maxLength); } else if (fsType.contains("ntfs")) { edit->setLimitBytes(false); maxLength = 255 - suffix.length(); edit->setMaxLengthLimit(maxLength); } else if (fsType.contains("fuse.kyfs")) { edit->setLimitBytes(false); QDBusInterface iface ("com.kylin.file.system.fuse","/com/kylin/file/system/fuse","com.kylin.file.system.fuse",QDBusConnection::systemBus()); QDBusReply reply = iface.call("GetFilenameLength"); if (reply.isValid()) { maxLength = reply.value(); } maxLength = maxLength - suffix.length(); edit->setMaxLengthLimit(maxLength); } edit->blockSignals(false); // QTimer::singleShot(1, parent, [=]() { // this->updateEditorGeometry(edit, option, index); // }); // connect(edit, &TextEdit::textChanged, this, [=]() { // updateEditorGeometry(edit, option, index); // }); connect(edit, &TextEdit::textChanged, this, [=]() { auto text = edit->toPlainText(); //fix bug#220283, rename edit position wrong issue //short file name no need update to avoid position wrong edit->adjustText(); updateEditorGeometry(edit, option, index); }); connect(edit, &TextEdit::finishEditRequest, this, &TreeviewDelegate::slot_finishEdit); connect(edit, &QWidget::destroyed, this, [=]() { Q_EMIT isEditing(false); }); // connect(edit, &TextEdit::focusLost, [edit]() { // edit->finishEditRequest(); // }); return edit; } void TreeviewDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { TextEdit *edit = qobject_cast(editor); if (!edit) return; Q_EMIT isEditing(true); edit->setText(index.data(Qt::DisplayRole).toString()); auto cursor = edit->textCursor(); cursor.setPosition(0, QTextCursor::MoveAnchor); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); bool isDir = FileUtils::getFileIsFolder(index.data(Qt::UserRole).toString()); bool isDesktopFile = index.data(Qt::UserRole).toString().endsWith(".desktop"); bool isSoftLink = FileUtils::getFileIsSymbolicLink(index.data(Qt::UserRole).toString()); if (!isDesktopFile && !isSoftLink && !isDir && edit->toPlainText().contains(".") && !edit->toPlainText().startsWith(".")) { int n = 1; if(index.data(Qt::DisplayRole).toString().contains(".tar.")) //ex xxx.tar.gz xxx.tar.bz2 n = 2; while(n){ cursor.movePosition(QTextCursor::WordLeft, QTextCursor::KeepAnchor, 1); cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1); --n; } } //qDebug()<setTextCursor(cursor); } void TreeviewDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::updateEditorGeometry(editor, option, index); TextEdit *edit = qobject_cast(editor); if (!edit || !edit->m_backgroundEdit) return; QRect editRect = option.rect; auto leftShift = option.decorationSize.width()/2; editRect.setLeft(editRect.left() + leftShift); editRect.setWidth(editRect.width()); editor->setGeometry(editRect); edit->m_backgroundEdit->setGeometry(1, 2, edit->width() - 2, edit->height() - 4); QTimer::singleShot(0, edit, [=]() { qreal docHeight = edit->document()->size().height(); int availableHeight = edit->height(); int topMargin = (availableHeight - docHeight) / 2; topMargin = qMax(2, static_cast(topMargin)); edit->setMargins(1, topMargin, 1, 2); }); } void TreeviewDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { TextEdit *edit = qobject_cast(editor); if (!edit) return; auto text = edit->toPlainText(); if (text.isEmpty()) return; if (text == index.data(Qt::DisplayRole).toString()) return; //process special name . or .. or only space if (text == "." || text == ".." || text.trimmed() == "") return; if (! index.isValid()) return; auto view = qobject_cast(parent()); auto oldName = index.data(Qt::DisplayRole).toString(); if (text == oldName) { //create new file, should select the file or folder auto flags = QItemSelectionModel::Select|QItemSelectionModel::Rows; view->selectionModel()->select(index, flags); view->setFocus(); return; } if (view->getSelections().count() > 1) { auto fileOpMgr = FileOperationManager::getInstance(); QStringList lists = view->getSelections(); auto renameOp = new FileBatchRenameOperation(lists, text); connect(renameOp, &FileBatchRenameOperation::operationFinished, view, [=](){ auto info = renameOp->getOperationInfo().get(); auto uri = info->target(); QTimer::singleShot(100, view, [=](){ view->setSelections(QStringList()<scrollToSelection(uri); view->setFocus(); }); }, Qt::BlockingQueuedConnection); fileOpMgr->startOperation(renameOp, true); } else { auto fileOpMgr = FileOperationManager::getInstance(); QString ddd = index.data(Qt::UserRole).toString(); auto renameOp = new FileRenameOperation(index.data(Qt::UserRole).toString(), text); connect(renameOp, &FileRenameOperation::operationFinished, view, [=](){ auto info = renameOp->getOperationInfo().get(); auto uri = info->target(); QTimer::singleShot(100, view, [=](){ view->setSelections(QStringList()<scrollToSelection(uri); view->setFocus(); }); }, Qt::BlockingQueuedConnection); fileOpMgr->startOperation(renameOp, true); } } void TreeviewDelegate::slot_finishEdit() { auto edit = qobject_cast(sender()); commitData(edit); closeEditor(edit, QAbstractItemDelegate::SubmitModelCache); if(edit){ delete edit; edit = nullptr; } } TextEdit::TextEdit(QWidget *parent) : QTextEdit (parent) { // fix #164278, icon view text editor doesn't cover view item. // note on ukui platform theme, style panel frame is not visible. setFrameShape(QFrame::NoFrame); setAlignment(Qt::AlignLeft|Qt::AlignVCenter); setViewportMargins(1, 2, 1, 2); if (m_backgroundEdit == nullptr) { m_backgroundEdit = new QTextEdit(this); m_backgroundEdit->setReadOnly(true); m_backgroundEdit->setFrameShape(QTextEdit::NoFrame); m_backgroundEdit->setGeometry(1, 2, size().width() - 2, size().height() - 4); m_backgroundEdit->lower(); } } void TextEdit::adjustText() { if (m_max_length_limit) { //fix #154584 blockSignals(true); auto position = textCursor().position(); while (true) { if (m_limit_bytes) { auto local8Bit = toPlainText().toLocal8Bit(); if (local8Bit.length() <= m_max_length_limit) { break; } } else { if (toPlainText().length() <= m_max_length_limit) { break; } } if (position > 0) { position--; textCursor().beginEditBlock(); textCursor().setPosition(position); textCursor().deletePreviousChar(); textCursor().endEditBlock(); } else { break; } } blockSignals(false); } } void TextEdit::setMaxLengthLimit(int length) { m_max_length_limit = length; } void TextEdit::setLimitBytes(bool limitBytes) { m_limit_bytes = limitBytes; } void TextEdit::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { Q_EMIT finishEditRequest(); return; } return QTextEdit::keyPressEvent(e); } void TextEdit::focusOutEvent(QFocusEvent *e) { QTextEdit::focusOutEvent(e); Q_EMIT focusLost(); // 发出我们自己的信号 } void TextEdit::setMargins(int left, int top, int right, int bottom) { setViewportMargins(left, top, right, bottom); } bool isFileContentOn() { bool flage = false; const QByteArray id("org.ukui.search.settings"); if (QGSettings::isSchemaInstalled(id)) { auto settings = new QGSettings(id, QByteArray(), nullptr); QStringList keys = settings->keys(); if (keys.contains("contentIndexEnable")) { flage = settings->get("contentIndexEnable").toBool(); } delete settings; } return flage; } ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_global.hpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000000215415156143275033302 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #ifndef PEONYINTELLIGENTSEARCHVIEW_GLOBAL_H #define PEONYINTELLIGENTSEARCHVIEW_GLOBAL_H #include #if defined(PEONYINTELLIGENTSEARCHVIEW_LIBRARY) # define PEONYINTELLIGENTSEARCHVIEW_EXPORT Q_DECL_EXPORT #else # define PEONYINTELLIGENTSEARCHVIEW_EXPORT Q_DECL_IMPORT #endif #endif // PEONYINTELLIGENTSEARCHVIEW_GLOBAL_H peony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/search-view.h0000664000175000017500000001066215156143275030256 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #ifndef SEARCHVIEW_H #define SEARCHVIEW_H #include #include "directory-view-plugin-iface.h" #include #include #include using namespace Peony; class SearchView : public QTreeView,public DirectoryViewIface, public DirectoryViewIface2 { Q_OBJECT public: explicit SearchView(QWidget *parent = nullptr); ~SearchView(); QModelIndexList getSelectedIndexes(); void invertSelections() override; void bindModel(FileItemModel *sourceModel, FileItemProxyFilterSortModel *proxyModel) override; void setProxy(DirectoryViewProxyIface *proxy) override; const QString getDirectoryUri() override; const QStringList getSelections() override; const QStringList getAllFileUris() override; void open(const QStringList &uris, bool newWindow) override; void setDirectoryUri(const QString &uri) override; void beginLocationChange() override; void stopLocationChange() override; void closeView() override; void setSelections(const QStringList &uris) override; void scrollToSelection(const QString &uri) override; void setCutFiles(const QStringList &uris) override; DirectoryViewProxyIface *getProxy() override; int getSortType() override; void setSortType(int sortType) override; int getSortOrder() override; void setSortOrder(int sortOrder) override; void editUri(const QString &uri) override; void editUris(const QStringList uris) override; void startDrag(Qt::DropActions flags) override; // void bindModel(FileItemModel *sourceModel, FileItemProxyFilterSortModel *proxyModel) override; // void setProxy(DirectoryViewProxyIface *proxy) override ; void reUpdateScrollBar(); const QString viewId() override { return "Search View"; } void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override; // //location // const QString getDirectoryUri() override; // //selections // const QStringList getSelections() override; // //children // const QStringList getAllFileUris() override; // void setDirectoryUri(const QString &uri) override; // void beginLocationChange() override; // void stopLocationChange() override; // void closeView() override; // //selections // void setSelections(const QStringList &uris) override; // // virtual void invertSelections() = 0; // void scrollToSelection(const QString &uri) override; // //clipboard // void setCutFiles(const QStringList &uris) override; // DirectoryViewProxyIface *getProxy() override; // int getSortType() override; // void setSortType(int sortType) override; // int getSortOrder() override; // void setSortOrder(int sortOrder) override; // //edit // void editUri(const QString &uri) override; /*! * \brief editUris * \param uris * \todo * implement batch rename */ void doMultiSelect(bool) override; bool isEnableMultiSelect() override; void setItemsVisible(bool visible) override; const int getAllDisplayFileCount() override; void slotRename(); void queryFileinfo(QString uri); bool m_delegate_editing = false; public Q_SLOTS: void getSelectedIndex(const QItemSelection &selected, const QItemSelection &deselected); protected: void mousePressEvent(QMouseEvent *e) override; void updateGeometries() override; private: QModelIndexList m_selectedIndexes; QList> m_selectionInfos; QTimer* m_renameTimer; bool m_editValid; QModelIndex m_last_index; Q_SIGNALS: void menuRequest(const QPoint &pos); }; #endif // SEARCHVIEW_H peony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/search-view-container.h0000664000175000017500000000716715156143275032244 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #ifndef SEARCHVIEWCONTAINER_H #define SEARCHVIEWCONTAINER_H #include #include #include #include "search-view.h" #include"searchresultmodel.h" #include #include #include enum extractType{ keyWord=0, spaceName, searchPath }; namespace Peony { class SearchViewContainer : public DirectoryViewWidget { public: explicit SearchViewContainer(QWidget *parent = nullptr,QString uri=""); ~SearchViewContainer(); const virtual QString viewId() { return "Search View"; } const virtual QString getDirectoryUri(); //selections const virtual QStringList getSelections(); //children const virtual QStringList getAllFileUris(); virtual int getSortType(); virtual Qt::SortOrder getSortOrder(); //zoom virtual int currentZoomLevel() { return -1; } virtual int minimumZoomLevel() { return -1; } virtual int maximumZoomLevel() { return -1; } virtual bool supportZoom() { return false; } QString extractWithRegex(extractType type); void applySorting(); void performSearch(); public Q_SLOTS: virtual void bindModel(FileItemModel *model, FileItemProxyFilterSortModel *proxyModel); //location //virtual void open(const QStringList &uris, bool newWindow) {} virtual void setDirectoryUri(const QString &uri); virtual void beginLocationChange(); virtual void stopLocationChange(); virtual void closeDirectoryView() {} //selections virtual void setSelections(const QStringList &uris) {} virtual void invertSelections(); virtual void scrollToSelection(const QString &uri) {} //clipboard //cut items should be drawn differently. virtual void setCutFiles(const QStringList &uris) {} virtual void setSortType(int sortType); virtual void setSortOrder(int sortOrder); virtual void editUri(const QString &uri); virtual void editUris(const QStringList uris); virtual void repaintView() {} virtual void clearIndexWidget() {} //zoom virtual void setCurrentZoomLevel(int zoomLevel) {} void onItemDoubleClicked(const QModelIndex &index); void requestShowAISubsystemDialog(); private: QString m_currentUri; SearchView *m_view = nullptr; SearchFileItemModel *m_model; SearchFilterProxyModel* m_proxyModel; QDBusInterface* m_signalTransInterface; bool isAiConditionAvialable; bool m_isFileContentOn; TreeviewDelegate* m_delegate ; QString m_clientId; AIConditionType aiConditionType; int m_sortType; int m_sortOrder; QString m_defaultUri; std::shared_ptr m_watcher; QTimer* m_searchDelayTimer = nullptr; QString m_keyword; }; } #endif // SEARCHVIEWCONTAINER_H peony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/thumbnail/0000775000175000017500000000000015156143275027646 5ustar fengfeng././@LongLink0000644000000000000000000000015500000000000011604 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/thumbnail/searchthumbnailmanager.cpppeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/thumbnail/searchthumbnailman0000664000175000017500000001414215156143275033440 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "thumbnail/searchthumbnailmanager.h" #include #include #include #include #include #include #include #include #include #include #include class ThumbnailManagerPrivate { public: ThumbnailManagerPrivate(); ~ThumbnailManagerPrivate(); void startThumbnail(const QString &path); QThread *thread; QMutex mutex; QString pathWaitForThumbnail; QString currentPathForThumbnailing; UkuiFileMetadata::ExtractorManager *manager; QTimeLine *timeLine; bool strictedSignalMode = false; }; class ThumbnailJob : public QObject { friend class searchThumbnailManager; Q_OBJECT public: ThumbnailJob(const QString &path, UkuiFileMetadata::ExtractorManager *manager); ~ThumbnailJob(); Q_SIGNALS: void thumnailFinished(); public Q_SLOTS: void startThumbnail(); private: QString path; QImage image; UkuiFileMetadata::ExtractorManager *manager; }; searchThumbnailManager::searchThumbnailManager(QObject *parent) : QObject(parent) { priv = new ThumbnailManagerPrivate; connect(priv->timeLine, &QTimeLine::finished, this, [=]{ if (priv->currentPathForThumbnailing.isEmpty() && !priv->pathWaitForThumbnail.isEmpty()) { priv->currentPathForThumbnailing = priv->pathWaitForThumbnail; priv->pathWaitForThumbnail.clear(); if (!priv->thread->isRunning()) { auto job = new ThumbnailJob(priv->currentPathForThumbnailing, priv->manager); job->moveToThread(priv->thread); connect(priv->thread, &QThread::started, job, &ThumbnailJob::startThumbnail); connect(job, &ThumbnailJob::thumnailFinished, priv->thread, &QThread::quit); connect(priv->thread, &QThread::finished, this, [=]{ QImage image = job->image; if (!image.isNull()) { if (priv->strictedSignalMode) { if (priv->pathWaitForThumbnail.isEmpty()) { Q_EMIT this->thumbnailFinished(job->path, image, nullptr); } else { //qDebug() << "stricted mode, not send" << job->path << job->image; } } else { Q_EMIT this->thumbnailFinished(job->path, image, nullptr); } } else { Q_EMIT this->thumbnailFinished(job->path, QImage(), tr("Failed to create image for %1").arg(job->path)); } priv->currentPathForThumbnailing.clear(); delete job; priv->thread; priv->thread = new QThread; if (priv->timeLine->state() == QTimeLine::NotRunning && !priv->pathWaitForThumbnail.isEmpty()) priv->timeLine->start(); }); priv->thread->start(); } else { qDebug() << "a thumbnail job is running, waiting previous job finished" << priv->currentPathForThumbnailing; } } else { qDebug() << "a thumbnail job is running, waiting previous job finished" << priv->currentPathForThumbnailing; } }); } searchThumbnailManager::~searchThumbnailManager() { delete priv; } void searchThumbnailManager::startPreview(const QString &path) { priv->startThumbnail(path); } void searchThumbnailManager::setStrictedMode(bool strictedMode) { priv->strictedSignalMode = strictedMode; } ThumbnailManagerPrivate::ThumbnailManagerPrivate() { thread = new QThread; manager = new UkuiFileMetadata::ExtractorManager; timeLine = new QTimeLine; timeLine->setDuration(100); timeLine->setUpdateInterval(100); } ThumbnailManagerPrivate::~ThumbnailManagerPrivate() { timeLine->stop(); thread->quit(); delete thread; delete manager; delete timeLine; } void ThumbnailManagerPrivate::startThumbnail(const QString &path) { if (path.isEmpty()) return; QMutexLocker l(&mutex); if (path == currentPathForThumbnailing) return; pathWaitForThumbnail = path; if (timeLine->state() == QTimeLine::NotRunning) timeLine->start(); } ThumbnailJob::ThumbnailJob(const QString &path, UkuiFileMetadata::ExtractorManager *manager) : path(path), manager(manager) { } ThumbnailJob::~ThumbnailJob() { image.detach(); QImage().swap(image); } void ThumbnailJob::startThumbnail() { QString mimeType = UkuiFileMetadata::MimeUtils::strictMimeType(path, {}).name(); QList extractors = manager->fetchExtractors(mimeType); UkuiFileMetadata::SimpleExtractionResult result(path, mimeType, UkuiFileMetadata::ExtractionResult::Flag::ExtractThumbnail); result.setThumbnailRequest(UkuiFileMetadata::ThumbnailRequest(QSize(128, 128), 1)); for(auto extractor : extractors) { extractor->extract(&result); if(result.thumbnail().isValid()) { image = result.thumbnail().image(); break; } } Q_EMIT this->thumnailFinished(); } #include "searchthumbnailmanager.moc" ././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/thumbnail/searchthumbnailmanager.hpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/thumbnail/searchthumbnailman0000664000175000017500000000301715156143275033437 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef SEARCHTHUMBNAILMANAGER_H #define SEARCHTHUMBNAILMANAGER_H #include class ThumbnailManagerPrivate; class searchThumbnailManager : public QObject { Q_OBJECT public: explicit searchThumbnailManager(QObject *parent = nullptr); ~searchThumbnailManager(); Q_SIGNALS: void thumbnailFinished(const QString &path, const QImage &image, const QString &errorMessage); public Q_SLOTS: Q_INVOKABLE void startPreview(const QString &path); /*! * \brief setStrictedMode * \param strictedMode * \note * send thumbnailFinished() signal only the last thumbnail job done. */ Q_INVOKABLE void setStrictedMode(bool strictedMode); private: ThumbnailManagerPrivate *priv; }; #endif // SEARCHTHUMBNAILMANAGER_H ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_kk.tspeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000001126515156143275033305 0ustar fengfeng QObject Intelligent Search View وي قابٸلەتتٸ ٸزدەۋ كورىنۋى Use customized Intelligent Data Manager views when searching in File Manager حۇجات باسقارۋشىدان ىزدەگەندە ٶزى بەلگٸلەنگەن وي قابٸلەتتٸ ساندىق مالىمەتتەردى باسقارۋ كورىنۋدى ٸستەتۋ كەرەك SearchFileItemModel File Name Match اتٸ سايكەستىرۋ Intelligent Content Recognition Match وي قابٸلەتتٸ مازمۇن پارىقتاندىرۋ سايكەستىك Text Content Name مى Modified Date وزگەرتىلگەن ۋاقىتى File Type حۇجات تۇرى File Size حۇجات ۇلكەندىگى To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:#3790FA;'>Open "AI Index"</span> To Obtain the Matching Results of Text, Pictures, Videos. Please<span style='color:#3790FA;'> Open "File Content Index"</span> To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:blue;'>Open "AI Index"</span> To Obtain Natural Language Recognition Results, Please <span style='color:blue;'>Enable "AI Index"</span> in【Settings】->【Global Search】 تابيعي ٴتٸل پارىقتاندىرۋ ناتيجەسىنە قول جەتكىزۋدە ، " > 【 جالپىلىقتى ٸزدەۋ ] < span style=color: blue > "AI كورسەتكىش" ٸشٸۋ</span> كەرەك Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 You can manually <span style='color:#3790FA;'>add files</span> or <span style='color:#3790FA;'>modify the space conditions.</span> 您可以手动 <span style='color:#3790FA;'>添加文件</span> ,或 <span style='color:#3790FA;'>修改空间条件</span> searchThumbnailManager Failed to create image for %1 ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_ug.tspeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000001136415156143275033305 0ustar fengfeng QObject Intelligent Search View ئەقلىي ئىقتىدارلىق ئىزدەش كۆرۈنۈشى Use customized Intelligent Data Manager views when searching in File Manager ھۆججەت باشقۇرغۇچتىن ئىزدىگەندە ئۆزى بەلگىلەنگەن ئەقلىي ئىقتىدارلىق سانلىق مەلۇماتلارنى باشقۇرۇش كۆرۈنۈشىنى ئىشلىتىش كېرەك SearchFileItemModel File Name Match ئىسمى ماسلاشتۇرۇش Intelligent Content Recognition Match ئەقلىي ئىقتىدارلىق مەزمۇن پەرقلەندۈرۈش ماسلاشتۇر Text Content Name نامى Modified Date ئۆزگەرتىلگەن ۋاقتى File Type ھۆججەت تىپى File Size ھۆججەت چوڭلۇقى To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:#3790FA;'>Open "AI Index"</span> To Obtain the Matching Results of Text, Pictures, Videos. Please<span style='color:#3790FA;'> Open "File Content Index"</span> To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:blue;'>Open "AI Index"</span> To Obtain Natural Language Recognition Results, Please <span style='color:blue;'>Enable "AI Index"</span> in【Settings】->【Global Search】 تەبىئىي تىل پەرقلەندۈرۈش نەتىجىسىگە ئېرىشىشتە ، " > 【 ئومۇمىيلىقنى ئىزدەش ] < span style=color: blue > "AI ئىندېكس" ئېچىش</span> كېرەك Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 You can manually <span style='color:#3790FA;'>add files</span> or <span style='color:#3790FA;'>modify the space conditions.</span> 您可以手动 <span style='color:#3790FA;'>添加文件</span> ,或 <span style='color:#3790FA;'>修改空间条件</span> searchThumbnailManager Failed to create image for %1 ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view.propeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000000521015156143275033276 0ustar fengfengQT += widgets dbus TARGET = peony-intelligent-search-view TEMPLATE = lib DEFINES += PEONYINTELLIGENTSEARCHVIEW_LIBRARY include(../../common.pri) CONFIG += debug c++11 no_keywords link_pkgconfig plugin embed_translations PKGCONFIG +=gio-2.0 glib-2.0 gio-unix-2.0 peony libnotify ukui-file-metadata # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #TRANSLATIONS = peony-extension-computer-view_zh_CN.ts \ # peony-extension-computer-view_de.ts \ # peony-extension-computer-view_es.ts \ # peony-extension-computer-view_fr.ts \ # peony-extension-computer-view_kk_KZ.ts \ # peony-extension-computer-view_ug_CN.ts \ # peony-extension-computer-view_ky_KG.ts \ # peony-extension-computer-view_cs.ts \ # peony-extension-computer-view_tr.ts \ # peony-extension-computer-view_bo_CN.ts \ # peony-extension-computer-view_mn.ts \ # peony-extension-computer-view_zh_HK.ts #include(computer-view/computer-view.pri) TRANSLATIONS += peony-intelligent-search-view_zh_CN.ts\ peony-intelligent-search-view_zh_HK.ts\ peony-intelligent-search-view_mn.ts\ peony-intelligent-search-view_ug.ts\ peony-intelligent-search-view_kk.ts\ peony-intelligent-search-view_ky.ts\ peony-intelligent-search-view_bo_CN.ts CONFIG += link_pkgconfig PKGCONFIG += gio-unix-2.0 SOURCES += \ peony-intelligent-search-view-plugin.cpp \ search-view-container.cpp \ search-view.cpp\ searchresultmodel.cpp\ thumbnail/searchthumbnailmanager.cpp HEADERS += \ peony-intelligent-search-view-plugin.h \ peony-intelligent-search-view_global.h \ search-view-container.h \ search-view.h\ searchresultmodel.h \ thumbnail/searchthumbnailmanager.h #DESTDIR += ../testdir # Default rules for deployment. unix { target.path = $$[QT_INSTALL_LIBS]/peony-extensions } !isEmpty(target.path): INSTALLS += target CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = / ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_bo_CN.tspeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000001213015156143275033275 0ustar fengfeng QObject Intelligent Search View རིག་ནུས་འཚོལ་ཞིབ་ཀྱི་རི་མོ། Use customized Intelligent Data Manager views when searching in File Manager ཡིག་ཆའི་དོ་དམ་ཡོ་བྱད་ཁྲོད་འཚོལ་ཞིབ་བྱེད་སྐབས་རང་ཉིད་ཀྱི་མཚན་ཉིད་བཞག་པའི་རིག་ནུས་གྲངས་གཞིའི་དོ་དམ་རི་མོ་བཀོལ་སྤྱོད་བྱེད་དགོས། SearchFileItemModel File Name Match མིང་ཆ་མཐུན་པ། Intelligent Content Recognition Match རིག་ནུས་ཀྱི་ནང་དོན་ཆ་འགྲིག་དབྱེ་འབྱེད་བྱེད་པ། Text Content Name མིང་། Modified Date དུས་འགྱུར་བ། File Type ཡིག་ཆའི་རིགས་གྲས། File Size ཡིག་ཆའི་ཆེ་ཆུང་། To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:#3790FA;'>Open "AI Index"</span> To Obtain the Matching Results of Text, Pictures, Videos. Please<span style='color:#3790FA;'> Open "File Content Index"</span> To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:blue;'>Open "AI Index"</span> To Obtain Natural Language Recognition Results, Please <span style='color:blue;'>Enable "AI Index"</span> in【Settings】->【Global Search】 རང་བྱུང་སྐད་ཆ་ངོས་འཛིན་གྱི་མཇུག་འབྲས་ཐོབ་པར་བྱེད་དགོས >་པ་སྟེ། (>་)ཁྱོན་ཡོངས་ནས་འཚོལ་ཞིབ་བྱེད་དགོས་པ་དང་། <</span>་ Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 You can manually <span style='color:#3790FA;'>add files</span> or <span style='color:#3790FA;'>modify the space conditions.</span> 您可以手动 <span style='color:#3790FA;'>添加文件</span> ,或 <span style='color:#3790FA;'>修改空间条件</span> searchThumbnailManager Failed to create image for %1 ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view-plugin.hpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000000456015156143275033305 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #ifndef PEONYINTELLIGENTSEARCHVIEWPLUGIN_H #define PEONYINTELLIGENTSEARCHVIEWPLUGIN_H #include "peony-intelligent-search-view_global.h" #include #include namespace Peony { class PEONYINTELLIGENTSEARCHVIEW_EXPORT PeonyIntelligentSearchViewPlugin : public QObject, public DirectoryViewPluginIface2 { Q_OBJECT public: Q_PLUGIN_METADATA(IID DirectoryViewPluginIface2_iid FILE "common.json") Q_INTERFACES(Peony::DirectoryViewPluginIface2) public: explicit PeonyIntelligentSearchViewPlugin(QObject *parent = nullptr); PluginType pluginType() {return PluginType::DirectoryViewPlugin2;} const QString name() {return QObject::tr("Intelligent Search View");} const QString description() {return QObject::tr("Use customized Intelligent Data Manager views when searching in File Manager");} const QIcon icon() {return QIcon::fromTheme("kylin-data-manager");} void setEnable(bool enable) {} bool isEnable() {return true;} //view QString viewIdentity() { return "Search View"; } QString viewName() {return name();} QIcon viewIcon() {return icon();} bool supportUri(const QString &uri) {return uri.startsWith("search:///");} int zoom_level_hint() {return -1;} int minimumSupportedZoomLevel() {return -1;} int maximumSupportedZoomLevel() {return -1;} int priority(const QString &directoryUri); bool supportZoom() {return false;} DirectoryViewWidget *create(); Q_SIGNALS: private: QString m_currentUri; QString m_spacename; }; } #endif // PEONYINTELLIGENTSEARCHVIEWPLUGIN_H peony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/search-view.cpp0000664000175000017500000004317615156143275030617 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #include "search-view.h" #include "file-utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LISTVIEW_ITEM_BORDER_RADIUS 6 SearchView::SearchView(QWidget *parent): QTreeView(parent) { m_renameTimer = new QTimer(this); m_renameTimer->setInterval(3000); m_renameTimer->setSingleShot(true); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->setObjectName("_searchview_layout"); setProperty("highlightMode", true); //setFrameShape(QFrame::NoFrame); setVerticalScrollMode(ScrollPerPixel); setAutoScroll(true); setAutoScrollMargin(100); auto cornerWidget = new QWidget; cornerWidget->setObjectName("_listview_corner"); cornerWidget->setAttribute(Qt::WA_AlwaysStackOnTop); cornerWidget->setBackgroundRole(QPalette::Base); cornerWidget->setAutoFillBackground(true); setCornerWidget(cornerWidget); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); setSelectionBehavior(QTreeView::SelectRows); setAlternatingRowColors(true); setAutoFillBackground(true); setBackgroundRole(QPalette::Base); header()->setSectionResizeMode(QHeaderView::Interactive); header()->setSectionsMovable(true); header()->setStretchLastSection(false); header()->setMinimumSectionSize(130); header()->setTextElideMode(Qt::ElideRight); connect(header(), &QHeaderView::sectionResized, this, [=]{ }); setExpandsOnDoubleClick(false); setSortingEnabled(true); setEditTriggers(QTreeView::NoEditTriggers); setDragEnabled(true); setDragDropMode(QTreeView::DragDrop); setSelectionMode(QTreeView::ExtendedSelection); setUniformRowHeights(true); setIconSize(QSize(40, 40)); setMouseTracking(true);//追踪鼠标 setViewportMargins(0, 0, 0, 0); } SearchView::~SearchView() { m_selectedIndexes.clear(); m_selectionInfos.clear(); } QModelIndexList SearchView::getSelectedIndexes() { return this->selectionModel()->selectedRows(); } void SearchView::startDrag(Qt::DropActions flags) { auto indexes = selectedIndexes(); if (indexes.count() > 0) { auto pos = mapFromGlobal(QCursor::pos()); qreal scale = 1.0; QWidget *window = this->window(); if (window) { auto windowHandle = window->windowHandle(); if (windowHandle) { scale = windowHandle->devicePixelRatio(); } } auto drag = new QDrag(this); QMimeData* data = model()->mimeData(indexes); QVariant isSearchData = QVariant(true); data->setData("peony-qt/is-search", isSearchData.toByteArray()); //临时添加属性解决无法拖拽侧边栏问题,后续model层完善后,触发flags可去除下面代码 QByteArray encodeData; QDataStream stream(&encodeData, QIODevice::WriteOnly); data->setData("application/x-qabstractitemmodeldatalist", encodeData); drag->setMimeData(data); int num = indexes.count() / 4; if (num > 50) { QRect pixmapRect = QRect(100, 100, 400, 400); QPixmap pixmap(pixmapRect.size() * scale); pixmap.fill(Qt::transparent); pixmap.setDevicePixelRatio(scale); QPainter painter(&pixmap); quint64 count = 0; painter.save(); QRect iconRect = pixmapRect; iconRect.setSize(QSize(139, 139)); for (auto index : indexes) { if (count > 50) { break; } count++; iconRect.moveTo(iconRect.x()+1, iconRect.y()+1); QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); if (!icon.isNull()) { painter.save(); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.drawPixmap(QPoint(100 + count, 100 + count), icon.pixmap(139, 139)); painter.restore(); } } QFont font = qApp->font(); font.setPointSize(10); QFontMetrics metrics(font); QString text = num > 999 ? "..." : QString::number(num); int height = metrics.horizontalAdvance(text); int width = metrics.height(); int diameter = std::max(height, width); int radius = diameter / 2; QRectF textRect = QRectF(iconRect.topRight().x() - diameter - 10, iconRect.topRight().y(), diameter + 10, diameter + 10); painter.setBrush(Qt::red); painter.setPen(Qt::red); painter.drawEllipse(textRect); painter.setPen(Qt::white); painter.drawText(textRect, Qt::AlignCenter, text); painter.restore(); drag->setPixmap(pixmap); drag->setHotSpot(QPoint(200,200)); drag->setDragCursor(QPixmap(), Qt::MoveAction); drag->exec(Qt::MoveAction); } else { QRegion rect; QHash indexRectHash; for (auto index : indexes) { rect += (visualRect(index)); indexRectHash.insert(index, visualRect(index)); } QRect realRect = rect.boundingRect(); QPixmap pixmap(realRect.size() * scale); pixmap.fill(Qt::transparent); pixmap.setDevicePixelRatio(scale); QPainter painter(&pixmap); quint64 count = 0; for (auto index : indexes) { painter.save(); painter.translate(indexRectHash.value(index).topLeft() - rect.boundingRect().topLeft()); //painter.translate(-rect.boundingRect().topLeft()); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); QStyleOptionViewItem opt; initViewItemOption(&opt); auto viewItemDelegate = static_cast(itemDelegate()); viewItemDelegate->initIndexOption(&opt, index); opt.displayAlignment = Qt::Alignment(Qt::AlignLeft|Qt::AlignVCenter); opt.rect.setSize(indexRectHash.value(index).size()); opt.rect.moveTo(0, 0); opt.state |= QStyle::State_Selected; painter.setOpacity(0.8); count++; if(count == 1){ QPainterPath leftRoundedRegion; leftRoundedRegion.setFillRule(Qt::WindingFill); leftRoundedRegion.addRoundedRect(opt.rect, LISTVIEW_ITEM_BORDER_RADIUS, LISTVIEW_ITEM_BORDER_RADIUS); leftRoundedRegion.addRect(opt.rect.adjusted(LISTVIEW_ITEM_BORDER_RADIUS, 0, 0, 0)); painter.setClipPath(leftRoundedRegion); }else if(count == 4){ QPainterPath rightRoundedRegion; rightRoundedRegion.setFillRule(Qt::WindingFill); rightRoundedRegion.addRoundedRect(opt.rect, LISTVIEW_ITEM_BORDER_RADIUS, LISTVIEW_ITEM_BORDER_RADIUS); rightRoundedRegion.addRect(opt.rect.adjusted(0, 0, -LISTVIEW_ITEM_BORDER_RADIUS, 0)); painter.setClipPath(rightRoundedRegion); count = 0; } QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, &painter, this); painter.restore(); } drag->setPixmap(pixmap); drag->setHotSpot(pos - rect.boundingRect().topLeft() - QPoint(0, header()->height())); drag->setDragCursor(QPixmap(), Qt::MoveAction); drag->exec(Qt::MoveAction); } } } void SearchView::invertSelections() { auto selectionModel = this->selectionModel(); if (!selectionModel) return; // 获取当前选中行 auto selectedRows = selectionModel->selectedRows(); // 创建新选中区域 QItemSelection newSelection; // 遍历所有行进行反选 for (int row = 0; row < model()->rowCount(); ++row) { QModelIndex catIndex = model()->index(row, 0, QModelIndex()); for (int j = 0; j < model()->rowCount(catIndex); ++j) { QModelIndex childIndex = model()->index(j, 0, catIndex); if (!selectedRows.contains(childIndex)) { int columnCount = model()->columnCount(childIndex.parent()); QModelIndex lastColumnIndex = model()->index(childIndex.row(), columnCount - 1, childIndex.parent()); newSelection.select(childIndex, lastColumnIndex); } } } // 应用反选 selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect); } void SearchView::bindModel(FileItemModel *sourceModel, FileItemProxyFilterSortModel *proxyModel) { return; } void SearchView::setProxy(DirectoryViewProxyIface *proxy) { return; } const QString SearchView::getDirectoryUri() { return ""; } const QStringList SearchView::getSelections() { return QStringList(); } const QStringList SearchView::getAllFileUris() { return QStringList(); } void SearchView::open(const QStringList &uris, bool newWindow) { return; } void SearchView::setDirectoryUri(const QString &uri) { return; } void SearchView::beginLocationChange() { return; } void SearchView::stopLocationChange() { return; } void SearchView::closeView() { return; } void SearchView::setSelections(const QStringList &uris) { return; } void SearchView::scrollToSelection(const QString &uri) { return; } void SearchView::setCutFiles(const QStringList &uris) { return; } DirectoryViewProxyIface *SearchView::getProxy() { return nullptr; } int SearchView::getSortType() { return 0; } void SearchView::setSortType(int sortType) { return; } int SearchView::getSortOrder() { return 0; } void SearchView::setSortOrder(int sortOrder) { return; } void SearchView::editUri(const QString &uri) { setState(QTreeView::NoState); auto selected = selectionModel()->selectedRows(); if (selected.isEmpty()) { qWarning() << "No selection"; return; } auto proxmodel = qobject_cast(model()); auto searchmodel = qobject_cast(proxmodel->sourceModel()); QModelIndex index = selected.first(); auto id = proxmodel->mapToSource(index).internalId(); if(searchmodel->isContentItem(id) && searchmodel->hasDefaultContent()){ return; } auto fileUri = index.data(Qt::UserRole).toString(); queryFileinfo(fileUri); edit(index); } void SearchView::editUris(const QStringList uris) { this->editUri(uris.first()); } void SearchView::doMultiSelect(bool) { return; } bool SearchView::isEnableMultiSelect() { return true; } void SearchView::setItemsVisible(bool visible) { return; } const int SearchView::getAllDisplayFileCount() { auto proxyModel = qobject_cast(this->model()); int count = 0; if (!proxyModel) { return count; } QModelIndex root = QModelIndex(); for(int i = 0; i < proxyModel->rowCount(root); ++i) { QModelIndex catIndex = proxyModel->index(i, 0, root); if (catIndex.isValid()) { count += proxyModel->rowCount(catIndex); } } qDebug() << __func__ << __LINE__ << count; return count; } void SearchView::mousePressEvent(QMouseEvent *e) { m_editValid = true; QTreeView::mousePressEvent(e); m_selectedIndexes = this->selectionModel()->selectedRows(); if (e->button() == Qt::RightButton) { m_selectionInfos.clear(); for (auto index : m_selectedIndexes) { auto uri = index.data(Qt::UserRole).toString(); queryFileinfo(uri); } Q_EMIT menuRequest(viewport()->mapToGlobal(e->pos())); return; } if(!m_renameTimer->isActive()) { m_renameTimer->start(); m_editValid = false; } else { //if remain time is between[0.75, 3000],then trigger rename event; //to make sure only click one row bool all_index_in_same_row = true; if (!this->selectedIndexes().isEmpty()) { int first_index_row = this->selectedIndexes().first().row(); for (auto index : this->selectedIndexes()) { if (first_index_row != index.row()) { all_index_in_same_row = false; break; } } } //qDebug()<remainingTime()<styleHints()->mouseDoubleClickInterval(); //qDebug()<<"m_last_index:"<pos()):"<pos()); //优化文件点击策略,提升用户体验,关联bug#125368 //在双击时间间隔内,如果未触发双击事件,但是点击的是同一个有效图标,触发双击事件 //系统默认双击间隔为400ms, 策略为[0,400],触发双击,(400,3000)触发重命名 if(m_renameTimer->remainingTime()> 0 && m_renameTimer->remainingTime() < 3000 - qApp->styleHints()->mouseDoubleClickInterval() && indexAt(e->pos()) == m_last_index && m_last_index.isValid() && m_editValid == true && all_index_in_same_row) { slotRename(); } else { m_editValid = false; } } } void SearchView::updateGeometries() { setUpdatesEnabled(false); QTreeView::updateGeometries(); reUpdateScrollBar(); setUpdatesEnabled(true); } void SearchView::reUpdateScrollBar() { if (!model()) return; if (model()->rowCount() == 0) { return; } int rowCount = model()->rowCount(); auto index = model()->index(0, 0); int fileNameCount = model()->rowCount(model()->index(0, 0)); int fileContentCount = model()->rowCount(model()->index(1, 0)); rowCount += (fileNameCount + fileContentCount); int totalHeight = sizeHintForIndex(index).height()*rowCount; int currentScrollBarValue = verticalScrollBar()->value(); verticalScrollBar()->setSingleStep(iconSize().height()); verticalScrollBar()->setPageStep(viewport()->height() - header()->height()); verticalScrollBar()->setRange(0, totalHeight + header()->height()+100 - viewport()->height()); verticalScrollBar()->setValue(currentScrollBarValue); } void SearchView::scrollTo(const QModelIndex &index, ScrollHint hint) { Q_UNUSED(index) Q_UNUSED(hint) QTreeView::scrollTo(index,hint); reUpdateScrollBar(); } void SearchView::slotRename() { //standardPaths not allow rename auto currentSelections = getSelections(); bool hasStandardPath = FileUtils::containsStandardPath(currentSelections); if (hasStandardPath) return; //delay edit action to avoid doubleClick or dragEvent qDebug()<<"slotRename"<stop(); setIndexWidget(m_last_index, nullptr); edit(m_last_index); m_editValid = false; } }); } void SearchView::queryFileinfo(QString uri) { m_selectionInfos.clear(); auto info = Peony::FileInfo::fromUri(uri); if (info->isEmptyInfo()) { Peony::FileInfoJob j(info); j.querySync(); } m_selectionInfos.append(info); } void SearchView::getSelectedIndex(const QItemSelection &selected, const QItemSelection &deselected) { //qDebug()<<"list view selection changed"<setIndexWidget(index, nullptr); } if (!currentSelections.isEmpty()) { int first_index_row = currentSelections.first().row(); bool all_index_in_same_row = true; for (auto index : currentSelections) { if (first_index_row != index.row()) { all_index_in_same_row = false; break; } } if (all_index_in_same_row) { if(m_last_index.row() != currentSelections.first().row()) { m_editValid = false; } m_last_index = currentSelections.first(); } } else { m_last_index = QModelIndex(); m_editValid = false; } } ././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view-plugin.cpppeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000000306515156143275033304 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #include "peony-intelligent-search-view-plugin.h" #include "search-view-container.h" #include #include using namespace Peony; PeonyIntelligentSearchViewPlugin::PeonyIntelligentSearchViewPlugin(QObject *parent) { //加载翻译 QTranslator *t = new QTranslator(this); t->load(":/peony-intelligent-search-view_"+QLocale::system().name()); QApplication::installTranslator(t); } int PeonyIntelligentSearchViewPlugin::priority(const QString &directoryUri) { qDebug() << "=======" << __func__ << directoryUri; m_currentUri = directoryUri; if (directoryUri.startsWith("search:///")) { return 1; } return -1; } DirectoryViewWidget *PeonyIntelligentSearchViewPlugin::create() { return new SearchViewContainer(nullptr,m_currentUri); } ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_zh_HK.tspeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000001050315156143275033277 0ustar fengfeng QObject Intelligent Search View 智慧搜索檢視 Use customized Intelligent Data Manager views when searching in File Manager 在檔案管理員中搜索時使用自定義的智能資料管理檢視 SearchFileItemModel File Name Match 名稱匹配 Intelligent Content Recognition Match 智慧內容識別匹配 Text Content Name 檔名稱 Modified Date 修改日期 File Type 檔案類型 File Size 檔大小 To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:#3790FA;'>Open "AI Index"</span> To Obtain the Matching Results of Text, Pictures, Videos. Please<span style='color:#3790FA;'> Open "File Content Index"</span> To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:blue;'>Open "AI Index"</span> To Obtain Natural Language Recognition Results, Please <span style='color:blue;'>Enable "AI Index"</span> in【Settings】->【Global Search】 獲取自然語言識別結果,需在【設置】->【全域搜索】 <span style='color:blue;'>打開“AI 索引”</span> Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 You can manually <span style='color:#3790FA;'>add files</span> or <span style='color:#3790FA;'>modify the space conditions.</span> 您可以手动 <span style='color:#3790FA;'>添加文件</span> ,或 <span style='color:#3790FA;'>修改空间条件</span> searchThumbnailManager Failed to create image for %1 peony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/search-view-container.cpp0000664000175000017500000003350115156143275032566 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #include "search-view-container.h" #include "file-item-model.h" #include "file-item-proxy-filter-sort-model.h" #include #include #include #include #include"QHeaderView" #include #include #include #include #include #include #include "search-vfs-uri-parser.h" using namespace Peony; SearchViewContainer::SearchViewContainer(QWidget *parent,QString uri):m_defaultUri(uri) { auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->setObjectName("_searchview_layout"); setBackgroundRole(QPalette::Base); setAutoFillBackground(true); m_view = new SearchView(this); DirectoryViewHelper * viewHelper = DirectoryViewHelper::globalInstance(); viewHelper->addListViewWithDirectoryViewWidget(m_view, this); //m_view->setModel(m_model); m_clientId = QUuid::createUuid().toString();//分配客户端ID layout->addWidget(m_view); layout->addSpacing(6); setLayout(layout); m_signalTransInterface = new QDBusInterface("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller"); QDBusReply reply = m_signalTransInterface->call("getAinotAvailabletype"); aiConditionType = AIConditionType(reply.value()); isAiConditionAvialable = !(aiConditionType == aiIndexNotFit); connect(m_view, &QTreeView::doubleClicked, this, &SearchViewContainer::onItemDoubleClicked); connect(m_view, &QTreeView::clicked, this, [this](const QModelIndex &index){ QModelIndex realindex = m_proxyModel->mapToSource(index); quintptr id = realindex.internalId(); if(!isAiConditionAvialable && m_model->isContentItem(id) && m_model->hasDefaultContent()){ requestShowAISubsystemDialog(); } }); connect(m_view, &SearchView::menuRequest, this,[this](const QPoint &pos){ Q_EMIT menuRequest(pos); }); m_searchDelayTimer = new QTimer(this); m_searchDelayTimer->setSingleShot(true); m_searchDelayTimer->setInterval(500); // 设置为500毫秒延迟 connect(m_searchDelayTimer, &QTimer::timeout, this, &SearchViewContainer::performSearch); } SearchViewContainer::~SearchViewContainer() { QStandardItemModel* model = qobject_cast(m_view->model()); if(model) { model->clear(); } delete m_view; if (m_model) { m_model->clearAll(); } delete m_model; m_signalTransInterface->call("cancelSearch"); } const QString SearchViewContainer::getDirectoryUri() { return m_currentUri; } const QStringList SearchViewContainer::getSelections() { QModelIndexList selectedIndexs = m_view->getSelectedIndexes(); QStringList selections; for (auto index:selectedIndexs) { if(!index.parent().isValid()) continue; QModelIndex realindex = m_proxyModel->mapToSource(index); QString uri = m_model->data(realindex,Qt::UserRole).toString(); selections<("_searchview_layout"); bool ok = false; if (parentWidget()) { int statusBarHeight = parentWidget()->property("statusBarHeight").toInt(&ok); if (ok) { layout->setContentsMargins(0, 0, 0, statusBarHeight); } } disconnect(m_model); disconnect(m_proxyModel); SearchFileItemModel* searchmodel = new SearchFileItemModel(); m_proxyModel = new SearchFilterProxyModel(m_view); m_proxyModel->setSourceModel(searchmodel); m_view->setModel(m_proxyModel); m_model = searchmodel; m_model->setClientId(m_clientId); // 配置视图 m_view->setAnimated(true); m_view->setSortingEnabled(true); m_view->expandAll(); m_view->setColumnWidth(0, 400); //m_view->setIndentation(40); m_view->resizeColumnToContents(0); m_view->header()->setSectionResizeMode(0, QHeaderView::Interactive); // 第一列可交互调整 m_view->header()->setSectionResizeMode(1, QHeaderView::Interactive); // 第二列可交互调整 m_view->header()->setSectionResizeMode(2, QHeaderView::Interactive); // 第三列可交互调整 m_view->header()->setSectionResizeMode(3, QHeaderView::Stretch); m_view->setTextElideMode(Qt::ElideRight); //m_view->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // 或者让所有列按比例拉伸 m_view->header()->setStretchLastSection(false); m_view->setContextMenuPolicy(Qt::CustomContextMenu);//启用右键选中 m_delegate = new TreeviewDelegate(m_view); m_view->setItemDelegate(m_delegate); connect(m_delegate, &TreeviewDelegate::isEditing, this, [=](const bool &editing){ m_view->m_delegate_editing = editing; }); //添加索引跳转 如果条件不满足 m_model->setAiCondition(isAiConditionAvialable); QDBusConnection connection = QDBusConnection::sessionBus(); if (!connection.isConnected()) { qDebug() << "条件更新信号创建失败:" << connection.lastError().message(); } connection.connect("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller", "searchConditionChanged", m_proxyModel,SLOT(updateconditions(QString))); connect(searchmodel,&SearchFileItemModel::searchFinished,this,[=](){ m_view->reUpdateScrollBar(); Q_EMIT viewDirectoryChanged(); }); connect(searchmodel, &SearchFileItemModel::resizeColFitContent, [=](){ m_view->resizeColumnToContents(0); }); m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); auto selectionModel = m_view->selectionModel(); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &DirectoryViewWidget::viewSelectionChanged); connect(searchmodel, &SearchFileItemModel::expand, [=](){ int count = m_proxyModel->rowCount(); for (int var = 0; var < count; ++var) { auto index = m_proxyModel->index(var,0); if(m_model->hasChildren(m_proxyModel->mapToSource(index))){ m_view->expand(index); } } }); connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, m_view, [=](const QItemSelection &selected, const QItemSelection &deselected){ auto currentSelections = selected.indexes(); if (!currentSelections.isEmpty()) { auto tmpIndex = currentSelections.first(); auto index = m_proxyModel->mapToSource(tmpIndex); if (m_model->isFileCategory(index.internalId()) || m_model->isContentCategory(index.internalId())) { return; } } m_view->getSelectedIndex(selected, deselected); }); } void SearchViewContainer::setDirectoryUri(const QString &uri) { if (m_currentUri != uri) { m_currentUri = uri; m_proxyModel->setCurrenturi(m_currentUri); m_watcher = std::make_shared(m_currentUri, nullptr, true); m_watcher->setMonitorChildrenChange(true); connect(m_watcher.get(), &FileWatcher::fileDeleted, this, [=](QString uri) { m_model->childRemove(uri); }); connect(m_watcher.get(), &FileWatcher::fileRenamed, this, [=](const QString &oldUri, const QString &newUri) { m_model->childRename(oldUri, newUri); QString fileName = QUrl(newUri).fileName(); if(!fileName.contains(m_keyword)){ m_model->childRemove(newUri); } }); m_watcher->startMonitor(); } } void SearchViewContainer::beginLocationChange() { Q_EMIT viewDirectoryChanged(); if (m_searchDelayTimer->isActive()) { m_searchDelayTimer->stop(); } // 启动延迟定时器,500ms后执行搜索 m_searchDelayTimer->start(); m_signalTransInterface->call("cancelSearch"); m_model->updateSearchstatus(false); } void SearchViewContainer::stopLocationChange() { Q_EMIT viewDirectoryChanged(); } void SearchViewContainer::invertSelections() { m_view->invertSelections(); } void SearchViewContainer::setSortType(int sortType) { if (m_sortType == sortType) return; m_sortType = sortType; applySorting(); } void SearchViewContainer::setSortOrder(int sortOrder) { if (m_sortOrder == sortOrder) return; m_sortOrder = sortOrder; applySorting(); } void SearchViewContainer::editUri(const QString &uri) { m_view->editUri(uri); } void SearchViewContainer::editUris(const QStringList uris) { m_view->editUris(uris); } void SearchViewContainer::applySorting() { int sortColumn; switch (m_sortType) { case 0: // 按名称 sortColumn = 0; break; case 1: // 按日期 sortColumn = 1; break; case 2: // 按大小 sortColumn = 2; break; case 3: // 按类型 sortColumn = 3; break; default: sortColumn = 0; } Qt::SortOrder order = static_cast(m_sortOrder); if (m_proxyModel) { QString uri = (!m_defaultUri.isEmpty()&&m_currentUri.isEmpty())?m_defaultUri:m_currentUri; m_proxyModel->setCurrenturi(uri); m_proxyModel->checkSortSettings(); m_view->header()->setSortIndicator(sortColumn, order); // m_proxyModel->sort(m_sortType, order); // m_proxyModel->update(); } } QStringList const SearchViewContainer:: getAllFileUris(){ return m_proxyModel->getFiltereUrilist(); } int SearchViewContainer::getSortType() { return m_sortType; } Qt::SortOrder SearchViewContainer::getSortOrder() { Qt::SortOrder order = static_cast(m_sortOrder); return order; } void SearchViewContainer::onItemDoubleClicked(const QModelIndex &index){ if (!index.isValid()) return; // 判断是否为二级Item(有父节点且父节点没有父节点) QModelIndex realindex = m_proxyModel->mapToSource(index); quintptr id = realindex.internalId(); if(!isAiConditionAvialable && m_model->isContentItem(id) && m_model->hasDefaultContent()){ //requestShowAISubsystemDialog(); return; } else if (index.parent().isValid() && !index.parent().parent().isValid()) { QModelIndex realindex = m_proxyModel->mapToSource(index); QString uri = m_model->data(realindex,Qt::UserRole).toString(); if (m_view->getSelectedIndexes().count() == 1) { m_view->queryFileinfo(uri); Q_EMIT this->viewDoubleClicked(uri); } } } void SearchViewContainer::requestShowAISubsystemDialog() { if(aiConditionType==aiIndexNotFit) QProcess::startDetached("ukui-control-center", {"-m", "search"}); else QProcess::startDetached("ukui-control-center", {"-m", "aisubsystem"}); } QString SearchViewContainer::extractWithRegex(extractType type){ QString tmpUri = FileUtils::urlDecode(m_currentUri); QStringList list = tmpUri.split("&"); if (Peony::SearchVFSUriParser::checkForSpecialInStringLists(list)) { list = Peony::SearchVFSUriParser::handleAmpersandSplitStringList(&tmpUri, list); for (QString tmp:list) { if(type == keyWord && tmp.startsWith("name_regexp")){ return tmp.remove("name_regexp="); }else if(type == spaceName && tmp.startsWith("search_uris=idm:")){ return tmp.remove("search_uris=idm:///"); }else if(type == searchPath && tmp.startsWith("search_uris=file:")){ return tmp.remove("search_uris=file://"); } } } switch (type) { case keyWord:{ QRegularExpression regex("name_regexp=([^&]+)"); QRegularExpressionMatch match = regex.match(tmpUri); return match.hasMatch() ? match.captured(1) : QString(); break; } case spaceName:{ QRegularExpression regex(R"(search_uris=idm:///([^&]+))"); QRegularExpressionMatch match = regex.match(tmpUri); return match.hasMatch() ? match.captured(1) : QString(); break; } case searchPath:{ QRegularExpression regex(R"(search_uris=file://([^&]+))"); QRegularExpressionMatch match = regex.match(tmpUri); return match.hasMatch() ? match.captured(1) : QString(); break; } default: break; } } void SearchViewContainer::performSearch() { QString key = extractWithRegex(keyWord); QString Spacename = extractWithRegex(spaceName); QString path = extractWithRegex(searchPath); m_model->clearResult(); QString realpath = (path == QStandardPaths::writableLocation(QStandardPaths::HomeLocation)) ? "" : path; m_model->updateSearchstatus(false); m_signalTransInterface->call("setCurrentSearchPath", realpath); // 设置当前搜索路径 m_signalTransInterface->call("startsearch", key, Spacename, m_clientId); m_keyword = key; m_model->keywordchange(key); m_delegate->setHighlightKeyword(key); } ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_mn.tspeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000001260515156143275033304 0ustar fengfeng QObject Intelligent Search View ᠤᠶᠤᠨᠲᠤ ᠡᠷᠢᠯᠲᠡ ᠶᠢᠨ ᠵᠢᠷᠤᠭ ᠢ ᠤᠶᠤᠨᠲᠤ ᠡᠷᠢᠵᠦ ᠦᠵᠡᠨ᠎ᠡ ᠃ Use customized Intelligent Data Manager views when searching in File Manager ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ ᠶᠢᠨ ᠮᠠᠰᠢᠨ ᠳᠣᠲᠣᠷ᠎ᠠ ᠡᠷᠢᠬᠦ ᠦᠶ᠎ᠡ ᠳᠦ ᠥᠪᠡᠷ ᠢᠶᠡᠨ ᠲᠣᠮᠢᠶᠠᠯᠠᠭᠰᠠᠨ ᠤᠶᠤᠨᠲᠤ ᠲᠣᠭ᠎ᠠ ᠪᠠᠷᠢᠮᠲᠠ ᠶᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ ᠶᠢᠨ ᠬᠠᠷᠠᠭᠠᠨ ᠵᠢᠷᠤᠭ ᠢ ᠬᠡᠷᠡᠭᠯᠡᠨ᠎ᠡ ᠃ SearchFileItemModel File Name Match ᠨᠡᠷᠡᠶᠢᠳᠦᠯ ᠢ ᠲᠣᠬᠢᠷᠠᠨ᠎ᠠ ᠃ Intelligent Content Recognition Match ᠤᠶᠤᠨᠲᠤ ᠠᠭᠤᠯᠭ᠎ᠠ ᠶᠢ ᠢᠯᠭᠠᠬᠤ ᠳᠤ ᠲᠣᠬᠢᠷᠠᠯᠴᠠᠨ᠎ᠠ ᠃ Text Content Name ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠨᠡᠷᠡᠶᠢᠳᠦᠯ ᠃ Modified Date ᠵᠠᠰᠠᠭᠰᠠᠨ ᠡᠳᠦᠷ ᠬᠤᠭᠤᠴᠠᠭ᠎ᠠ File Type ᠹᠠᠢᠯ ᠤ᠋ᠨ ᠳᠦᠷᠦᠯ File Size ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠶᠡᠬᠡ ᠪᠠᠭ᠎ᠠ ᠃ To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:#3790FA;'>Open "AI Index"</span> To Obtain the Matching Results of Text, Pictures, Videos. Please<span style='color:#3790FA;'> Open "File Content Index"</span> To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:blue;'>Open "AI Index"</span> To Obtain Natural Language Recognition Results, Please <span style='color:blue;'>Enable "AI Index"</span> in【Settings】->【Global Search】 ᠪᠠᠶᠢᠭᠠᠯᠢ ᠶᠢᠨ ᠦᠭᠡ ᠬᠡᠯᠡᠨ ᠦ ᠢᠯᠭᠠᠬᠤ ᠦᠷ᠎ᠡ ᠳ᠋ᠦᠩ ᠢ ᠣᠯᠬᠤ ᠳᠤ ᠂ 〔 ᠪᠠᠶᠢᠷᠢᠯᠠᠭᠤᠯᠤᠯᠲᠠ 〕 > ᠪᠦᠬᠦ ᠲᠠᠯ᠎ᠠ ᠶᠢᠨ ᠡᠷᠢᠯᠲᠡ 〕 <san o 〈 〈color᠄ blue;> 《AI ᠰᠦᠪᠡᠭᠴᠢᠯᠡᠬᠦ ᠲᠠᠲᠠᠭᠤᠷ》 ᠢ ᠨᠡᠭᠡᠭᠡᠨ</span>᠎ᠡ᠃ Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 You can manually <span style='color:#3790FA;'>add files</span> or <span style='color:#3790FA;'>modify the space conditions.</span> 您可以手动 <span style='color:#3790FA;'>添加文件</span> ,或 <span style='color:#3790FA;'>修改空间条件</span> searchThumbnailManager Failed to create image for %1 ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_zh_CN.tspeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000001114415156143275033301 0ustar fengfeng QObject Intelligent Search View 智能搜索视图 Use customized Intelligent Data Manager views when searching in File Manager 在文件管理器中搜索时使用自定义的智能数据管理视图 SearchFileItemModel File Name Match 名称匹配 Intelligent Content Recognition Match 智能内容识别匹配 Text Content 文本内容 Name 文件名称 Modified Date 修改日期 File Type 文件类型 File Size 文件大小 To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:#3790FA;'>Open "AI Index"</span> 获取自然语言识别结果,需在【设置】->【全局搜索】 <span style='color:#3790FA;'>打开"AI 索引"</span> To Obtain the Matching Results of Text, Pictures, Videos. Please<span style='color:#3790FA;'> Open "File Content Index"</span> <span style='color:#3790FA;'> 前往开启 "文件内容索引"</span>,以获取正文、图片、视频内容匹配结果。 To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:blue;'>Open "AI Index"</span> To Obtain Natural Language Recognition Results, Please <span style='color:blue;'>Enable "AI Index"</span> in【Settings】->【Global Search】 获取自然语言识别结果,需在【设置】->【全局搜索】 <span style='color:blue;'>打开"AI 索引"</span> Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 You can manually <span style='color:#3790FA;'>add files</span> or <span style='color:#3790FA;'>modify the space conditions.</span> 您可以手动 <span style='color:#3790FA;'>添加文件</span> ,或 <span style='color:#3790FA;'>修改空间条件</span> searchThumbnailManager Failed to create image for %1 ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-view_ky.tspeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/peony-intelligent-search-vie0000664000175000017500000001137715156143275033311 0ustar fengfeng QObject Intelligent Search View روحىي جۅندۅمدۉلۉك ىزدۅۅ گۅرۉنۉشۉ Use customized Intelligent Data Manager views when searching in File Manager ۅجۅت باشقۇرعۇچتان ىزدەگەندە ۅزۉ بەلگىلەنگەن روحىي جۅندۅمدۉلۉك ساندۇۇ باياندامالاردى باشقارىش گۅرۉنۉشۉن ىشتەتىش كەرەك SearchFileItemModel File Name Match اتى شايكەشتىرىپ Intelligent Content Recognition Match روحىي جۅندۅمدۉلۉك مازمۇن ايىرمالاندىرىش شىرەڭكە Text Content Name ات-تەك اتى Modified Date ۅزگۅرتۉلگۅن ۇباقتى File Type ۅجۅت تۉرۉ File Size ۅجۅت چوڭدۇعۇ To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:#3790FA;'>Open "AI Index"</span> To Obtain the Matching Results of Text, Pictures, Videos. Please<span style='color:#3790FA;'> Open "File Content Index"</span> To Obtain Natural Language Recognition Results, Please in【Settings】->【Global Search】<span style='color:blue;'>Open "AI Index"</span> To Obtain Natural Language Recognition Results, Please <span style='color:blue;'>Enable "AI Index"</span> in【Settings】->【Global Search】 تابىحىي تىل ايىرمالاندىرىش ناتىيجاسىنا ەە بولۇۇدا ، " > 【 ئومۇمىيلىقنى ىزدۅۅ ] < span style=color: blue > "AI بەلگىلەنگن نارق " اچۇۇ</span> كەرەك Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 You can manually <span style='color:#3790FA;'>add files</span> or <span style='color:#3790FA;'>modify the space conditions.</span> 您可以手动 <span style='color:#3790FA;'>添加文件</span> ,或 <span style='color:#3790FA;'>修改空间条件</span> searchThumbnailManager Failed to create image for %1 peony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/data/0000775000175000017500000000000015156143275026574 5ustar fengfengpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/data/nodata.svg0000664000175000017500000000751115156143275030567 0ustar fengfengpeony-extensions/peony-intelligent-plugin/peony-intelligent-search-view/searchresultmodel.h0000775000175000017500000002332015156143275031564 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #ifndef SEARCHRESULTMODEL_H #define SEARCHRESULTMODEL_H #include #include "file-info.h" #include "clipboard-utils.h" #include "PeonyFileInfoJob" #include "thumbnail-manager.h" #include "PeonyFileWatcher" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__)|| defined(_M_ARM64) #define ARCH_AVAILABLE_AI #endif bool isFileContentOn(); enum AIConditionType{ allFit=0, aiIndexNotFit, aiSubsystemNotFit }; class SearchFileItemModel : public QAbstractItemModel { Q_OBJECT public: enum ItemIds { ROOT_ID = 0, FILE_CATEGORY_ID = 1, CONTENT_CATEGORY_ID = 2, TEXT_CONTENT_CATEGORY_ID = 3, FILE_ITEM_BASE_ID = 0x100000, CONTENT_ITEM_BASE_ID = 0x200000, TEXT_CONTENT_ITEM_BASE_ID = 0x300000, }; explicit SearchFileItemModel(QObject *parent = nullptr); ~SearchFileItemModel(); // QAbstractItemModel接口 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QMimeData *mimeData(const QModelIndexList& indexes) const override; // 数据操作接口 void clearAll(); void keywordchange(QString keyword); bool isFileCategory(quintptr id) const { return id == FILE_CATEGORY_ID; } bool isContentCategory(quintptr id) const { return id == CONTENT_CATEGORY_ID; } bool isTextContentCategory(quintptr id) const { return id == TEXT_CONTENT_CATEGORY_ID; } bool isFileItem(quintptr id) const { return id >= FILE_ITEM_BASE_ID && id < CONTENT_ITEM_BASE_ID; } bool isContentItem(quintptr id) const { return id >= CONTENT_ITEM_BASE_ID && id = TEXT_CONTENT_ITEM_BASE_ID; } void updateSearchstatus(bool isFinish); void setClientId(QString clientid); void addDefaultContent(); void setAiCondition(bool isAiavailable); void childRemove(const QString uri); bool childRename(const QString& oldUri, const QString& newUri); bool hasDefaultContent(); void loadNextBatchIcons(QList infolist); void loadContentIcon(); bool isSortingEnabled(); public Q_SLOTS: void addFileNameResults(QList fileResults,QString clientid); void addFileNameResultsByBatch(QList fileResults,QString clientid,bool finish); void addContentResults( QList contentResults,QString clientid); void clearResult(); //void addTextContentResults( QList contentResults,QString clientid); Q_SIGNALS: void searchFinished(); void resizeColFitContent(); void expand(int index=0); private: QList m_fileResults; QList m_contentResults; //QList m_textContentResults; QString m_keyword; bool searchFilefinish = false; bool searchContentfinish = false; //bool searchTextContentFinish = false; QString m_clientId; bool m_isAiavailable; bool m_showFileExtension; bool m_hasDefaultContent = false; std::map m_iconMap; QMutex m_dataMutex; bool m_SortingEnabled; QList m_thumbnailQueue; // 控制同时进行的缩略图加载任务数量 int m_maxConcurrentThumbnails = 100; // 当前正在进行的缩略图加载任务数 int m_currentThumbnailTasks = 0; // 处理缩略图队列的方法 void processThumbnailQueue(); }; class TextEdit : public QTextEdit { Q_OBJECT public: explicit TextEdit(QWidget *parent = nullptr); void adjustText(); void setMaxLengthLimit(int length); void setLimitBytes(bool limitBytes); QTextEdit *m_backgroundEdit = nullptr; void setMargins(int left, int top, int right, int bottom); Q_SIGNALS: void finishEditRequest(); void focusLost(); // 自定义信号,用于通知焦点丢失 protected: void keyPressEvent(QKeyEvent *e); void focusOutEvent(QFocusEvent *e) override; private: int m_max_length_limit = 0; bool m_limit_bytes = true; }; class TreeviewDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit TreeviewDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} void initIndexOption(QStyleOptionViewItem *option, const QModelIndex &index) const { return initStyleOption(option, index); } void setHighlightKeyword(const QString& keyword) { m_keyword = keyword; } QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void setEditorData(QWidget* editor, const QModelIndex& index) const override; void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; Q_SIGNALS: void isEditing(bool editing) const; private Q_SLOTS: void slot_finishEdit();/* 编辑完成 */ private: QString m_keyword; }; class SearchFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: enum Type { FileType=1, FileSize, DateRange, FileName, FileLabel, ConditionMatchRule, FileContent, SearchLocation, }; enum FilterFileType { ALL_TYPE, FILE_FOLDER, PICTURE, VIDEO, TXT_FILE, AUDIO, WPS_FILE, OTHERS }; Q_ENUM(FilterFileType) enum FilterFileModifyTime { ALL_TIME, TODAY, YESTERDAY, THIS_WEEK, LAST_WEEK, THIS_MONTH, LAST_MONTH, THIS_YEAR, LAST_YEAR, BEFORE_LAST_YEAR }; Q_ENUM(FilterFileModifyTime) enum FilterFileSize { ALL_SIZE, EMPTY, TINY, SMALL, MEDIUM, BIG, LARGE, GREAT }; Q_ENUM(FilterFileSize) const QString Folder_Type = "inode/directory"; const QString Image_Type = "image/"; const QString Video_Type = "video/"; const QString Text_Type = "text/"; const QString Wps_Type = "application/wps-office"; const QString Audio_Type = "audio/"; const int ALL_FILE = 0; const quint64 K_BASE = 1024; const quint64 TINY_BASE = 16 * K_BASE; const quint64 SMALL_BASE = K_BASE * K_BASE; const quint64 MEDIUM_BASE = 128 * K_BASE * K_BASE; const quint64 BIG_BASE = K_BASE * K_BASE * K_BASE; const quint64 LARGE_BASE = 4 * K_BASE * K_BASE * K_BASE; explicit SearchFilterProxyModel(QObject* parent = nullptr); QStringList getFiltereUrilist(); void update(); void checkSortSettings(); void setCurrenturi(QString uri); bool startWithChinese(const QString &displayName) const; public Q_SLOTS: void updateconditions(QString conditionsobj); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; bool hasChildren(const QModelIndex& parent) const override; bool checkFileModifyTimeFilter(quint64 modifiedTime) const; bool checkFileSizeFilter(quint64 size) const; bool checkFileSizeOrTypeFilter(quint64 sizem, bool isDir) const; bool checkFileNameFilter(const QString &displayName) const; bool checkFileContentFilter(const QString &displayName) const; bool checkFileTypeFilter(QString type) const; private: QStringList urilist; QMap m_conditions; QMapm_contains; QStringList m_keywords; bool m_keywordsContain = true; bool needFileContent = true; bool m_use_default_name_sort_order; bool m_use_global_sort; bool m_folder_first; QString m_uri; Peony::GlobalSettings *m_settings; }; #endif // SEARCHRESULTMODEL_H peony-extensions/common.pri0000664000175000017500000000031415156143137015037 0ustar fengfengINCLUDEPATH += $$PWD DISTFILES += $$PWD/common.json exists(/usr/include/ukuisdk/kylin-com4cxx.h) { message("kylin common for cxx find.") DEFINES += KYLIN_COMMON=true LIBS += -lukui-com4cxx } peony-extensions/peony-admin/0000775000175000017500000000000015156143137015255 5ustar fengfengpeony-extensions/peony-admin/admin-menu-plugin.cpp0000664000175000017500000001034215156143137021307 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * Authors: Meihong * */ #include "admin-menu-plugin.h" #include "file-info.h" #include #include #include #include #include #include #include #include using namespace Peony; AdminMenuPlugin::AdminMenuPlugin(QObject *parent) : QObject(parent) { QTranslator *t = new QTranslator(this); bool b_load = t->load(":/translations/peony-admin_"+QLocale::system().name()); qDebug()<<"\n\n\n\n\n\n\n AdminMenuPlugin translate:"< AdminMenuPlugin::menuActions(Types types, const QString &uri, const QStringList &selectionUris) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QList l; if (uri.startsWith("mtp://")) { return l; } if (selectionUris.isEmpty()) { auto directoryAction = new QAction(tr("Open Directory as Admin")); l<connect(directoryAction, &QAction::triggered, [=](){ QtConcurrent::run([=](){ QProcess p; QUrl url = uri; p.setProgram("pkexec"); QStringList args; args<<"peony"<isVirtual()<mimeType(); if (selectionUris.first().startsWith("computer:///") || selectionUris.first().startsWith("trash:///")|| info->isVirtual()) return l; if (info->isDir()) { auto directoryAction = new QAction(tr("Open Directory as Admin")); l<connect(directoryAction, &QAction::triggered, [=](){ QtConcurrent::run([=](){ QStringList args; QUrl url = selectionUris.first(); args<<"peony"<mimeType().startsWith("text")) { auto directoryAction = new QAction(tr("Open Text as Admin")); l<connect(directoryAction, &QAction::triggered, [=](){ QtConcurrent::run([=](){ QProcess p; QUrl url = selectionUris.first(); p.setProgram("pkexec"); p.setArguments(QStringList()<<"pluma"< Peony::AdminMenuPlugin Open Directory as Admin Otevřít složku jako správce Open Text as Admin Otevřít text jako správce peony-extensions/peony-admin/translations/peony-admin_tr.ts0000664000175000017500000000120315156143137023267 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin Klasörü Yönetici Olarak Aç Open Text as Admin Metin Dosyasını Yönetici Olarak Aç peony-extensions/peony-admin/translations/peony-admin_bo_CN.ts0000664000175000017500000000150115156143137023623 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin དོ་དམ་པའི་ཐོབ་ཐང་ལ་བརྟེན་ནས་དཀར་ཆག་ཕྱེ་བ། Open Text as Admin དོ་དམ་པའི་ཐོབ་ཐང་ལ་བརྟེན་ནས་ཡིག་ཆ་ཁ་འབྱེད་པ། peony-extensions/peony-admin/translations/peony-admin_zh_CN.ts0000664000175000017500000000117615156143137023654 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin 以管理员身份打开目录 Open Text as Admin 以管理员身份打开文件 peony-extensions/peony-admin/translations/peony-admin_zh_HK.ts0000664000175000017500000000117715156143275023662 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin 以管理員身份打開 Directory Open Text as Admin 以管理員身份打開文本 peony-extensions/peony-admin/translations/peony-admin_mn.ts0000664000175000017500000000145415156143137023264 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin ᠬᠠᠮᠢᠶᠠᠷᠤᠭᠴᠢ ᠵᠢᠨ ᠨᠡᠷ᠎ᠡ ᠪᠡᠷ ᠭᠠᠷᠴᠠᠭ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠬᠦ Open Text as Admin ᠬᠠᠮᠢᠺᠶᠠᠷᠤᠭᠴᠢ ᠵᠢᠨ ᠨᠡᠷ᠎ᠡ ᠪᠡᠷ ᠹᠠᠢᠯ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠬᠦ peony-extensions/peony-admin/translations/peony-admin_ug.ts0000664000175000017500000000134715156143137023266 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin باشقۇرغۇچى سالاھىيىتىدە مۇندەرىجىنى ئېچىۋېتىش Open Text as Admin باشقۇرغۇچى سالاھىيىتىدە ھۆججەتنى ئېچىۋېتىش peony-extensions/peony-admin/translations/peony-admin_ky.ts0000664000175000017500000000135515156143137023275 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin باشقارعۇۇچۇ كۉبۅلۉگۉندۅ مازمۇۇندۇ ئچىپ جىبەرۉۉ Open Text as Admin باشقارعۇۇچۇ كۉبۅلۉگۉندۅ ۅجۅتۉن ئچىپ جىبەرۉۉ peony-extensions/peony-admin/translations/peony-admin_kk.ts0000664000175000017500000000133715156143137023257 0ustar fengfeng Peony::AdminMenuPlugin Open Directory as Admin باسقارۋشٸسٸ سالاۋاتىندا باسمازمۇندى اشىپ وتۋ Open Text as Admin باسقارۋشٸسٸ سالاۋاتىندا حۇجاتتى اشىپ وتۋ peony-extensions/peony-admin/org.freedesktop.peony-admin.policy.in0000664000175000017500000001747115156143137024435 0ustar fengfeng Peony Admin https://github.com/ukui/peony-extensions Run Peony as Root شغّل نوتلس كجذر Esegui Peony come Root Executar o Peony como Root Lancez Peony en tant qu'administrateur Запустить Peony как Администратор Paleisti failų naršyklę kaip Administratorius Ejecutar Peony como administrador Uruchom Peony jako Root Spustenie programu Peony ako správca Peony als Aministrator ausführen Authentication is required to run the File Manager as Administrator المصادقة مطلوبة لتشغيل مدير الملفات كمدير Autenticazione richiesta per lanciare il File Manager come Amministratore Autenticação é necessária para executar o Gestor de Ficheiros como Administrador L'authentification est requise pour lancer le gestionnaire de fichiers en tant qu'administrateur Для запуска Файлового Менеджера от имени Администратора требуется аутентификация Failų naršyklei paleisti Administratoriaus teisėmis reikia įvesti slaptažodį Se necesita autentificación para ejecutar el Gestor de Ficheros como Administrador Uwierzytelnienie jest wymagane aby uruchomić Menedżer Plików jako Administrator Na spustenie správcu súborov ako administrátor sa vyžaduje overenie totožnosti Authentifizierung ist erforderlich um den Datei-Manager als Administrator auszuführen 以超级用户身份运行 Peony system-file-manager auth_admin auth_admin auth_admin_keep $${PEONY_PATH} true Open Pluma as Root افتح محرر النصوص Pluma كجذر Abrir Pluma como Root Aprire Pluma come Root Ouvrir Pluma en tant qu'administrateur Открыть Pluma как Администратор Atverti teksto failą kaip Administratorius Abrir Pluma como Administrador Otwórz Pluma jako Root Otvorenie programu Pluma ako správca Öffne Pluma als Administrator Authentication is required to run the Text Editor as Administrator المصادقة مطلوبة لتشغيل محرر النصوص كمدير Autenticação é necessária para executar o Editor de Texto como Administrador L'authentification est requise pour lancer l'éditeur de texte en tant qu'administrateur Для запуска Текстового Редактора от имени Администратора требуется аутентификация Tekstų redagavimo programai paleisti Administratoriaus teisėmis reikia įvesti slaptažodį Autenticazione richiesta per eseguire l'Editor di Testo come Amministratore Se necesita autentificación para abrir el Editor de Texto como Administrador Uwierzytelnienie jest wymagane aby uruchomić Edytor tekstowy jako Administrator Na spustenie textového editora ako administrátor sa vyžaduje overenie totožnosti Authentifizierung ist erforderlich um den Text-Editor als Administrator auszuführen 以超级用户身份打开 Pluma accessories-text-editor auth_admin auth_admin auth_admin_keep $${PLUMA_PATH} true Run as Root شغّل كجذر Exécuter en tant qu'administrateur Eseguire come Root Executar como Root Запустить как Администратор Vykdyti Administratoriaus teisėmis Uruchom jako Root Spustenie ako správca Als Administrator ausführen Authentication is required to run as Administrator المصادقة مطلوبة للتشغيل كمدير L'authentification est requise pour exécuter en tant qu'administrateur Autenticazione necessaria per eseguire come Amministratore Autenticação é necessária para executar como Administrador Для запуска исполняемых файлов от имени Администратора требуется аутентификация Vykdomųjų failų paleidimui Administratoriaus teisėmis reikia įvesti slaptažodį Uwierzytelnienie jest wymagane aby uruchomić jako Administrator Na spustenie ako administrátor sa vyžaduje overenie totožnosti Authentifizierung ist erforderlich zum ausführen als Administrator 以超级用户身份运行 utilities-terminal auth_admin auth_admin auth_admin_keep $${TERMINAL_PATH} true peony-extensions/peony-admin/admin-menu-plugin.h0000664000175000017500000000354115156143137020757 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef ADMINMENUPLUGIN_H #define ADMINMENUPLUGIN_H #include #include namespace Peony { class AdminMenuPlugin : public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit AdminMenuPlugin(QObject *parent = nullptr); PluginInterface::PluginType pluginType() override {return PluginInterface::MenuPlugin;} const QString name() override {return "Peony Qt Admin Extension";} const QString description() override {return "Allow user launch file/directory as admin";} const QIcon icon() override {return QIcon::fromTheme("system-users-symbolic");} void setEnable(bool enable) override {m_enable = enable;} bool isEnable() override {return m_enable;} QString testPlugin() override {return "";} QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; private: bool m_enable; }; } #endif // ADMINMENUPLUGIN_H peony-extensions/peony-send-to-device/0000775000175000017500000000000015156143275016776 5ustar fengfengpeony-extensions/peony-send-to-device/translations/0000775000175000017500000000000015156143275021517 5ustar fengfengpeony-extensions/peony-send-to-device/translations/peony-send-to-device_zh_HK.ts0000664000175000017500000000116515156143275027113 0ustar fengfeng Peony::DriverAction Send to a removable device 發送到可行動裝置 Peony::SendToPlugin Send to a removable device 發送到可行動裝置 peony-extensions/peony-send-to-device/translations/peony-send-to-device_es.ts0000664000175000017500000000121115156143275026507 0ustar fengfeng Peony::DriverAction Send to a removable device Enviar a un dispositivo extraíble Peony::SendToPlugin Send to a removable device Enviar a un dispositivo extraíble peony-extensions/peony-send-to-device/translations/peony-send-to-device_cs.ts0000664000175000017500000000115115156143275026510 0ustar fengfeng Peony::DriverAction Send to a removable device Peony::SendToPlugin Send to a removable device peony-extensions/peony-send-to-device/translations/peony-send-to-device_kk_KZ.ts0000664000175000017500000000133115156143275027114 0ustar fengfeng Peony::DriverAction Send to a removable device شىعارىپ جىبەرۋگە بولاتٸن اسپاپقا جولداۋ Peony::SendToPlugin Send to a removable device شىعارىپ جىبەرۋگە بولاتٸن اسپاپقا جولداۋ peony-extensions/peony-send-to-device/translations/peony-send-to-device_ky_KG.ts0000664000175000017500000000135115156143275027111 0ustar fengfeng Peony::DriverAction Send to a removable device چىعارىپ جىبەرۉۉگۅ بولوتۇرعان اسباپقا جولدوو Peony::SendToPlugin Send to a removable device چىعارىپ جىبەرۉۉگۅ بولوتۇرعان اسباپقا جولدوو peony-extensions/peony-send-to-device/translations/peony-send-to-device_mn.ts0000664000175000017500000000137715156143275026527 0ustar fengfeng Peony::DriverAction Send to a removable device ᠰᠢᠯᠵᠢᠮᠡᠯ ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠳ᠋ᠤ᠌ ᠢᠯᠡᠬᠡᠬᠦ Peony::SendToPlugin Send to a removable device ᠰᠢᠯᠵᠢᠮᠡᠯ ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠳ᠋ᠤ᠌ ᠢᠯᠡᠬᠡᠬᠦ peony-extensions/peony-send-to-device/translations/peony-send-to-device_bo_CN.ts0000664000175000017500000000136015156143275027065 0ustar fengfeng Peony::DriverAction Send to a removable device སྤོ་སྒུལ་སྒྲིག་ཆས་ལ་སྐྱེལ་བ། Peony::SendToPlugin Send to a removable device སྤོ་སྒུལ་སྒྲིག་ཆས་ལ་སྐྱེལ་བ། peony-extensions/peony-send-to-device/translations/peony-send-to-device_de.ts0000664000175000017500000000121315156143275026472 0ustar fengfeng Peony::DriverAction Send to a removable device An einen Wechseldatenträger senden Peony::SendToPlugin Send to a removable device An einen Wechseldatenträger senden peony-extensions/peony-send-to-device/translations/peony-send-to-device_fr.ts0000664000175000017500000000122315156143275026512 0ustar fengfeng Peony::DriverAction Send to a removable device Envoyer vers un périphérique amovible Peony::SendToPlugin Send to a removable device Envoyer vers un périphérique amovible peony-extensions/peony-send-to-device/translations/peony-send-to-device_tr.ts0000664000175000017500000000115115156143275026530 0ustar fengfeng Peony::DriverAction Send to a removable device Peony::SendToPlugin Send to a removable device peony-extensions/peony-send-to-device/translations/peony-send-to-device_ug_CN.ts0000664000175000017500000000133715156143275027104 0ustar fengfeng Peony::DriverAction Send to a removable device چىقىرىۋېتىشكە بولىدىغان ئۈسكۈنىگە يوللاش Peony::SendToPlugin Send to a removable device چىقىرىۋېتىشكە بولىدىغان ئۈسكۈنىگە يوللاش peony-extensions/peony-send-to-device/translations/peony-send-to-device_zh_CN.ts0000664000175000017500000000116215156143275027106 0ustar fengfeng Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 peony-extensions/peony-send-to-device/peony-send-to-device.pro0000664000175000017500000000236615156143275023465 0ustar fengfengQT += core gui widgets concurrent include(../common.pri) TARGET = peony-send-to-device TEMPLATE = lib DEFINES += PEONYADMIN_LIBRARY PKGCONFIG += peony CONFIG += link_pkgconfig no_keywords c++11 plugin debug SOURCES += \ send-to-device-plugin.cpp HEADERS += \ send-to-device-plugin.h TRANSLATIONS += \ translations/peony-send-to-device_cs.ts \ translations/peony-send-to-device_de.ts \ translations/peony-send-to-device_es.ts \ translations/peony-send-to-device_fr.ts \ translations/peony-send-to-device_kk_KZ.ts \ translations/peony-send-to-device_ug_CN.ts \ translations/peony-send-to-device_ky_KG.ts \ translations/peony-send-to-device_tr.ts \ translations/peony-send-to-device_zh_CN.ts \ translations/peony-send-to-device_bo_CN.ts \ translations/peony-send-to-device_mn.ts \ translations/peony-send-to-device_zh_HK.ts #RESOURCES += \ # resources.qrc CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = /translations/ target.path = $$[QT_INSTALL_LIBS]/peony-extensions INSTALLS += target SKIP_TEST = $$(EXTENSIONS_SKIP_TEST) isEmpty(SKIP_TEST) { message("build with tests") QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage LIBS += -lgcov } peony-extensions/peony-send-to-device/send-to-device-plugin.cpp0000664000175000017500000002456315156143275023616 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Ding Jing * */ #include "send-to-device-plugin.h" #include "file-info.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KYLIN_COMMON #include #endif #define V10_SP1_EDU "V10SP1-edu" static QString getIconName (GIcon* icons); static void mounted_func (gpointer data, gpointer udata); static void handle_mount_added (GVolumeMonitor* monitor, GMount* mount, gpointer data); static void handle_mount_removed(GVolumeMonitor* monitor, GMount* mount, gpointer data); using namespace Peony; SendToPlugin::SendToPlugin(QObject *parent) : QObject(parent), mEnable(true) { // translator QTranslator *t = new QTranslator(this); t->load(":/translations/peony-send-to-device_"+QLocale::system().name()); QFile file(":/translations/peony-send-to-device_"+QLocale::system().name()+".ts"); QApplication::installTranslator(t); } QList SendToPlugin::menuActions(Types types, const QString &uri, const QStringList &selectionUris) { #ifdef KYLIN_COMMON if (QString::fromStdString(KDKGetPrjCodeName()) == V10_SP1_EDU) { return QList(); } #endif QList l; if (selectionUris.count() <= 0) { return l; } auto info = FileInfo::fromUri(selectionUris.first()); if (selectionUris.first().startsWith("computer:///") || selectionUris.first().startsWith("trash:///") || selectionUris.first().startsWith("filesafe:///") || info->isVirtual()) { return l; } QStringList selectionTargerUris = selectionUris; if (selectionUris.first().startsWith("recent:///")) { selectionTargerUris.clear(); for (auto u : selectionUris){ selectionTargerUris << FileUtils::getTargetUri(u); } } QAction* action = new DriverAction(selectionTargerUris); if (action) { l << action; } Q_UNUSED(uri) Q_UNUSED(types) return l; } DriverItem::DriverItem(QString uri, QIcon icon, QString name, QObject* parent) : QAction(parent), mName(name), mIcon(icon), mUri(uri) { setIcon(mIcon); setText(name); } const QIcon DriverItem::icon() { return mIcon; } const QString DriverItem::name() { return mName; } const QString DriverItem::uri() { return mUri; } static void handle_mount_added(GVolumeMonitor* monitor, GMount* mount, gpointer data) { DriverAction* drivers = (DriverAction*)data; mounted_func (mount, drivers); Q_UNUSED(monitor) } static void handle_mount_removed(GVolumeMonitor* monitor, GMount* mount, gpointer data) { char* path = nullptr; GFile* location = nullptr; if (!mount || !data) return; location = g_mount_get_default_location(mount); if (location) { path = g_file_get_uri(location); } if (nullptr != path) { ((DriverAction*)data)->driverRemove(path); } qDebug() << "remove uri:" << path; if (!path) g_free(path); if (!location) g_object_unref(location); Q_UNUSED(monitor) } static void mounted_func (gpointer data, gpointer udata) { GMount* mount = (GMount*)data; GFile* location = nullptr; char* uri = nullptr; DriverAction* act = (static_cast(udata)); if (!data || !udata || !act) return; if (mount) { location = g_mount_get_default_location(mount); if (location) { uri = g_file_get_uri(location); auto path = g_file_peek_path(location); if (path) { g_autoptr (GUnixMountEntry) entry = g_unix_mount_at(path, NULL); if (entry) { auto fsType = g_unix_mount_get_fs_type(entry); if (QString(fsType).contains("fuse.kyfs")) { return; } } } } } // check permission FileInfoJob* fileInfo = new FileInfoJob(uri); fileInfo->setAutoDelete (); fileInfo->queryAsync (); act->connect(fileInfo, &FileInfoJob::queryAsyncFinished, act, [=] (bool success) { if (success) { GMount* mount = (GMount*)data; if (G_IS_MOUNT (mount)) { g_autofree char* name = g_mount_get_name(mount); GIcon* icons = g_mount_get_icon(mount); QString icon = getIconName (icons); if (icon.isEmpty()) { g_autoptr(GVolume) volume = g_mount_get_volume(mount); g_autoptr(GIcon) g_icon = g_volume_get_icon(volume); icon = FileUtils::getIconStringFromGIcon(g_icon); } if (icon == "drive-harddisk-usb") { double size = FileUtils::getDeviceSize(fileInfo->getInfo()->unixDeviceFile().toUtf8().constData()); if (size > 128) { icon = "drive-harddisk-usb"; } else { icon = "drive-removable-media-usb"; } } std::shared_ptr info = fileInfo->getInfo(); if (uri && name && info.get()->canExecute() && info.get()->canWrite()) { Q_EMIT (static_cast(udata))->driverAdded(uri, name, icon); } qDebug() << "name:" << name << " uri:" << uri << " icons:" << icons << " icon:" << icon; } } }); if (!uri) g_free(uri); if (!location) g_object_unref(location); } static QString getIconName (GIcon* icons) { if (nullptr == icons) { return ""; } QString icon = nullptr; if (G_IS_ICON(icons)) { const gchar* const* iconNames = g_themed_icon_get_names(G_THEMED_ICON (icons)); if (iconNames) { auto p = iconNames; while (*p) { QIcon icont = QIcon::fromTheme(*p); if (!icont.isNull()) { icon = QString (*p); break; } else { p++; } } } g_object_unref(icons); } return icon; } DriverAction::DriverAction(const QStringList& uris, QObject *parent) : QAction(parent) { mMenu = new QMenu(); mVolumeMonitor = g_volume_monitor_get(); mDeviceAdd = g_signal_connect(G_OBJECT(mVolumeMonitor), "mount-added", G_CALLBACK(handle_mount_added), (gpointer)this); mDeviceRemove = g_signal_connect(G_OBJECT(mVolumeMonitor), "mount-removed", G_CALLBACK(handle_mount_removed), (gpointer)this); connect(this, &DriverAction::driverAdded, this, [=] (QString uri, QString name, QString icon) { if (!mDrivers.contains(uri)) { QString curUri = uri; if (curUri.endsWith("/")) { curUri.chop(1); } QString usrName = QStandardPaths::writableLocation(QStandardPaths::HomeLocation).section("/", -1, -1); if ("file:///data" == curUri || "file:///backup" == curUri || "file:///boot" == curUri || "file:///tmp" == curUri || "file:///var" == curUri || "file:///media/" + usrName + "/SYSBOOT" == curUri || "file:///media/" + usrName + "/sysboot" == curUri || "file:///media/" + usrName + "/data" == curUri || "file:///media/" + usrName + "/DATA" == curUri || curUri.startsWith("burn://") || curUri.startsWith("ftp://") || curUri.startsWith("smb://") || curUri.startsWith("file:///media/" + usrName + "/KYLIN-") || curUri.startsWith("file:///media/" + usrName + "/kylin-") ) { return ; } auto it = new DriverItem (uri, QIcon::fromTheme(icon), name); it->connect(it, &QAction::triggered, it, [=] () { qDebug() << "======" << __func__ << "uris:" << uris << "it->uri" << it->uri(); FileCopyOperation* op = new FileCopyOperation(uris, it->uri(), nullptr); op->setAutoDelete(true); FileOperationManager::getInstance()->startOperation(op); }); mMenu->addAction(it); mDrivers[uri] = it; } showAction(); }); connect(this, &DriverAction::driverRemove, this, [=] (QString uri) { if (mDrivers.contains(uri)) { auto it = mDrivers[uri]; mMenu->removeAction(it); it->deleteLater(); mDrivers.remove(uri); } showAction(); }); GList* mounts = g_volume_monitor_get_mounts (mVolumeMonitor); if (mounts) { g_list_foreach (mounts, mounted_func, this); g_list_free_full(mounts, g_object_unref); } setMenu(mMenu); setText(tr("Send to a removable device")); showAction(); } DriverAction::~DriverAction() { //if (mMenu) delete mMenu; if (mVolumeMonitor) { g_signal_handler_disconnect(G_OBJECT(mVolumeMonitor), mDeviceAdd); g_signal_handler_disconnect(G_OBJECT(mVolumeMonitor), mDeviceRemove); g_object_unref(mVolumeMonitor); } for (auto it = mDrivers.begin(); it != mDrivers.end(); ++it) { it.value()->deleteLater(); } if (!mDrivers.isEmpty()) { mDrivers.clear(); } } void DriverAction::showAction() { setVisible(mDrivers.size() > 0 ? true : false); } peony-extensions/peony-send-to-device/send-to-device-plugin.h0000664000175000017500000000573015156143137023253 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Ding Jing * */ #ifndef ADMINMENUPLUGIN_H #define ADMINMENUPLUGIN_H #include #include #include #undef signals namespace Peony { class DriverItem; class RemoveableDriver; class SendToPlugin : public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit SendToPlugin(QObject *parent = nullptr); QString testPlugin() override {return "";} bool isEnable() override {return mEnable;} void setEnable(bool enable) override {mEnable = enable;} const QString description() override {return tr("Send to a removable device");} const QString name() override {return "Peony Qt Send to a removable device";} const QIcon icon() override {return QIcon::fromTheme("document-send-symbolic");} PluginInterface::PluginType pluginType() override {return PluginInterface::MenuPlugin;} QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; private: bool mEnable; }; class DriverAction : public QAction { Q_OBJECT public: explicit DriverAction (const QStringList& uris, QObject* parent = nullptr); ~DriverAction (); Q_SIGNALS: void driverRemove (QString uri); void driverAdded (QString uri, QString name, QString icon); private: void showAction (); private: gulong mDeviceAdd; gulong mDeviceRemove; QMenu* mMenu = nullptr; GVolumeMonitor* mVolumeMonitor = nullptr; QMap mDrivers; }; class DriverItem : public QAction { friend class RemoveableDriverModel; Q_OBJECT public: explicit DriverItem (QString uri, QIcon icon, QString name, QObject* parent = nullptr); const QIcon icon (); const QString uri (); const QString name (); private: bool mConnect; QString mName; QIcon mIcon; QString mUri; }; } #endif peony-extensions/COPYING0000664000175000017500000001674315156143137014103 0ustar fengfeng GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. peony-extensions/peony-intelligent-data-management-service/0000775000175000017500000000000015156143275023165 5ustar fengfeng././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service_mn.tspeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000002775515156143275033255 0ustar fengfeng AISearchTask Failed to create ai search session. ᠡᠷᠢᠬᠦ ᠬᠤᠷᠠᠯ ᠢ ᠡᠭᠦᠳᠦᠨ ᠪᠠᠶᠢᠭᠤᠯᠬᠤ ᠶᠢᠨ ᠠᠷᠭ᠎ᠠ ᠦᠭᠡᠶ ᠃ Search task cancelled. ᠡᠷᠢᠬᠦ ᠡᠭᠦᠷᠭᠡ ᠨᠢᠭᠡᠨᠲᠡ ᠬᠦᠴᠦᠨ ᠦᠭᠡᠢ ᠪᠣᠯᠵᠠᠢ ᠃ Condition File name contains "%1" ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠸᠷᠢᠶᠠᠯ ᠤᠨ ᠨᠡᠷ᠎ᠡ ᠳᠤ  ア  ᠪᠠᠭᠲᠠᠨ᠎ᠠ File name without "%1" 《1》 ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠨᠡᠷ᠎ᠡ ᠠᠪᠴᠤ ᠪᠣᠯᠬᠤ ᠦᠭᠡᠶ᠃ File type contains "%1" ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠸᠷᠢᠶᠠᠯ ᠤᠨ ᠳᠦᠷᠦᠯ ᠬᠡᠯᠪᠡᠷᠢ ᠳᠤ  ア  ᠪᠠᠭᠲᠠᠨ᠎ᠠ By suffix ᠠᠷᠤ ᠲᠠᠯ᠎ᠠ ᠪᠠᠷ ᠳᠠᠷᠤ ᠃ Folder ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠬᠠᠪᠤᠳᠠᠷ Plain Text ᠴᠤᠯ ᠳᠠᠢ᠍ᠢᠰᠺᠸᠲ WPS Document WPS ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠃ PDF PDF ᠬᠡᠯᠪᠡᠷᠢ ᠃ Image ᠵᠢᠷᠤᠭ Video ᠸᠢᠳᠢᠤ᠋ Audio ᠳᠠᠭᠤᠨ ᠤ ᠳᠠᠪᠲᠠᠮᠵᠢ ᠃ File type contains %1 ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠲᠦᠷᠦᠯ ᠬᠡᠯᠪᠡᠷᠢ ᠳᠦ 1 ᠪᠠᠭᠲᠠᠨ᠎ᠠ ᠃ All ᠪᠦᠬᠦᠢᠯᠡ Empty(0K) ᠬᠤᠭᠤᠰᠤᠨ(0K) Tiny(0-16K) ᠵᠢᠵᠢᠭ ᠬᠡᠯᠪᠡᠷᠢ (0-16K) Small(16K-1M) ᠵᠢᠵᠢᠭ ᠨᠣᠮᠧᠷ (16K-1M) Medium(1M-128M) ᠳᠤᠮᠳᠠ ᠵᠡᠷᠭᠡ(1M-128M) Big(128M-1G) ᠶᠡᠬᠡ(128M-1G) Large(1-4G) ᠮᠤᠨᠳᠠᠭ ᠶᠡᠬᠡ(1-4G) Great(>4G) ᠮᠠᠰᠢ ᠶᠡᠭᠡ(>4G) File size contains %1 ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠶᠡᠬᠡ ᠪᠠᠭ᠎ᠠ ᠳᠤ 1 ᠪᠠᠭᠲᠠᠨ᠎ᠠ ᠃ File size without %1 ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠶᠡᠬᠡ ᠪᠠᠭ᠎ᠠ (〈 1 ᠪᠠᠭᠲᠠᠬᠤ ᠦᠭᠡᠢ ) File content match %1 ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠠᠭᠤᠯᠭ᠎ᠠ ᠨᠢ 1 ᠃ File location contains "%1" ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠸᠷᠢᠶᠠᠯ ᠤᠨ ᠪᠠᠶᠢᠷᠢ ᠳᠤ  ア  ᠪᠠᠭᠲᠠᠨ᠎ᠠ File location without "%1" 《1》 ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠪᠠᠶᠢᠷᠢ ᠪᠠᠶᠢᠬᠤ ᠦᠭᠡᠶ᠃ Later than "%1" 《1》 ᠡᠴᠡ ᠬᠣᠵᠢᠮᠳᠠᠭᠰᠠᠨ ᠨᠢ 《1》 ᠡᠴᠡ ᠬᠣᠵᠢᠮ ᠣᠷᠣᠶᠢᠲᠠ Earlier than "%1" 《 1 》 ᠡᠴᠡ ᠡᠷᠲᠡ 《 1 》 ᠡᠴᠡ ᠡᠷᠲᠡ 《 1 》 ᠡᠴᠡ ᠡᠷ DatabaseManager New Space %1 ᠰᠢᠨ᠎ᠡ ᠣᠷᠣᠨ ᠵᠠᠶ ᠨᠢ 1 ᠃ File %1 has areally been added to %2 ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ 1 ᠶᠢ ᠨᠢᠭᠡᠨᠲᠡ ᠵᠢᠩᠬᠢᠨᠢ ᠪᠡᠷ ᠨᠡᠮᠡᠭᠡᠳ 2 ᠪᠣᠯᠲᠠᠯ᠎ᠠ ᠨᠡᠮᠡᠵᠡᠢ ᠃ File %1 doesn't exsit in any space. ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠢ ᠁ 1 ᠶᠠᠮᠠᠷ ᠴᠤ ᠣᠷᠣᠨ ᠵᠠᠶ ᠳᠤ ᠠᠷᠢᠯᠬᠤ ᠦᠭᠡᠢ ᠃ Failed to get space %1's id from query ᠠᠰᠠᠭᠤᠨ ᠯᠠᠪᠯᠠᠬᠤ ᠳᠤᠮᠳᠠ ᠠᠴᠠ ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢᠨ 1 ᠶᠢᠨ ID ᠣᠯᠵᠤ ᠳᠡᠶᠢᠯᠬᠦ ᠦᠭᠡᠶ ᠃ Failed to get %1's id from query ᠪᠠᠶᠢᠴᠠᠭᠠᠨ ᠯᠠᠪᠯᠠᠬᠤ ᠳᠤᠮᠳᠠ ᠠᠴᠠ 1 ᠶᠢᠨ ID ᠣᠯᠵᠤ ᠳᠡᠶᠢᠯᠬᠦ ᠦᠭᠡᠶ ᠃ ModelController Failed to rename space %1 to %2, %2 is existed. ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢᠨ ア ᠶᠢ ア ᠬᠦᠨᠳᠦ ᠶᠢ イ ᠭᠡᠵᠦ ᠨᠡᠷᠡᠯᠡᠵᠦ ᠂ ᠣᠷᠣᠰᠢᠵᠤ イ ᠣᠷᠣᠰᠢᠵᠤ ᠪᠠᠶᠢᠨ᠎ᠠ ᠃ Failed to remove space %1, error message: %2 ᠣᠷᠣᠨ ᠵᠠᠶ ᠨᠢ ア ᠶᠢ ᠬᠠᠰᠤᠵᠤ ᠳᠡᠶᠢᠯᠬᠦ ᠦᠭᠡᠶ ᠂ ᠪᠤᠷᠤᠭᠤ ᠮᠡᠳᠡᠭᠡ ᠄ イ ᠃ New Space %1 ᠰᠢᠨ᠎ᠡ ᠣᠷᠣᠨ ᠵᠠᠶ ᠨᠢ 1 ᠃ Failed to updatestatus space %1, error message: %2 ᠪᠠᠶᠢᠳᠠᠯ ᠤᠨ ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢ ᠰᠢᠨᠡᠳᠬᠡᠬᠦ ᠶᠢᠨ ᠠᠷᠭ᠎ᠠ ᠦᠭᠡᠶ ᠂ ᠪᠤᠷᠤᠭᠤ ᠮᠡᠳᠡᠭᠡ ᠄ 2 ᠃ QObject %1 %2 %1 %2 Intelligent Data Manager Introduction ᠤᠶᠤᠨᠲᠤ ᠲᠣᠭ᠎ᠠ ᠪᠠᠷᠢᠮᠲᠠ ᠶᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ ᠶᠢᠨ ᠪᠠᠭᠠᠵᠢ ᠶᠢᠨ ᠲᠠᠨᠢᠯᠴᠠᠭᠤᠯᠭ᠎ᠠ ᠃ peony-intelligent-data-management-service ᠮᠦ ᠳ᠋ᠠᠨ ᠤ ᠤᠶᠤᠨᠲᠤ ᠲᠣᠭ᠎ᠠ ᠪᠠᠷᠢᠮᠲᠠ ᠶᠢᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ ᠶᠢᠨ ᠦᠢᠯᠡᠴᠢᠯᠡᠭᠡ ᠃ exit ᠲᠦᠷ ᠳᠠᠬᠢᠨ ᠡᠬᠢᠯᠡᠭᠦᠯᠬᠦ ᠦᠭᠡᠢ UKUI::IDM::FileInfoJob Create Date: %1 ᠡᠭᠦᠳᠦᠨ ᠪᠠᠶᠢᠭᠤᠯᠬᠤ ᠡᠳᠦᠷ ᠬᠤᠭᠤᠴᠠᠭ᠎ᠠ ᠄ 1 ᠃ Open Date: %1 ᠴᠢᠯᠥᠭᠡᠲᠡᠶ ᠲᠠᠯᠪᠢᠬᠤ ᠡᠳᠦᠷ ᠬᠤᠭᠤᠴᠠᠭ᠎ᠠ ᠄ 1 ᠃ peony-extensions/peony-intelligent-data-management-service/main.cpp0000664000175000017500000000606615156143275024625 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #include #include #include #include #include "model/modelcontroller.h" #include #include #include #include "model/searchresultmodel.h" #include "3rd-parties/qtsinglecoreapplication.h" #include #include #ifdef KY_SDK_KABASE #include #endif int main(int argc, char *argv[]) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif #ifdef KY_SDK_KABASE qInstallMessageHandler(kdk::kabase::Log::logOutput); #else initUkuiLog4qt("peony-intelligent-data-management-service"); #endif QCoreApplication::setApplicationName("peony-intelligent-data-management-service"); QtSingleCoreApplication app("peony-intelligent-data-management-service", argc, argv); if (app.isRunning()) { QString message = app.arguments().join(' '); qDebug() << "send message" << message; if (app.sendMessage(message)) { return 0; } else { qWarning() << "failed to send massage to running program"; } } QTranslator idm; if (idm.load("/usr/share/peony-intelligent-data-management-service/peony-intelligent-data-management-service_"+QLocale::system().name())) app.installTranslator(&idm); QCommandLineParser parser; parser.addHelpOption(); QCommandLineOption quitOption(QStringList() << "q" << "quit", QObject::tr("exit")); parser.addOption(quitOption); parser.process(app); if (parser.isSet(quitOption)) { app.quit(); return 0; } auto mc = ModelController::globalInstance(); mc->setParent(&app); QObject::connect(&app, &QtSingleCoreApplication::messageReceived, &app, [&](const QString & message) { QStringList arguments = message.split(" "); qDebug() << "message recived" << message << "arguments" << arguments; parser.process(arguments); if (parser.isSet(quitOption)) { app.quit(); }}); return app.exec(); } peony-extensions/peony-intelligent-data-management-service/database/0000775000175000017500000000000015156143275024731 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/database/databasemanager.cpp0000664000175000017500000006635015156143275030546 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "databasemanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char letters[] = {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}; QString createRandomString(int length) { QString result; for (int i = 0; i < length; ++i) { int randomIndex = QRandomGenerator::global()->bounded(61); result.append(letters[randomIndex]); } return result; } class DatabaseManagerPrivate { public: DatabaseManagerPrivate(DatabaseManager *manager, const QString &dbname,const QString &filedbname) { QString connectionName = QString("sqlite_con_thread_%1").arg(QString::number(reinterpret_cast(QThread::currentThreadId()))); QString filedbconnectionName = QString("filesqlite_con_thread_%1").arg(QString::number(reinterpret_cast(QThread::currentThreadId()))); if (QSqlDatabase::contains(connectionName) && QSqlDatabase::contains(filedbconnectionName)) { db = QSqlDatabase::database(connectionName); db.open(); filedb = QSqlDatabase::database(filedbconnectionName); filedb.open(); return; } db = QSqlDatabase::addDatabase("QSQLITE", connectionName); filedb = QSqlDatabase::addDatabase("QSQLITE", filedbconnectionName); QObject::connect(QThread::currentThread(), &QThread::destroyed, qApp, [=]{ QSqlDatabase::removeDatabase(connectionName); }); QString dbDirectory = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); QDir d; d.mkpath(dbDirectory); db.setDatabaseName(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + dbname); filedb.setDatabaseName(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + filedbname); if (!db.open()) { qCritical() << "can not open database" << db.lastError().text(); } else { // 如果没有创建空间,创建默认空间 auto tables = db.tables(); if (tables.isEmpty()) { // 创建空间表格,包含Name、ID、空间条件 QString createSpacesTableCommand = "CREATE TABLE \"Spaces\" (" "\"Name\" TEXT NOT NULL UNIQUE," "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT," "\"Conditions\" BLOB," "\"Active\" BOOL NOT NULL," "\"AutoUpdate\" BOOL NOT NULL," "\"lastAddedFileCount\" DEFAULT 0," "\"lastAddedFileTimeMsec\" DEFAULT 0" ")"; db.exec(createSpacesTableCommand); createInternalSpaces(); } else { // 将spaces和id的表格进行缓存 NeedAddintroFile = false; QSqlQuery query(db); query.prepare("SELECT (id, Name) FROM Spaces WHERE Active=true"); bool ok = query.exec(); if (!ok) { qWarning() << query.lastError().text(); } else { while (query.next()) { int id = query.value(0).toInt(); QString name = query.value(1).toString(); spaces.insert(id, name); } } bool flag = false; QString queryStr = QString("PRAGMA table_info(Spaces);"); if(query.exec(queryStr)) { while(query.next()) { QString colname = query.value(1).toString(); if(colname == "lastAddedFileCount" || colname == "lastAddedFileTimeMsec") { flag =true; break; } } } if(!flag) { QString addfilecount = QString("ALTER TABLE Spaces ADD COLUMN lastAddedFileCount INTEGER DEFAULT 0"); if (!query.exec(addfilecount)) { qCritical() << "failed to add col" <addFileToSpace("/usr/share/1", 1, true); //manager->addFileToSpace("/usr/share/2", 1, true); NeedAddintroFile = true; } } } void createIntroSpace() { QString workSpaceName = QObject::tr("Intelligent Data Manager Introduction"); // 创建默认空间 QString createWorkCommand = QString("INSERT INTO Spaces (ID, Name, Active,AutoUpdate,lastAddedFileCount, lastAddedFileTimeMsec) VALUES (1, '%1', true,true, 0 , 0)").arg(workSpaceName); db.exec(createWorkCommand); spaces.insert(0, workSpaceName); // 默认空间条件初始化 QJsonObject obj; obj["conditions count"] = 0; obj["labels"] = QJsonArray::fromStringList(QStringList()); QJsonDocument doc(obj); QString configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); QDir d; d.mkpath(configPath + "/spaces"); QString jsonPath = QString("%1/spaces/1.json").arg(configPath); QFile file(jsonPath); if (file.open(QFile::WriteOnly|QFile::Text)) { file.write(doc.toJson()); file.close(); } } void createInternalSpaces() { ensureXdgUserDirs(); createIntroSpace(); //createDocSpace(); //createDownloadSpace(); } void ensureXdgUserDirs() { QString xdgUserDirsConfigFilePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/user-dirs.dirs"; QFile xdgUserDirsConfigFile(xdgUserDirsConfigFilePath); if (!xdgUserDirsConfigFile.exists()) { QProcess p; p.setProgram("/usr/bin/xdg-user-dirs-update"); p.start(); p.waitForFinished(); } } QSqlDatabase db; QSqlDatabase filedb; QMap spaces; bool NeedAddintroFile = false; }; DatabaseManager::DatabaseManager(const QString &dbname, const QString &filedbname,QObject *parent) : QObject(parent) { priv = new DatabaseManagerPrivate(this, dbname,filedbname); if(priv->NeedAddintroFile){ bool ok = addFileToSpace("/usr/share/peony-intelligent-data-management-service/智能空间使用手册.pdf",1,true); ok = addFileToSpace("/usr/share/peony-intelligent-data-management-service/智能空间引导手册.pdf",1,true); priv->NeedAddintroFile = false; } } DatabaseManager::~DatabaseManager() { priv->db.close(); priv->filedb.close(); delete priv; } QStringList DatabaseManager::getAllSpaceNames(QString *errorMessage) { if (!priv->spaces.isEmpty()) { return priv->spaces.values(); } QStringList l; QSqlQuery query(priv->db); query.prepare("SELECT Name FROM Spaces WHERE Active=true ORDER BY id ASC"); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return l; } else { while (query.next()) { l << query.value(0).toString(); } } return l; } bool DatabaseManager::createSpace(const QString &spaceName, const QStringList &conditions, QString *errorMessage, QString *resultName) { QSqlQuery query(priv->db); QString realSpaceName = spaceName; if (spaceName.isEmpty()) { auto currentSpaceNames = getAllSpaceNames(); int count = 1; while (true) { realSpaceName = tr("New Space %1").arg(count); if (currentSpaceNames.contains(realSpaceName)) { count++; } else { break; } } } query.prepare("INSERT INTO Spaces (Name, Conditions, Active,AutoUpdate,lastAddedFileCount, lastAddedFileTimeMsec) VALUES (:n, :c,true,true,0 ,0)"); query.bindValue(":n", realSpaceName); query.bindValue(":c", conditions.join('\n')); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } else { int newSpaceId = getSpaceIdByName(spaceName); priv->spaces.insert(newSpaceId, spaceName); if (resultName) { *resultName = realSpaceName; } return true; } } bool DatabaseManager::renameSpace(const QString ¤tSpaceName, const QString &newSpaceName, QString *errorMessage) { QSqlQuery query(priv->db); query.prepare("UPDATE Spaces SET Name=:n WHERE Name=:c"); query.bindValue(":c", currentSpaceName); query.bindValue(":n", newSpaceName); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } else { return true; } } bool DatabaseManager::updateSpaceStatusDescription(int id,quint64 count,quint64 time) { QSqlQuery query(priv->db); query.prepare("UPDATE Spaces SET lastAddedFileCount=:count ,lastAddedFileTimeMsec=:time WHERE id=:n"); query.bindValue(":count", count); query.bindValue(":time", time); query.bindValue(":n", id); bool ok = query.exec(); return ok; } QVector DatabaseManager::getSpaceStatusDescription(int id) { QSqlQuery query(priv->db); QVectorvec; query.prepare("SELECT lastAddedFileCount , lastAddedFileTimeMsec FROM Spaces WHERE id=:n"); query.bindValue(":n", id); bool ok = query.exec(); quint64 count = 0; quint64 msectime = 0; //QString queryStr = QString("SELECT lastAddedFileCount , lastAddedFileTimeMsec FROM Spaces WHERE id=64;"); if(ok) { while(query.next()) { count = query.value(0).toULongLong(); vec.push_back(count); msectime = query.value(1).toULongLong(); vec.push_back(msectime); } } return vec; } bool DatabaseManager::removeSpace(const QString &spaceName, QString *errorMessage) { QSqlQuery query(priv->db); query.prepare("UPDATE Spaces SET Name=:randomName,Active=false,AutoUpdate=false WHERE Name=:n"); QString randomString = createRandomString(64); query.bindValue(":randomName", randomString); query.bindValue(":n", spaceName); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } else { priv->spaces.remove(priv->spaces.key(spaceName)); return true; } } bool DatabaseManager::getSpacestatus(const QString &spaceName){ QString errorMessage; QSqlQuery query(priv->db); query.prepare("SELECT AutoUpdate FROM Spaces WHERE Name=:n"); query.bindValue(":n", spaceName); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), &errorMessage); return false; } else { if (query.next()) { bool status = query.value(0).toBool(); return status; } } return true; } bool DatabaseManager::updateSpacestatus(const QString &spaceName,bool status){ QString errorMessage; QSqlQuery query(priv->db); query.prepare("UPDATE Spaces SET AutoUpdate=:s WHERE Name=:n"); query.bindValue(":s", status); query.bindValue(":n", spaceName); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), &errorMessage); return false; } else { return true; } } QStringList DatabaseManager::getSpaceCondition(const QString &spaceName, QString *errorMessage) { QStringList l; QSqlQuery query(priv->db); query.prepare("SELECT Conditions from Spaces WHERE name=:n"); query.bindValue(":n", spaceName); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return l; } else { if (query.next()) { QString conditions = query.value(0).toString(); l = conditions.split('\n'); return l; } } return l; } bool DatabaseManager::changeSpaceConditions(const QString &spaceName, const QStringList &conditions, QString *errorMessage) { QSqlQuery query(priv->db); query.prepare("UPDATE Spaces SET Conditions=:c WHERE Name=:n"); query.bindValue(":c", conditions.join('\n')); query.bindValue(":n", spaceName); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } else { return true; } } bool DatabaseManager::addFileToSpace(const QString &filePath, const QString &spaceName, bool isUserAdded, QString *errorMessage) { int spaceId = getSpaceIdByName(spaceName); return addFileToSpace(filePath, spaceId, isUserAdded, errorMessage); } bool DatabaseManager::addFileToSpace(const QString &filePath, int spaceId, bool isUserAdded, QString *errorMessage) { // 更新或者插入文件空间信息 // fixme: 使用文件id信息进行空间管理 QString targetTable = isUserAdded? "UserAddedFiles": "AutoAddedFiles"; QSqlQuery query(priv->filedb); QString cmd = QString("SELECT Path,id,SpaceIds FROM %1 WHERE Path=:path").arg(targetTable); query.prepare(cmd); query.bindValue(":path", filePath); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } else { QString path; QString oldSpaces; int id; if (query.next()) { // 已经加入至少一个空间,更新信息 path = query.value("path").toString(); oldSpaces = query.value("SpaceIds").toString(); id = query.value("id").toInt(); QString spaceName = priv->spaces.value(spaceId); QStringList spaces = oldSpaces.split('\n'); if (spaces.contains(QString::number(spaceId))) { setErrorString(tr("File %1 has areally been added to %2").arg(filePath).arg(spaceName), errorMessage); return false; } else { spaces << QString::number(spaceId); QString targetString = spaces.join('\n'); QSqlQuery updateQuery(priv->filedb); updateQuery.prepare(QString("UPDATE %1 SET SpaceIds=:SpaceIds WHERE Path=:path").arg(targetTable)); updateQuery.bindValue(":SpaceIds", targetString); updateQuery.bindValue(":path", filePath); bool ok = updateQuery.exec(); if (!ok) { setErrorString(updateQuery.lastError().text(), errorMessage); return false; } else { return true; } } } else { // 文件未在任何空间中,添加数据 int spaceIdToBeAdded = spaceId; QSqlQuery insertQuery(priv->filedb); insertQuery.prepare(QString("INSERT INTO %1 (Path,SpaceIds) VALUES (:path,:space)").arg(targetTable)); insertQuery.bindValue(":path", filePath); insertQuery.bindValue(":space", QString::number(spaceIdToBeAdded)); bool ok = insertQuery.exec(); if (!ok) { setErrorString(insertQuery.lastError().text(), errorMessage); return false; } else { return true; } } } return false; } bool DatabaseManager::removeFileFromSpace(const QString &filePath, const QString &spaceName, bool isUserRemove, QString *errorMessage) { int spaceId = getSpaceIdByName(spaceName); if (spaceId < 0) { qCritical() << "invalid space id" << spaceId << "from" << spaceName; return false; } return removeFileFromSpace(filePath, spaceId, isUserRemove, errorMessage); } bool DatabaseManager::removeFileFromSpace(const QString &filePath, int spaceId, bool isUserRemove, QString *errorMessage) { // 更新文件空间信息,如果文件不存在,返回false // fixme: 使用文件id信息进行空间管理 QString targetTable = isUserRemove? "UserAddedFiles": "AutoAddedFiles"; QSqlQuery query(priv->filedb); query.prepare(QString("SELECT Path,id,SpaceIds FROM %1 WHERE Path=:path").arg(targetTable)); query.bindValue(":path", filePath); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } else { if (query.next()) { QString path; QString oldSpaces; int id; // 更新信息 path = query.value("path").toString(); oldSpaces = query.value("SpaceIds").toString(); id = query.value("id").toInt(); QStringList spaces = oldSpaces.split('\n'); spaces.removeAll(QString::number(spaceId)); QString targetString = spaces.join('\n'); if (spaces.isEmpty()) { // 如果删除此文件后没有数据则清空此记录 QSqlQuery updateQuery(priv->filedb); QString cmd = QString("DELETE FROM %1 WHERE Path=:path").arg(targetTable); updateQuery.prepare(cmd); updateQuery.bindValue(":path", filePath); bool ok = updateQuery.exec(); if (!ok) { setErrorString(updateQuery.lastError().text(), errorMessage); return false; } else { return true; } } else { QSqlQuery updateQuery(priv->filedb); QString cmd = QString("UPDATE %1 SET SpaceIds=:SpaceIds WHERE Path=:path").arg(targetTable); updateQuery.prepare(cmd); updateQuery.bindValue(":SpaceIds", targetString); updateQuery.bindValue(":path", filePath); bool ok = updateQuery.exec(); if (!ok) { setErrorString(updateQuery.lastError().text(), errorMessage); return false; } else { return true; } } } else { setErrorString(tr("File %1 doesn't exsit in any space.").arg(filePath), errorMessage); return false; } } return false; } bool DatabaseManager::removeFileFromAllSpace(const QString &filePath, QString *errorMessage) { QSqlQuery query(priv->filedb); query.prepare("UPDATE UserAddedFiles SET SpaceIds=NULL WHERE Path=:path"); query.bindValue(":path", filePath); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } query.prepare("UPDATE AutoAddedFiles SET SpaceIds=NULL WHERE Path=:path"); query.bindValue(":path", filePath); ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return false; } else { return true; } return false; } QStringList DatabaseManager::getAllFilesFromSpaces(bool isUserAdded, QString *errorMessage) { QStringList l; QString targetTable = isUserAdded? "UserAddedFiles": "AutoAddedFiles"; QSqlQuery query(priv->filedb); QString cmd = QString("SELECT Path FROM %1 WHERE SpaceIds IS NOT NULL").arg(targetTable); query.prepare(cmd); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return l; } else { while (query.next()) { l << query.value(0).toString(); } } return l; } QList > > DatabaseManager::getAllFilesWithSpaceIds(bool isUserAdded, QString *errorMessage) { QList>> l; QString targetTable = isUserAdded? "UserAddedFiles": "AutoAddedFiles"; QSqlQuery query(priv->filedb); QString cmd = QString("SELECT Path,SpaceIds FROM %1 WHERE SpaceIds IS NOT NULL").arg(targetTable); query.prepare(cmd); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return l; } else { while (query.next()) { auto path = query.value(0).toString(); QString spaceIds = query.value(1).toString(); if (spaceIds.isEmpty() || spaceIds == "") { qWarning() << "file" << path << "has no spaceIds value"; continue; } QStringList ids = spaceIds.split('\n'); QList idsList; for (QString idString : ids) { bool ok = false; int id = idString.toInt(&ok); if (!ok) { qWarning()<< QString("Parse file %3 space ids %2 failed, current string: %1.").arg(idString).arg(spaceIds).arg(path); } else { idsList << id; } } QPair> pathWithSpaceIds; pathWithSpaceIds.first = path; pathWithSpaceIds.second = idsList; l << pathWithSpaceIds; } } return l; } QMap DatabaseManager::getAllFilesFromSpacesBySpaceIds(bool isUserAdded, QString *errorMessage) { QMap l; QString targetTable = isUserAdded? "UserAddedFiles": "AutoAddedFiles"; QSqlQuery query(priv->filedb); QString cmd = QString("SELECT Path,SpaceIds FROM %1 WHERE SpaceIds IS NOT NULL").arg(targetTable); query.prepare(cmd); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return l; } else { while (query.next()) { auto path = query.value(0).toString(); QString spaceIds = query.value(1).toString(); QStringList ids = spaceIds.split('\n'); for (QString idString : ids) { bool ok = false; int id = idString.toInt(&ok); if (!ok) { qWarning()<< QString("Parse file %3 space ids %2 failed, current string: %1.").arg(idString).arg(spaceIds).arg(path); } else { if (l.keys().contains(id)) { QStringList newList = l.value(id); l.insert(id, newList<spaces.key(spaceName, -1); if (cachedId > 0) { return cachedId; } QSqlQuery query(priv->db); query.prepare("SELECT id FROM Spaces WHERE name=:name"); query.bindValue(":name", spaceName); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return -1; } else { if (query.next()) { int id = query.value(0).toInt(&ok); if (!ok) { setErrorString(tr("Failed to get space %1's id from query").arg(spaceName), errorMessage); return -1; } return id; } } return -1; } int DatabaseManager::getFileIdByPath(const QString &filePath, bool isUserAdded, QString *errorMessage) { // fixme: 使用缓存管理 QString targetTable = isUserAdded? "UserAddedFiles": "AutoAddedFiles"; QSqlQuery query(priv->filedb); // 表名不能作为绑定变量 //query.prepare("SELECT id FROM :table WHERE Path=:path"); //query.bindValue(":table", targetTable); QString cmd = QString("SELECT id FROM %1 WHERE Path=:path").arg(targetTable); query.prepare(cmd); query.bindValue(":path", filePath); bool ok = query.exec(); if (!ok) { setErrorString(query.lastError().text(), errorMessage); return -1; } else { if (query.next()) { int id = query.value(0).toInt(&ok); if (!ok) { setErrorString(tr("Failed to get %1's id from query").arg(filePath), errorMessage); return -1; } return id; } } return -1; } void DatabaseManager::setErrorString(const QString &errorMessage, QString *targetString) { if (targetString) *targetString = errorMessage; } peony-extensions/peony-intelligent-data-management-service/database/databasemanager.h0000664000175000017500000001025015156143275030177 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef DATABASEMANAGER_H #define DATABASEMANAGER_H #include #include class DatabaseManagerPrivate; class DatabaseManager : public QObject { Q_OBJECT public: explicit DatabaseManager(const QString &dbname = "peony-idm.db",const QString &filedbname = "peony-idm-file.db", QObject *parent = nullptr); ~DatabaseManager(); /*! * \brief getAllSpaceNames * \param errorMessage * \return all active space * \note removed spaces will not in this list, but still has a record and unique id in database. */ QStringList getAllSpaceNames(QString *errorMessage = nullptr); bool createSpace(const QString &spaceName, const QStringList &conditions, QString *errorMessage = nullptr, QString *resultName = nullptr); bool renameSpace(const QString ¤tSpaceName, const QString &newSpaceName, QString *errorMessage = nullptr); bool removeSpace(const QString &spaceName, QString *errorMessage = nullptr); bool updateSpaceStatusDescription(int id,quint64 count,quint64 time); QVector getSpaceStatusDescription(int id); QStringList getSpaceCondition(const QString &spaceName, QString *errorMessage = nullptr); bool changeSpaceConditions(const QString &spaceName, const QStringList &conditions, QString *errorMessage = nullptr); bool addFileToSpace(const QString &filePath, const QString &spaceName, bool isUserAdded = false, QString *errorMessage = nullptr); // deprecated bool addFileToSpace(const QString &filePath, int spaceId, bool isUserAdded = false, QString *errorMessage = nullptr); bool removeFileFromSpace(const QString &filePath, const QString &spaceName, bool isUserRemove = false, QString *errorMessage = nullptr); // deprecated bool removeFileFromSpace(const QString &filePath, int spaceId, bool isUserRemove = false, QString *errorMessage = nullptr); bool removeFileFromAllSpace(const QString &filePath, QString *errorMessage = nullptr); bool getSpacestatus(const QString &spaceName); bool updateSpacestatus(const QString &spaceName ,bool status); QStringList getAllFilesFromSpaces(bool isUserAdded = false, QString *errorMessage = nullptr); // todo: a batch function for improve database performance. // 当前测试环境中4000数据同时加入2个表格耗时已经达到10s级别,需要优化。 // 文件表格的批处理可能需要借助FileInfoManager实现,对于已经存在的文件和新文件做不同的处理 /*! * \brief getAllFilesWithSpaceIds * \param isUserAdded * \param errorMessage * \return all files in all spaces with space id info. */ QList>> getAllFilesWithSpaceIds(bool isUserAdded = false, QString *errorMessage = nullptr); /*! * \brief getAllFilesFromSpacesBySpaceIds * \param isUserAdded * \param errorMessage * \return a map with spaces and files in those spaces, the map key is space id in database. * \sa getSpaceIdByName() */ QMap getAllFilesFromSpacesBySpaceIds(bool isUserAdded = false, QString *errorMessage = nullptr); int getSpaceIdByName(const QString &spaceName, QString *errorMessage = nullptr); int getFileIdByPath(const QString &filePath, bool isUserAdded, QString *errorMessage = nullptr); Q_SIGNALS: private: void setErrorString(const QString &errorMessage, QString *targetString); DatabaseManagerPrivate *priv; }; #endif // DATABASEMANAGER_H ././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service_kk.tspeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000002371015156143275033240 0ustar fengfeng AISearchTask Failed to create ai search session. ai ٸزدەۋ جوبالاۋدى قۇرۋ جەڭىلىس قالدى. Search task cancelled. ٸزدەۋ مىندەتى كۇشىنەن قالدىرىلدى. Condition File name contains "%1" حۇجات مى «٪1» نى ٶز ىشىنە الادٸ File name without "%1" «٪1» بولماعان حۇجات مى File type contains "%1" حۇجات تيپى «٪1» نى ٶز ىشىنە الادٸ By suffix قوسىمشاسى Folder حۇجات قىسقىش Plain Text تۇنىق تەكىسىت WPS Document WPS حۇجاتى PDF Image راسىم Video سىن Audio ۇن File type contains %1 حۇجات تيپى ٪1 نى ٶز ىشىنە الادٸ All جالپىسى Empty(0K) قۇرعاق(0K) Tiny(0-16K) كىشكەنەسىنە (0-16K) Small(16K-1M) كشكەنە (16K-1M) Medium(1M-128M) ورتاشا (1M-128M) Big(128M-1G) ۇلكەن(128M-1G) Large(1-4G) ۇلكەن(1-4G) Great(>4G) كەرەمەت(>4G) File size contains %1 حۇجات ۇلكەندىگى ٪1 نى ٶز ىشىنە الادٸ File size without %1 حۇجات ۇلكەندىگى ٪1 جوق File content match %1 حۇجات مازمۇنى ٪1 بۇرشاق كەلەدى File location contains "%1" حۇجات ورنى «٪1» نى ٶز ىشىنە الادٸ File location without "%1" حۇجات ورنى «٪1» جوق Later than "%1" «٪1» دان كەيىن Earlier than "%1" «٪1» دان بۇرٸنعٸ DatabaseManager New Space %1 جاڭا بوستٸق ٪1 File %1 has areally been added to %2 حۇجات ٪1 راسىندا ٪2 گە قوسىلدى File %1 doesn't exsit in any space. حۇجات ٪1 ەشقانداي بوستققا شىقپايدى. Failed to get space %1's id from query سۈرۈشتۈرۈشتىن بوستٸق ٪1 نىڭ ID عا يە بولعالٸ بولمادى Failed to get %1's id from query سۈرۈشتۈرۈشتىن ٪1 نىڭ ازاماتتىق كۋالىك الۋ جەڭىلىپ قالدى ModelController Failed to rename space %1 to %2, %2 is existed. بوستقتٸڭ اتاعىن ٪1 گە ٪2 ورىنداپ وزگەرتۋ جەڭىلىس قالدى، ٪2 ساقتالعان. Failed to remove space %1, error message: %2 ٪1 بوستقتٸ شىعارىپ وتۋ جەڭىلىس قالدى، قاتەلىك حابارى: ٪2 New Space %1 جاڭا بوستٸق ٪1 Failed to updatestatus space %1, error message: %2 ٪1 بەينە بوستٸقتٸ جاڭالاۋ جەڭىلىس قالدى، قاتەلىك حابارى: ٪2 QObject %1 %2 Intelligent Data Manager Introduction وي قابٸلەتتٸ ساندىق مالىمەت باسقارۋشنىڭ تونۇشتۇرۇشى peony-intelligent-data-management-service پيون وي قابٸلەتتٸ ساندىق مالىمەت باسقارۋ قىزىمەت وتەۋى exit شەگىنۋ UKUI::IDM::FileInfoJob Create Date: %1 چيسىلا قۇرۋ: ٪1 Open Date: %1 اشىلعان ۋاقىتى: ٪1 peony-extensions/peony-intelligent-data-management-service/file/0000775000175000017500000000000015156143275024104 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/file/fileinfojob.cpp0000664000175000017500000005307415156143275027107 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "fileinfojob.h" #include "fileinfo_p.h" #include "fileinfomanager.h" #include "condition/condition.h" #include "ai/aiconditionshelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #undef signals #include #define PEONY_FILE_TIME_OPENED "metadata::peony-time-opened" #define PEONY_FILE_OPENED_COUNT "metadata::peony-opened-count" namespace UKUI { namespace IDM { class FileInfoJobPrivate{ public: FileInfoJobPrivate() { cancelled = false; canFetchInfos = false; hasCondition = false; } ~FileInfoJobPrivate() { if (conditionsHelper) { delete conditionsHelper; } } QList> infos; QSet> resultInfos; std::atomic_bool cancelled; std::atomic_bool canFetchInfos; bool hasCondition; bool addWatchByDefault; bool handleContentCondition = false; QList> andConditions; QList> orConditions; QStringList labels; AIConditionsHelper *conditionsHelper = nullptr; int spaceId = -1; }; } } using namespace UKUI::IDM; FileInfoJob *FileInfoJob::fromUris(const QStringList &uris, bool addWatchByDefault) { return new FileInfoJob(uris, true, addWatchByDefault); } FileInfoJob *FileInfoJob::fromPaths(const QStringList &paths, bool addWatchByDefault) { return new FileInfoJob(paths, false, addWatchByDefault); } FileInfoJob *FileInfoJob::fromInfos(const QList > &infos, bool addWatchByDefault) { auto job = new FileInfoJob; job->priv->addWatchByDefault = addWatchByDefault; job->priv->infos = infos; return job; } bool FileInfoJob::queryInfoSyncWithConditions(QSharedPointer &info, const QList> &andConditions, const QList> &orConditions) { g_autoptr (GFile) gfile = g_file_new_for_uri(info->priv->uri.toUtf8().constData()); g_autoptr (GFileInfo) gfileinfo = g_file_query_info(gfile, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," G_FILE_ATTRIBUTE_STANDARD_ICON"," G_FILE_ATTRIBUTE_STANDARD_TARGET_URI"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE"," G_FILE_ATTRIBUTE_STANDARD_SIZE"," G_FILE_ATTRIBUTE_TIME_ACCESS"," G_FILE_ATTRIBUTE_TIME_MODIFIED"," G_FILE_ATTRIBUTE_TIME_CREATED, G_FILE_QUERY_INFO_NONE, nullptr, nullptr); if (!gfileinfo) { return false; } else { updateInfoWithGFileInfo(info, gfile, gfileinfo); if (orConditions.isEmpty() && andConditions.isEmpty()) return true; else return infoMatchCondtions(info, andConditions, orConditions, false); } } FileInfoJob::~FileInfoJob() { delete priv; } void FileInfoJob::start() { // fixme: not use QThreadPool::globalInstance() QThreadPool::globalInstance()->start(this); } void FileInfoJob::cancel() { priv->cancelled = true; } void FileInfoJob::setConditions(const QList > &conditions) { priv->andConditions.clear(); priv->orConditions.clear(); if (conditions.isEmpty()) { priv->hasCondition = false; return; } for (auto condition : conditions) { if (condition->isAndCondition()) { priv->andConditions << condition; } else { priv->orConditions << condition; } } priv->hasCondition = true; if (!priv->conditionsHelper) { priv->conditionsHelper = new AIConditionsHelper; } } void FileInfoJob::setSpaceId(int spaceId) { priv->spaceId = spaceId; } void FileInfoJob::setLabels(const QStringList &labels) { if (labels.isEmpty()) { priv->labels.clear(); return; } priv->labels = labels; if (!priv->conditionsHelper) { priv->conditionsHelper = new AIConditionsHelper; } } void FileInfoJob::setHandleContentCondition(bool handleContentCondition) { priv->handleContentCondition = handleContentCondition; } void FileInfoJob::run() { priv->canFetchInfos = false; Q_EMIT started(); while (!priv->infos.isEmpty()) { auto info = priv->infos.takeFirst(); GError *error = nullptr; g_autoptr (GFile) gfile = g_file_new_for_uri(info->priv->uri.toUtf8().constData()); g_autoptr (GFileInfo) gfileinfo = g_file_query_info(gfile, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," G_FILE_ATTRIBUTE_STANDARD_ICON"," G_FILE_ATTRIBUTE_STANDARD_TARGET_URI"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE"," G_FILE_ATTRIBUTE_STANDARD_SIZE"," G_FILE_ATTRIBUTE_TIME_ACCESS"," G_FILE_ATTRIBUTE_TIME_MODIFIED"," G_FILE_ATTRIBUTE_TIME_CREATED"," G_FILE_ATTRIBUTE_RECENT_MODIFIED"," PEONY_FILE_OPENED_COUNT"," PEONY_FILE_TIME_OPENED"," "metadata::peony-file-label-ids", G_FILE_QUERY_INFO_NONE, nullptr, &error); if (error) { QString msg = error->message; g_error_free (error); { QMutexLocker l(&info->priv->mutex); info->priv->state = FileInfoPrivate::Invalid; } Q_EMIT errored(msg); continue; } if (priv->cancelled) { priv->infos.clear(); priv->resultInfos.clear(); Q_EMIT cancelled(); return; } updateInfoWithGFileInfo(info, gfile, gfileinfo); bool match = true; if (priv->hasCondition) { match = infoMatchCondtions(info, priv->andConditions, priv->orConditions, priv->handleContentCondition); } if (!priv->labels.isEmpty()) { if (match) { // 如果基本条件不匹配则不做标签匹配 match = infoMatchLabels(info, priv->labels); } } if (match) { if (priv->addWatchByDefault) { FileInfoManager::globalInstance()->addFileToWatcher(info->uri()); } priv->resultInfos << info; Q_EMIT infoUpdated(info); } } priv->canFetchInfos = true; Q_EMIT finished(); } QList > FileInfoJob::getInfos() { if (priv->canFetchInfos) { return priv->resultInfos.values(); } return QList>(); } void FileInfoJob::handleCancelWithSpaceId(int spaceId) { if (spaceId == priv->spaceId) { cancel(); } } void FileInfoJob::updateInfoWithGFileInfo(QSharedPointer &info, GFile *gfile, GFileInfo *gfileinfo) { g_autofree gchar *path = g_file_get_path(gfile); QString displayName = g_file_info_get_attribute_string(gfileinfo, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); GIcon *gicon = g_file_info_get_icon(gfileinfo); QString iconName = Peony::FileUtils::getIconStringFromGIconThreadSafety(gicon); guint64 timeAcessSec = g_file_info_get_attribute_uint64(gfileinfo, G_FILE_ATTRIBUTE_TIME_ACCESS); guint64 timeModifiedSec = g_file_info_get_attribute_uint64(gfileinfo, G_FILE_ATTRIBUTE_TIME_MODIFIED); guint64 timeCreatedSec = g_file_info_get_attribute_uint64(gfileinfo, G_FILE_ATTRIBUTE_TIME_CREATED); if (g_file_info_has_attribute(gfileinfo, PEONY_FILE_TIME_OPENED)) { QString peonyTimeOpenedString = g_file_info_get_attribute_string(gfileinfo, PEONY_FILE_TIME_OPENED); timeAcessSec = peonyTimeOpenedString.toULongLong(); } int openCount = 0; if (g_file_info_has_attribute(gfileinfo, PEONY_FILE_OPENED_COUNT)) { QString openCountString = g_file_info_get_attribute_string(gfileinfo, PEONY_FILE_OPENED_COUNT); openCount = openCountString.toULongLong(); } guint64 timeAcessMsec = timeAcessSec * 1000; guint64 timeModifiedMsec = timeModifiedSec * 1000; guint64 timeCreatedMsec = timeCreatedSec * 1000; gint64 timeRecentModified = g_file_info_get_attribute_int64(gfileinfo, G_FILE_ATTRIBUTE_RECENT_MODIFIED); const gchar *ctype = g_file_info_get_content_type(gfileinfo); QString contentType = ctype; g_autofree gchar *ftype = g_content_type_get_description(ctype); QString fileType = ftype; g_autofree gchar *formatsize = nullptr; guint64 size = g_file_info_get_size(gfileinfo); if (contentType != "inode/directory") { formatsize = g_format_size(size); } if (!path) { if (g_file_info_has_attribute(gfileinfo, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI)) { QUrl url(g_file_info_get_attribute_as_string(gfileinfo, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI)); path = g_strdup(url.toLocalFile().toUtf8().constData()); } } QString dateString; QString createString = Peony::GlobalSettings::getInstance()->transToSystemTimeFormat(timeCreatedSec); createString = tr("Create Date: %1").arg(createString); QString accessString = Peony::GlobalSettings::getInstance()->transToSystemTimeFormat(timeAcessSec); accessString = tr("Open Date: %1").arg(accessString); QString modifiyString = Peony::GlobalSettings::getInstance()->transToSystemTimeFormat(timeModifiedSec); //modifiyString = tr("Modify Date: %1").arg(modifiyString); if (timeAcessSec > timeModifiedSec) { // 显示打开时间 dateString = accessString; } else if (timeModifiedSec > timeCreatedSec) { // fixme: 由于WPS等文件修改时是以临时文件移动方式进行原文件覆盖,所以创建时间和修改时间在保存后是一致的,这里先显示修改时间,后续需要结合版本判断的信息来处理此处显示的内容 // 显示修改时间 dateString = modifiyString; } else { // 显示创建时间 dateString = createString; } QString parentPath = path; parentPath.truncate(parentPath.lastIndexOf('/')); // 标记信息 auto metaInfo = Peony::FileMetaInfo::fromGFileInfo(info->priv->uri, gfileinfo); auto labelStrings = metaInfo->getMetaInfoStringList("peony-file-label-ids"); QList labelIds; QList labelColors; // fixme: 线程安全问题 auto labelModel = FileLabelModel::getGlobalModel(); for (QString labelString : labelStrings) { int labelId = labelString.toInt(); auto item = labelModel->itemFromId(labelId); if (item) { labelColors << item->color(); labelIds << labelId; } } QString version = "test"; // thread safety { QMutexLocker l(&info->priv->mutex); info->priv->path = path; info->priv->parentPath = parentPath; info->priv->name = displayName; info->priv->iconName = iconName; info->priv->timeAccessMsec = timeAcessMsec; info->priv->timeModifiedMsec = timeModifiedMsec; info->priv->timeCreatedMsec = timeCreatedMsec; info->priv->openDateString = accessString; info->priv->modifyDateString = modifiyString; info->priv->createDateString = createString; info->priv->dateString = modifiyString; info->priv->version = version; info->priv->mimeType = contentType; info->priv->fileType = fileType; info->priv->size = size; info->priv->sizeFormat = formatsize; info->priv->labelIds = labelIds; info->priv->labelColors = labelColors; info->priv->timeRecentModified = timeRecentModified; info->priv->openCount = openCount; info->priv->state = FileInfoPrivate::State::Valid; //info->priv->ischecked = false; } } bool FileInfoJob::infoMatchCondtions(QSharedPointer &info, const QList > &andConditions, const QList > &orConditions, bool handleContentCondition) { QString displayName = info->displayName(); quint64 size = info->size(); QString contentType = info->mimeType(); quint64 timeModifiedMsec = info->timeModifiedMsec(); QString path = info->path(); bool match = true; // 如果符合所有条件, 则match最终会为true, 如果没有或条件则match初始值为true, 否则为false match = orConditions.count() == 0; // 或条件, 只需要满足其中之一即可通过 for (auto condition : orConditions) { // 如果存在条件匹配则跳过, 并且设置match为true switch (condition->type()) { case Condition::FileName: { match = condition->isFileNameMatchedWithKeywords(displayName); break; } case Condition::FileContent: { // 或条件的文件内容匹配目前仅用于普通搜索,这里可以跳过 // if (handleContentCondition) { QString contentDecription = condition->getFileContentContent(); AIConditionsHelper helper; match = helper.isFileMatchedWithDescriptionCondition(info->path(), contentDecription); //} break; } case Condition::FileSize: { match = condition->isFileSizeMatchedWithSize(size); break; } case Condition::FileType: { int subType = condition->getFileTypeSelectionType(); if (subType < 0) { match = condition->isFileTypeMatchedWithSuffixes(displayName); } else { match = condition->isFileTypeMatchedWithSubType(subType, displayName, contentType); } break; } case Condition::DateRange: { match = condition->isFileMatchedWithDate(0, timeModifiedMsec); break; } case Condition::SearchLocation: { match = condition->isFileMatchedWithSearchLocation(path); break; } default: { match = false; break; } } // 如果有一个条件匹配, 则跳出循环 if (match) { break; } } if (!match) { return match; } else { // 如果存在不匹配的且条件, 则跳过此次流程 for (auto condition : andConditions) { // 如果存在条件不匹配则跳过, 并且设置match为false switch (condition->type()) { case Condition::FileName: { match = condition->isFileNameMatchedWithKeywords(displayName); break; } case Condition::FileContent: { // 与条件的文件内容搜索目前结合语义搜索使用,这里需要区分是语义搜索还是动态添加场景 //if (handleContentCondition) { QString contentDecription = condition->getFileContentContent(); AIConditionsHelper helper; match = helper.isFileMatchedWithDescriptionCondition(info->path(), contentDecription); //} break; } case Condition::FileType: { int subType = condition->getFileTypeSelectionType(); if (subType < 0) { match = condition->isFileTypeMatchedWithSuffixes(displayName); } else { match = condition->isFileTypeMatchedWithSubType(subType, displayName, contentType); } break; } case Condition::FileSize: { match = condition->isFileSizeMatchedWithSize(size); break; } case Condition::SearchLocation: { match = condition->isFileMatchedWithSearchLocation(path); break; } case Condition::DateRange: { match = condition->isFileMatchedWithDate(0, timeModifiedMsec); break; } default: match = true; break; } // 存在不匹配, 则跳出循环 if (!match) { break; } } } return match; } bool FileInfoJob::infoMatchLabels(QSharedPointer &info, const QStringList &labels) { QUrl url = info->uri(); QString filePath = url.toLocalFile(); return priv->conditionsHelper->isFileMatchedWithLables(filePath, labels); } bool FileInfoJob::infoMatchCondtions(const QFileInfo &fileinfo, const QList> &andConditions, const QList > &orConditions) { QString displayName = fileinfo.fileName(); QString path = fileinfo.absoluteFilePath(); quint64 size = fileinfo.size(); QString contentType; if (fileinfo.isDir()) { contentType = "inode/directory"; } else { //QMimeDatabase db; //QMimeType mimeType = db.mimeTypeForFile(fileinfo.absoluteFilePath(), QMimeDatabase::MatchExtension); // fixme: 搜索根目录匹配时调用此方法内存会飙升 g_autofree gchar *contenttype = g_content_type_guess(path.toUtf8().constData(), nullptr, size, nullptr); contentType = contenttype; } quint64 timeModifiedMsec = fileinfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch(); QSharedPointer info = QSharedPointer(new FileInfo); info->priv->name = displayName; info->priv->size = size; info->priv->mimeType = contentType; info->priv->timeModifiedMsec = timeModifiedMsec; info->priv->path = path; return infoMatchCondtions(info, andConditions, orConditions, false); } FileInfoJob::FileInfoJob(const QStringList &args, bool isUriFormat, bool addWatchByDefault, QObject *parent) : QObject(parent), QRunnable() { setAutoDelete(false); priv = new FileInfoJobPrivate; priv->addWatchByDefault = addWatchByDefault; if (isUriFormat) { for (auto arg : args) { priv->infos.append(FileInfo::fromUri(arg)); } } else { for (auto arg : args) { priv->infos.append(FileInfo::fromPath(arg)); } } } void FileInfoJob::updateFileInfoDataformat(QSharedPointer &info) { GError *error = nullptr; g_autoptr (GFile) gfile = g_file_new_for_uri(info->priv->uri.toUtf8().constData()); g_autoptr (GFileInfo) gfileinfo = g_file_query_info(gfile, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," G_FILE_ATTRIBUTE_STANDARD_ICON"," G_FILE_ATTRIBUTE_STANDARD_TARGET_URI"," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE"," G_FILE_ATTRIBUTE_STANDARD_SIZE"," G_FILE_ATTRIBUTE_TIME_ACCESS"," G_FILE_ATTRIBUTE_TIME_MODIFIED"," G_FILE_ATTRIBUTE_TIME_CREATED"," G_FILE_ATTRIBUTE_RECENT_MODIFIED"," PEONY_FILE_OPENED_COUNT"," PEONY_FILE_TIME_OPENED"," "metadata::peony-file-label-ids", G_FILE_QUERY_INFO_NONE, nullptr, &error); if (error) { QString msg = error->message; g_error_free (error); { QMutexLocker l(&info->priv->mutex); info->priv->state = FileInfoPrivate::Invalid; } return; } updateInfoWithGFileInfo(info, gfile, gfileinfo); } FileInfoJob::FileInfoJob() { setAutoDelete(false); priv = new FileInfoJobPrivate; priv->addWatchByDefault = true; } peony-extensions/peony-intelligent-data-management-service/file/fileinfojob.h0000664000175000017500000000634315156143275026551 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef FILEINFOJOB_H #define FILEINFOJOB_H #include #include #include "file/fileinfo.h" #include class Condition; class QFileInfo; typedef struct _GFile GFile; /* Dummy typedef */ typedef struct _GFileInfo GFileInfo; namespace UKUI { namespace IDM { class FileInfo; class FileInfoJobPrivate; class FileInfoJob : public QObject, public QRunnable { Q_OBJECT public: static FileInfoJob *fromUris(const QStringList &uris, bool addWatchByDefault = true); static FileInfoJob *fromPaths(const QStringList &paths, bool addWatchByDefault = true); static FileInfoJob *fromInfos(const QList> &infos, bool addWatchByDefault = true); static bool infoMatchCondtions(const QFileInfo &fileinfo, const QList > &andConditions, const QList > &orConditions); static bool queryInfoSyncWithConditions(QSharedPointer &info, const QList> &andConditions, const QList> &orConditions); ~FileInfoJob(); void start(); void cancel(); /*! * \brief setConditions * 设置搜索过滤条件 * \param conditions */ void setConditions(const QList> &conditions); void setSpaceId(int spaceId); void setLabels(const QStringList &labels); void setHandleContentCondition(bool handleContentCondition); void run() override; QList> getInfos(); void updateFileInfoDataformat(QSharedPointer &info); Q_SIGNALS: void started(); void finished(); void cancelled(); void errored(const QString &errorMessage); void infoUpdated(const QSharedPointer &info); public Q_SLOTS: void handleCancelWithSpaceId(int spaceId); private: static void updateInfoWithGFileInfo(QSharedPointer &info, GFile *gfile, GFileInfo *gfileinfo); static bool infoMatchCondtions(QSharedPointer &info, const QList > &andConditions, const QList > &orConditions, bool handleContentCondition); bool infoMatchLabels(QSharedPointer &info, const QStringList &labels); explicit FileInfoJob(const QStringList &args, bool isUriFormat, bool addWatchByDefault = true, QObject *parent = nullptr); FileInfoJob(); FileInfoJobPrivate *priv; }; #endif // FILEINFOJOB_H } } peony-extensions/peony-intelligent-data-management-service/file/fileinfomanager.h0000664000175000017500000000431015156143275027401 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef FILEINFOMANAGER_H #define FILEINFOMANAGER_H #include #include namespace UKUI { namespace IDM { class FileInfoManagerPrivate; class FileInfoManager : public QObject { Q_OBJECT public: enum FileEventType { Unknown, FileCreated, FileModified, FileDeleted, FileMoveOut, FileMoveIn, FileMoved, FileAccessed, FileOpened, FileClosed, FileAttrChanged, FileIgnoredAutomatic, FileUnmount, UserAddFile, UserRemoveFile, Other }; Q_ENUM(FileEventType) static FileInfoManager *globalInstance(); void addFileToWatcher(const QString &url, bool *ok = nullptr); QStringList addFilesToWatcher(const QStringList &urls); void removeFileFromWatcher(const QString &url, bool *ok = nullptr); QStringList removeFilesFromWatcher(const QStringList &urls); Q_SIGNALS: void fileChanged(FileEventType type, const QString &arg1, const QString arg2); void remoteFileEvent(int eventType, const QString &arg1, const QString arg2); private: FileInfoManagerPrivate *priv; explicit FileInfoManager(QObject *parent = nullptr); ~FileInfoManager(); friend class FileInfo; QSharedPointer getInfoByUri(const QString &uri); void insertInfo(QSharedPointer info); void removeInfoPtrByUri(const QString &uri); }; } } #endif // FILEINFOMANAGER_H peony-extensions/peony-intelligent-data-management-service/file/fileinfo_p.h0000664000175000017500000000320315156143275026365 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef FILEINFO_P_H #define FILEINFO_P_H #include #include namespace UKUI { namespace IDM { class FileInfoPrivate { public: enum State { Invalid, Valid }; FileInfoPrivate() {} State state = Invalid; QString uri; QString path; QString parentPath; QString name; QString iconName; QString createDateString; QString modifyDateString; QString openDateString; QString dateString; quint64 timeAccessMsec = -1; quint64 timeModifiedMsec = -1; quint64 timeCreatedMsec = -1; qint64 timeRecentModified = -1; QString version; QString mimeType; QString fileType; quint64 size; QString sizeFormat; QList labelIds; QList labelColors; quint64 openCount = 0; bool ischecked = false; QMutex mutex; }; } } #endif // FILEINFO_P_H peony-extensions/peony-intelligent-data-management-service/file/fileinfo.h0000664000175000017500000000411015156143275026044 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef FILEINFO_H #define FILEINFO_H #include #include namespace UKUI { namespace IDM { class FileInfoPrivate; class FileInfo { friend class FileInfoJob; public: static QSharedPointer fromPath(const QString &filePath, bool addWatch = false); static QSharedPointer fromUri(const QString &uri, bool addWatch = false); static QSharedPointer fromUrl(const QUrl &url, bool addWatch = false); bool isValid() const; QString uri() const; QString path() const; QString parentPath() const; QString displayName() const; QString iconName() const; QString createDateString() const; QString modifyDateString() const; QString openDateString() const; QString dateString() const; QString version() const; QString mimeType() const; QString fileType() const; quint64 timeModifiedMsec() const; quint64 timeAccessMsec() const; qint64 timeRecentModified() const; quint64 size() const; QString sizeFormat() const; quint64 openCount() const; QList labelColors() const; void setChecked(bool flag); bool isChecked() const; ~FileInfo(); private: FileInfo(); QSharedPointer getInfoByUri(const QString &uri); FileInfoPrivate *priv; }; } } #endif // FILEINFO_H peony-extensions/peony-intelligent-data-management-service/file/qtremoteobject/0000775000175000017500000000000015156143275027133 5ustar fengfeng././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/file/qtremoteobject/fileeventhandlerimpl.hpeony-extensions/peony-intelligent-data-management-service/file/qtremoteobject/fileeventhandlerimpl.0000664000175000017500000000240315156143275033334 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef FILEEVENTHANDLERIMPL_H #define FILEEVENTHANDLERIMPL_H #include #include "rep_fileeventhandler_source.h" class FileEventHandlerImpl : public FileEventHandlerSource { Q_OBJECT public: explicit FileEventHandlerImpl(QObject *parent = nullptr); Q_SIGNALS: void reciveClientFileEvent(int eventType, const QString &arg1, const QString &arg2); public Q_SLOTS: virtual void handleFileEvent(int eventType, QString arg1, QString arg2); }; #endif // FILEEVENTHANDLERIMPL_H ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/file/qtremoteobject/fileeventhandlerimpl.cpppeony-extensions/peony-intelligent-data-management-service/file/qtremoteobject/fileeventhandlerimpl.0000664000175000017500000000220215156143275033331 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "fileeventhandlerimpl.h" #include FileEventHandlerImpl::FileEventHandlerImpl(QObject *parent) : FileEventHandlerSource(parent) { } void FileEventHandlerImpl::handleFileEvent(int eventType, QString arg1, QString arg2) { qDebug() << "server handle file event" << eventType << arg1 << arg2; Q_EMIT reciveClientFileEvent(eventType,arg1,arg2); } peony-extensions/peony-intelligent-data-management-service/file/fileinfomanager.cpp0000664000175000017500000001663715156143275027753 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "fileinfomanager.h" #include "file/qtremoteobject/fileeventhandlerimpl.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace UKUI { namespace IDM { class FileInfoManagerPrivate { public: FileInfoManagerPrivate(); ~FileInfoManagerPrivate(); QHash> hash; KYSDK_FILEWATCHER::FileWatcher *watcher; QThread *watcherThread; QEventLoop *watcherLoop; QRemoteObjectHost *host; FileEventHandlerImpl *impl; QMutex mutex; QMutex watcherMutex; }; FileInfoManagerPrivate::FileInfoManagerPrivate() { watcher = new KYSDK_FILEWATCHER::FileWatcher; watcherThread = new QThread; watcherLoop = new QEventLoop; host = new QRemoteObjectHost; impl = new FileEventHandlerImpl; } FileInfoManagerPrivate::~FileInfoManagerPrivate() { watcherThread->quit(); watcherThread->wait(); host->disableRemoting(impl); watcherLoop->quit(); watcher->clearWatchList(); watcher->disconnect(); delete watcher; delete watcherLoop; delete watcherThread; delete impl; delete host; } } } using namespace UKUI::IDM; static FileInfoManager *instance = nullptr; FileInfoManager *FileInfoManager::globalInstance() { if (!instance) { instance = new FileInfoManager; } return instance; } FileInfoManager::FileInfoManager(QObject *parent) : QObject(parent) { // 在主线程中初始化标记model FileLabelModel::getGlobalModel(); priv = new FileInfoManagerPrivate; priv->watcher->moveToThread(priv->watcherThread); priv->watcherLoop->moveToThread(priv->watcherThread); priv->host->moveToThread(priv->watcherThread); priv->impl->moveToThread(priv->watcherThread); connect(priv->watcherThread, &QThread::started, priv->watcherLoop, [=]{ QString displayEnv = (qgetenv("XDG_SESSION_TYPE") == "wayland") ? QLatin1String("WAYLAND_DISPLAY") : QLatin1String("DISPLAY"); QString display(qgetenv(displayEnv.toUtf8().data())); QUrl nodeUrl(QStringLiteral("local:ukui-idm-") + QString(qgetenv("USER")) + display); priv->host->setHostUrl(nodeUrl); priv->host->enableRemoting(priv->impl); priv->watcherLoop->exec(); }); connect(priv->impl, &FileEventHandlerImpl::reciveClientFileEvent, this, &FileInfoManager::remoteFileEvent); qRegisterMetaType("FileEventType"); // connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileAccessed, this, [=](QString name, QString parent){ // Q_EMIT fileChanged(FileAccessed, name, parent); // }); connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileAttrChanged, this, [=](QString name, QString parent){ Q_EMIT fileChanged(FileAttrChanged, name, parent); }); // connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileClosed, this, [=](QString name, QString parent){ // Q_EMIT fileChanged(FileClosed, name, parent); // }); // connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileCreated, this, [=](QString name, QString parent){ // Q_EMIT fileChanged(FileCreated, name, parent); // }); connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileDeleted, this, [=](QString name, QString parent){ Q_EMIT fileChanged(FileDeleted, name, parent); }); connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileIgnoredAutomatic, this, [=](QString name, QString parent){ Q_EMIT fileChanged(FileIgnoredAutomatic, name, parent); }); connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileModified, this, [=](QString name, QString parent){ Q_EMIT fileChanged(FileModified, name, parent); }); connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileMoveIn, this, [=](QString name, QString parent){ Q_EMIT fileChanged(FileMoveIn, name, parent); }); connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileMoveOut, this, [=](QString name, QString parent){ Q_EMIT fileChanged(FileMoveOut, name, parent); }); // connect(priv->watcher, &KYSDK_FILEWATCHER::FileWatcher::fileOpened, this, [=](QString name, QString parent){ // Q_EMIT fileChanged(FileOpened, name, parent); // }); priv->watcherThread->start(); } FileInfoManager::~FileInfoManager() { delete priv; } QSharedPointer FileInfoManager::getInfoByUri(const QString &uri) { QMutexLocker l(&priv->mutex); auto info = priv->hash.value(uri); return info; } void FileInfoManager::insertInfo(QSharedPointer info) { QMutexLocker l(&priv->mutex); priv->hash.insert(info->uri(), info); } void FileInfoManager::removeInfoPtrByUri(const QString &uri) { QMutexLocker l(&priv->mutex); priv->hash.remove(uri); } void FileInfoManager::addFileToWatcher(const QString &url, bool *ok) { if (url.isEmpty()) { if (ok) { *ok = false; } return; } QUrl tmp(url); QString path = tmp.toLocalFile(); if (!path.startsWith("/")) { if (ok) { *ok = false; return; } } QMutexLocker l(&priv->watcherMutex); int ret = priv->watcher->addWatchTarget(path, KYSDK_FILEWATCHER::FileWatcherType::PERIODIC, KYSDK_FILEWATCHER::FileWatcherAttribute::MODIFY|KYSDK_FILEWATCHER::FileWatcherAttribute::DELETE|KYSDK_FILEWATCHER::FileWatcherAttribute::ATTRIB); if (ok) { *ok = !ret; // 为0时成功 } } QStringList FileInfoManager::addFilesToWatcher(const QStringList &urls) { QStringList failedFiles; for (auto url : urls) { bool ok = false; addFileToWatcher(url, &ok); if (!ok && !url.isEmpty()) { failedFiles << url; } } return failedFiles; } void FileInfoManager::removeFileFromWatcher(const QString &url, bool *ok) { if (url.isEmpty()) { if (ok) { *ok = false; } return; } QUrl tmp(url); QString path = tmp.toLocalFile(); QMutexLocker l(&priv->watcherMutex); int ret = priv->watcher->removeWatchTarget(path); if (ok) { *ok = !ret; // 为0时成功 } } QStringList FileInfoManager::removeFilesFromWatcher(const QStringList &urls) { QStringList failedFiles; for (auto url : urls) { bool ok = false; removeFileFromWatcher(url, &ok); if (!ok && !url.isEmpty()) { failedFiles << url; } } return failedFiles; } peony-extensions/peony-intelligent-data-management-service/file/fileinfo.cpp0000664000175000017500000001161715156143275026411 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "fileinfo.h" #include "fileinfo_p.h" #include "fileinfomanager.h" #include #include #include #include using namespace UKUI::IDM; QSharedPointer FileInfo::fromPath(const QString &filePath, bool addWatch) { QUrl url = QUrl::fromLocalFile(filePath); return fromUri(url.toEncoded(), addWatch); } QSharedPointer FileInfo::fromUri(const QString &uri, bool addWatch) { QSharedPointer info = FileInfoManager::globalInstance()->getInfoByUri(uri); if (info.isNull()) { info = QSharedPointer(new FileInfo); info->priv->uri = uri; FileInfoManager::globalInstance()->insertInfo(info); if (addWatch) FileInfoManager::globalInstance()->addFileToWatcher(uri); } return info; } QSharedPointer FileInfo::fromUrl(const QUrl &url, bool addWatch) { return fromUri(url.toEncoded(), addWatch); } bool FileInfo::isValid() const { QMutexLocker(&priv->mutex); bool isValid = priv->state == FileInfoPrivate::State::Valid; return isValid; } QString FileInfo::uri() const { QMutexLocker(&priv->mutex); QString uri = priv->uri; return uri; } QString FileInfo::path() const { QMutexLocker(&priv->mutex); QString path = priv->path; return path; } QString FileInfo::parentPath() const { QMutexLocker(&priv->mutex); QString path = priv->parentPath; return path; } QString FileInfo::displayName() const { QMutexLocker(&priv->mutex); QString name = priv->name; return name; } QString FileInfo::iconName() const { QMutexLocker(&priv->mutex); QString iconName = priv->iconName; return iconName; } QString FileInfo::createDateString() const { QMutexLocker(&priv->mutex); QString string = priv->createDateString; return string; } QString FileInfo::modifyDateString() const { QMutexLocker(&priv->mutex); QString string = priv->modifyDateString; return string; } QString FileInfo::openDateString() const { QMutexLocker(&priv->mutex); QString string = priv->openDateString; return string; } QString FileInfo::dateString() const { QMutexLocker(&priv->mutex); QString string = priv->dateString; return string; } QString FileInfo::version() const { QMutexLocker(&priv->mutex); QString version = priv->version; return version; } QString FileInfo::mimeType() const { QMutexLocker(&priv->mutex); QString mimeType = priv->mimeType; return mimeType; } QString FileInfo::fileType() const { QMutexLocker(&priv->mutex); QString fileType = priv->fileType; return fileType; } quint64 FileInfo::timeModifiedMsec() const { QMutexLocker(&priv->mutex); quint64 timeModifiedMsec = priv->timeModifiedMsec; return timeModifiedMsec; } quint64 FileInfo::timeAccessMsec() const { QMutexLocker(&priv->mutex); quint64 timeAccessMsec = priv->timeAccessMsec; return timeAccessMsec; } qint64 FileInfo::timeRecentModified() const { QMutexLocker(&priv->mutex); qint64 timeRecentModified = priv->timeRecentModified; return timeRecentModified; } quint64 FileInfo::size() const { QMutexLocker(&priv->mutex); quint64 size = priv->size; return size; } QString FileInfo::sizeFormat() const { QMutexLocker(&priv->mutex); QString sizeFormat = priv->sizeFormat; return sizeFormat; } quint64 FileInfo::openCount() const { QMutexLocker(&priv->mutex); quint64 count = priv->openCount; return count; } QList FileInfo::labelColors() const { QList colors; QMutexLocker(&priv->mutex); colors = priv->labelColors; return colors; } void FileInfo::setChecked(bool flag) { QMutexLocker(&priv->mutex); priv->ischecked = flag; } bool FileInfo::isChecked() const { QMutexLocker(&priv->mutex); bool ischecked = priv->ischecked; return ischecked; } FileInfo::FileInfo() { priv = new FileInfoPrivate; } FileInfo::~FileInfo() { // TODO: 批量异步更新数据库 FileInfoManager::globalInstance()->removeFileFromWatcher(this->path()); FileInfoManager::globalInstance()->removeInfoPtrByUri(priv->uri); delete priv; } ././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service_ky.tspeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000002372415156143275033245 0ustar fengfeng AISearchTask Failed to create ai search session. ai ىزدۅۅ ماشعۇلاتىنى قۇرۇۇ جەڭىلۉۉ بولدۇ . Search task cancelled. ىزدۅۅ مىلدەتى ارعادان قالتىرىلدى. Condition File name contains "%1" ۅجۅت ناامى «٪1» نى ۅز ىچىنە الات File name without "%1" «٪1» بولبوعون ۅجۅت ناامى File type contains "%1" ۅجۅت تۉرۉ «٪1» نى ۅز ىچىنە الات By suffix قوشۇمچاسى Folder تىزىمدىك Plain Text تازا تەكىست WPS Document WPS ۅجۅتۉۉ PDF Image سۉرۅت Video ايىپ Audio دووش File type contains %1 ۅجۅت تۉرۉ ٪1 نى ۅز ىچىنە الات All باردىعى Empty(0K) كۅڭدۅي(0K) Tiny(0-16K) كىچىككىنە (0-16K) Small(16K-1M) كىچىك (16K-1M) Medium(1M-128M) ورتوچو (1M-128M) Big(128M-1G) چوڭ(128M-1G) Large(1-4G) چوڭ(1-4G) Great(>4G) قالتىس(>4G) File size contains %1 ۅجۅت چوڭدۇعۇ ٪1 نى ۅز ىچىنە الات File size without %1 ۅجۅت چوڭدۇعۇ ٪1 جوق File content match %1 ۅجۅت مازمۇنۇ ٪1 شاي گەلەت File location contains "%1" ۅجۅت وردۇ «٪1» نى ۅز ىچىنە الات File location without "%1" ۅجۅت وردۇ «٪1» جوق Later than "%1" «٪1» نان كىيىن Earlier than "%1" «٪1» نان مۇرۇنقۇ DatabaseManager New Space %1 جاڭى بوشتۇق ٪1 File %1 has areally been added to %2 ۅجۅت ٪1 چىنعى ٪2 گە قوشۇلدۇ File %1 doesn't exsit in any space. ۅجۅت ٪1 ارقانداي بوشتۇقتا چىقبايت. Failed to get space %1's id from query سۈرۈشتۈرۈشتىن بوشتۇق ٪1 نىڭ ID عا ەە بولعولۇ بولبودۇ Failed to get %1's id from query سۈرۈشتۈرۈشتىن ٪1 نىڭ كۉبۅلۉگۉنۉ الۇۇ جەڭىلۉۉ بولدۇ ModelController Failed to rename space %1 to %2, %2 is existed. بوشتۇقتۇن اتاعىن ٪1 گە ٪2 جاساپ ۅزگۅرتۉش جەڭىلۉۉ بولدۇ ، ٪2 باربولۇۇسۇ . Failed to remove space %1, error message: %2 ٪1 بوشتۇقتۇ چىعارۇۇ جەڭىلۉۉ بولدۇ ، قاتاالىق ۇچۇرۇ : ٪2 New Space %1 جاڭى بوشتۇق ٪1 Failed to updatestatus space %1, error message: %2 ٪1 ابالى بوشتۇعۇن جاڭىلوو جەڭىلۉۉ بولدۇ ، قاتاالىق ۇچۇرۇ : ٪2 QObject %1 %2 Intelligent Data Manager Introduction روحىي جۅندۅمدۉلۉك ساندۇۇ بايانداما باشقارۇۇچۇنۇن تونۇشتۇرۇشى peony-intelligent-data-management-service پئون روحىي جۅندۅمدۉلۉك ساندۇۇ بايانداما باشقارىش سان قاينارى exit جانىش ، قايتىش UKUI::IDM::FileInfoJob Create Date: %1 چىسلا قۇرۇۇ: ٪1 Open Date: %1 اچىلعان ۇباقتى: ٪1 peony-extensions/peony-intelligent-data-management-service/conf/0000775000175000017500000000000015156143275024112 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/conf/com.peony.idm.service0000664000175000017500000000024415156143275030153 0ustar fengfeng[D-BUS Service] Name=com.peony.idm.service Exec=/usr/bin/peony-intelligent-data-management-service SystemdService=peony-intelligent-data-management-service.service ././@LongLink0000644000000000000000000000016200000000000011602 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/conf/peony-intelligent-data-management-service.servicepeony-extensions/peony-intelligent-data-management-service/conf/peony-intelligent-data-management-se0000664000175000017500000000050015156143275033124 0ustar fengfeng[Unit] Description=Peony Intelligent Space Management Service PartOf=xdg-desktop-autostart.target After=xdg-desktop-autostart.target [Service] ExecStart=/usr/bin/peony-intelligent-data-management-service Restart=on-failure StandardOutput=journal StandardError=journal [Install] WantedBy=xdg-desktop-autostart.target peony-extensions/peony-intelligent-data-management-service/ai/0000775000175000017500000000000015156143275023556 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/ai/aiconditionshelper.cpp0000664000175000017500000000672715156143275030161 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "aiconditionshelper.h" #include #include #include #include AIConditionsHelper::AIConditionsHelper(QObject *parent) : QObject{parent} { #ifdef USE_AI session = data_management_create_session(); if (session) { inited = true; } #endif } bool AIConditionsHelper::isValid() { return inited; } AIConditionsHelper::~AIConditionsHelper() { #ifdef USE_AI data_management_destroy_session(session); #endif } QJsonDocument AIConditionsHelper::pharseConditions(const QString &description, bool *ok) { QJsonDocument doc; #ifdef USE_AI if (!inited) { setOkResult(ok, false); return doc; } char *results = nullptr; QJsonObject obj; obj["description"] = description; QJsonDocument descriptionDoc(obj); DataManagementStatus ret = data_management_extract_search_conditions(session, descriptionDoc.toJson().constData(), &results); if (!ret.success) { setOkResult(ok, false); return doc; } doc = QJsonDocument::fromJson(results); setOkResult(ok, true); return doc; #else setOkResult(ok, false); return doc; #endif } bool AIConditionsHelper::isFileMatchedWithDescriptionCondition(const QString &filePath, const QString &descriptionCondition, bool *ok) { #ifdef USE_AI bool matched = false; if (!inited) { setOkResult(ok, false); return matched; } QJsonObject obj; obj["file"] = filePath; obj["description"] = descriptionCondition; QJsonDocument doc(obj); bool result = false; auto ret = data_management_is_file_relevant_to_description(session, doc.toJson().constData(), &result); if (!ret.success) { return matched; } if (result == 1) { matched = true; } setOkResult(ok, true); return matched; #else setOkResult(ok, false); return false; #endif } bool AIConditionsHelper::isFileMatchedWithLables(const QString &filePath, const QStringList &labels, bool *ok) { #ifdef USE_AI bool matched = false; if (!inited) { setOkResult(ok, false); return matched; } QJsonObject obj; obj["file"] = filePath; obj["tags"] = QJsonArray::fromStringList(labels); QJsonDocument doc(obj); bool result = false; DataManagementStatus ret = data_management_is_file_relevant_to_tags(session, doc.toJson().constData(), &result); if (!ret.success) { return matched; } if (result == 1) { matched = true; } setOkResult(ok, true); return matched; #else setOkResult(ok, false); return false; #endif } void AIConditionsHelper::setOkResult(bool *ok, bool result) { if (ok) { *ok = result; } } peony-extensions/peony-intelligent-data-management-service/ai/aipreviewhelper.h0000664000175000017500000000261715156143275027130 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef AIPREVIEWHELPER_H #define AIPREVIEWHELPER_H #include #ifdef USE_AI #include #endif class AIPreviewHelper : public QObject { Q_OBJECT public: explicit AIPreviewHelper(QObject *parent = nullptr); ~AIPreviewHelper(); bool isValid(); QStringList getFileLabels(const QString &filePath, bool *ok = nullptr); QString getFileSummary(const QString &filePath, bool *ok = nullptr); private: void setOkResult(bool *ok, bool result); #ifdef USE_AI DataManagementSession *session = nullptr; #endif bool inited = false; }; #endif // AIPREVIEWHELPER_H peony-extensions/peony-intelligent-data-management-service/ai/aisearchtask.cpp0000664000175000017500000002353515156143275026734 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "aisearchtask.h" #include "file/fileinfo.h" #include "file/fileinfojob.h" #include "ai/aiconditionshelper.h" #include "ai/ailabelhelper.h" #include "condition/condition.h" #include #include #include #include #include #include "model/modelcontroller.h" #include AISearchTask::AISearchTask(const QString &description, QObject *parent) : QObject{parent}, m_description{description} { cancelled = false; } void AISearchTask::cancel() { cancelled = true; } void AISearchTask::doQuery() { // connect(this, &AISearchTask::finished, this, [=](bool successed, const QList> &results, const QJsonDocument &conditions, const QString &errorMsg){ // cancel(); // }); QList infos; DataManagementSession *session = data_management_create_session(); if (!session) { Q_EMIT finished(false, infos, QJsonDocument(), tr("Failed to create ai search session.")); return; } if (cancelled) { data_management_destroy_session(session); Q_EMIT finished(false, infos, QJsonDocument(), tr("Search task cancelled.")); return; } QJsonDocument conditionsDoc; if (pharseConditions) { AIConditionsHelper helper; conditionsDoc = helper.pharseConditions(m_description); qDebug() << conditionsDoc; QJsonObject conditionsObj = conditionsDoc.object(); auto conditions = Condition::generateContitionsFromDescription(conditionsObj); //将条件进行转换、AI文件内容返回的type为3对应文管文件内容类型type为7 if(conditions.size()==1 && conditions[0]->type() == Condition::DateRange){ auto fileContentObj = conditionsObj[QString::number(0)].toObject(); fileContentObj["condition type"] = int(Condition::FileContent); fileContentObj["content"] = m_description; fileContentObj["orCondition"] = false; conditionsObj[QString::number(0)] = fileContentObj; } conditions = Condition::generateContitionsFromDescription(conditionsObj); auto labels = Condition::getLabelsFromDescription(conditionsObj); bool hasConditions = false; bool hasLabels = false; bool hasContentConditions = false; QString contentDescription; hasConditions = !conditions.isEmpty(); // 获取文件内容描述条件 for (auto condition : conditions) { if (condition->type() == Condition::FileContent) { hasContentConditions = true; contentDescription = condition->getFileContentContent(); break; } } hasLabels = !labels.isEmpty(); QSet labelsMatchedInfos; QSet contentMatchedInfos; QSet basicMatchedInfos; QSet resultInfos; if (cancelled) { goto out; } if (hasLabels) { // 获取标签匹配结果 AILabelHelper labelHelper; QStringList files = labelHelper.getFilesByLabels(labels); for (auto path : files) { //auto info = UKUI::IDM::FileInfo::fromPath(path); labelsMatchedInfos << path; } } if (hasContentConditions) { // 获取文件内容匹配结果,注意这里使用的不是普通搜索按关键字的匹配方式 QJsonObject obj; obj["text"] = contentDescription; QJsonDocument doc(obj); char* searchresults = nullptr; data_management_similarity_search(session, doc.toJson().constData(), &searchresults); QJsonDocument resultDoc = QJsonDocument::fromJson(searchresults); data_management_destroy_result(searchresults); QJsonArray resultArray = resultDoc.array(); if (cancelled) { goto out; } // 使用语义解析获取的条件进行过滤 QList> orConditions; QList> andConditions; for (auto condition : conditions) { if (condition->isAndCondition()) { andConditions << condition; } else { orConditions << condition; } } for (auto item : resultArray) { if (cancelled) { goto out; } if (item.isObject()) { auto result = item.toObject(); auto value = result.value("filepath"); QString path = value.toString(); auto info = UKUI::IDM::FileInfo::fromPath(path); if (UKUI::IDM::FileInfoJob::infoMatchCondtions(QFileInfo(path), andConditions, orConditions)) { contentMatchedInfos << path; } } } } else if (!hasContentConditions && hasConditions) { // fixme: 不包含内容描述的语义搜索,获取符合基本条件的文件结果,这里应该退化成普通搜索的流程 } if (!hasConditions) { // 如果只有标签条件则直接返回标签结果 infos = labelsMatchedInfos.values(); } else { // 如果同时有标签条件和基础条件,则需要取各自结果的交集 if (hasContentConditions) { resultInfos = contentMatchedInfos; if (hasLabels) resultInfos = resultInfos.intersect(labelsMatchedInfos); infos = resultInfos.values(); } else if (hasConditions) { resultInfos = basicMatchedInfos; if (hasLabels) resultInfos = resultInfos.intersect(labelsMatchedInfos); infos = resultInfos.values(); } } } else { // 将整段description作为token进行搜索 QJsonObject obj; obj["text"] = m_description; QJsonDocument doc(obj); char* searchresults = nullptr; data_management_similarity_search(session, doc.toJson().constData(), &searchresults); QJsonDocument resultDoc = QJsonDocument::fromJson(searchresults); data_management_destroy_result(searchresults); QJsonArray resultArray = resultDoc.array(); if (cancelled) { goto out; } QSet labelsMatchedFiles; QSet conditionsMatchedFiles; bool hasLabels = false; auto labels = Condition::getLabelsFromDescription(m_conditionDescription); if (!labels.isEmpty()) { hasLabels = true; AILabelHelper helper; QStringList filesMatchedLabels = helper.getFilesByLabels(labels); for (auto filePath : filesMatchedLabels) { labelsMatchedFiles << filePath;//UKUI::IDM::FileInfo::fromPath(filePath); } if (cancelled) { goto out; } } // 使用手动设置的条件进行过滤 auto conditions = Condition::generateContitionsFromDescription(m_conditionDescription); QList> orConditions; QList> andConditions; for (auto condition : conditions) { if (condition->isAndCondition()) { andConditions << condition; } else { orConditions << condition; } } for (auto item : resultArray) { if (cancelled) { goto out; } if (item.isObject()) { auto result = item.toObject(); auto value = result.value("filepath"); QString path = value.toString(); //auto info = UKUI::IDM::FileInfo::fromPath(path); if (UKUI::IDM::FileInfoJob::infoMatchCondtions(QFileInfo(path), andConditions, orConditions)) { conditionsMatchedFiles << path; } } } if (hasLabels) { auto set = conditionsMatchedFiles; set = set.intersect(labelsMatchedFiles); infos = set.values(); } else { infos = conditionsMatchedFiles.values(); } } out: data_management_destroy_session(session); if (cancelled) { infos.clear(); //Q_EMIT finished(false, infos, QJsonDocument(), tr("Search task cancelled.")); } else { Q_EMIT finished(true,infos, conditionsDoc, nullptr); //resultInfos = infos; } } void AISearchTask::cancelWithSpaceId(int spaceId) { if (this->spaceId == spaceId) { cancel(); } } void AISearchTask::setPharseConditions(bool newPharseConditions) { pharseConditions = newPharseConditions; } void AISearchTask::setFilterConditionDescription(const QVariant &conditionDescription) { m_conditionDescription = conditionDescription; } void AISearchTask::setSpaceId(int spaceId) { this->spaceId = spaceId; } peony-extensions/peony-intelligent-data-management-service/ai/aipreviewhelper.cpp0000664000175000017500000000735615156143275027470 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "aipreviewhelper.h" #include #include #include #include #include #include #include "model/modelcontroller.h" AIPreviewHelper::AIPreviewHelper(QObject *parent) : QObject{parent} { #ifdef USE_AI session = data_management_create_session(); if (session) { inited = true; } #endif } AIPreviewHelper::~AIPreviewHelper() { #ifdef USE_AI data_management_destroy_session(session); #endif } bool AIPreviewHelper::isValid() { return inited; } QStringList AIPreviewHelper::getFileLabels(const QString &filePath, bool *ok) { QStringList list; #ifdef USE_AI if (!inited) { setOkResult(ok, false); return list; } QJsonObject obj; QStringList files; files << filePath; obj["files"] = QJsonArray::fromStringList(files); QJsonDocument doc(obj); char *results = nullptr; auto ret = data_management_get_tags_of_files(session, doc.toJson().constData(), &results); if (!ret.success) { setOkResult(ok, false); return list; } doc = QJsonDocument::fromJson(results); if (doc.isArray()) { QJsonArray resultsArray = doc.array(); for (auto result : resultsArray) { if (result.isObject()) { QJsonObject obj = result.toObject(); QJsonValue tagsValue = obj.value("tags"); if (tagsValue.isArray()) { QJsonArray tagsArray = tagsValue.toArray(); for (auto tag : tagsArray) { list << tag.toString(); } } break; } } } data_management_destroy_result(results); setOkResult(ok, true); return list; #else setOkResult(ok, false); return list; #endif } QString AIPreviewHelper::getFileSummary(const QString &filePath, bool *ok) { QString summary; #ifdef USE_AI if (!inited) { setOkResult(ok, false); return summary; } QJsonObject obj; QStringList files; files << filePath; obj["files"] = QJsonArray::fromStringList(files); QJsonDocument doc(obj); char *results = nullptr; auto ret = data_management_get_summarys_of_files(session, doc.toJson().constData(), &results); if (!ret.success) { setOkResult(ok, false); return summary; } doc = QJsonDocument::fromJson(results); if (doc.isArray()) { QJsonArray resultsArray = doc.array(); for (auto result : resultsArray) { if (result.isObject()) { QJsonObject obj = result.toObject(); summary = obj["summary"].toString(); break; } } } data_management_destroy_result(results); setOkResult(ok, true); return summary; #else setOkResult(ok, false); return summary; #endif } void AIPreviewHelper::setOkResult(bool *ok, bool result) { if (ok) { *ok = result; } } peony-extensions/peony-intelligent-data-management-service/ai/aiconditionshelper.h0000664000175000017500000000311715156143275027614 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef AICONDITIONSHELPER_H #define AICONDITIONSHELPER_H #include #ifdef USE_AI #include #endif class AIConditionsHelper : public QObject { Q_OBJECT public: explicit AIConditionsHelper(QObject *parent = nullptr); ~AIConditionsHelper(); QJsonDocument pharseConditions(const QString &description, bool *ok = nullptr); bool isFileMatchedWithDescriptionCondition(const QString &filePath, const QString &descriptionCondition, bool *ok = nullptr); bool isFileMatchedWithLables(const QString &filePath, const QStringList &labels, bool *ok = nullptr); bool isValid(); private: void setOkResult(bool *ok, bool result); #ifdef USE_AI DataManagementSession *session = nullptr; #endif bool inited = false; }; #endif // AICONDITIONSHELPER_H peony-extensions/peony-intelligent-data-management-service/ai/ailabelhelper.cpp0000664000175000017500000001444015156143275027056 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "ailabelhelper.h" #include #include #include #include #include #include #include AILabelHelper::AILabelHelper(QObject *parent) : QObject{parent} { #ifdef USE_AI session = data_management_create_session(); if (session) { inited = true; } #endif } AILabelHelper::~AILabelHelper() { #ifdef USE_AI data_management_destroy_session(session); #endif } bool AILabelHelper::isValid() { return inited; } QStringList AILabelHelper::getTopLabels(int maxCount, bool *ok) { QStringList list; #ifdef USE_AI if (!inited) { setOkResult(ok, false); return list; } char *results = nullptr; auto ret = data_management_get_all_tags(session, &results); if (!ret.success) { setOkResult(ok, false); return list; } QJsonDocument doc = QJsonDocument::fromJson(results); if (doc.isObject()) { QJsonObject obj = doc.object(); QJsonValue tagsValue = obj.value("tags"); if (tagsValue.isArray()) { QJsonArray tagsArray = tagsValue.toArray(); for (auto tag : tagsArray) { list << tag.toString(); if (maxCount > 0 && list.count() == maxCount) break; } } } else if (doc.isArray()) { QJsonArray tagsArray = doc.array(); for (auto tag : tagsArray) { list << tag.toString(); if (maxCount > 0 && list.count() == maxCount) break; } } data_management_destroy_result(results); setOkResult(ok, true); return list; #else setOkResult(ok, false); return list; #endif } QStringList AILabelHelper::getSimilarLabels(const QString &label, bool *ok) { QStringList list; #ifdef USE_AI if (!inited) { setOkResult(ok, false); return list; } QJsonObject obj; QStringList labels; labels << label; obj["tags"] = QJsonArray::fromStringList(labels); QJsonDocument tagsDoc(obj); char *results = nullptr; auto ret = data_management_search_similar_tags(session, tagsDoc.toJson().constData(), &results); if (!ret.success) { setOkResult(ok, false); return list; } QJsonDocument doc = QJsonDocument::fromJson(results); if (doc.isObject()) { QJsonObject obj = doc.object(); QJsonValue tagsValue = obj.value("tags"); if (tagsValue.isArray()) { QJsonArray tagsArray = tagsValue.toArray(); for (auto tag : tagsArray) { list << tag.toString(); } } } else if (doc.isArray()) { QJsonArray tagsArray = doc.array(); for (auto tag : tagsArray) { list << tag.toString(); } } data_management_destroy_result(results); setOkResult(ok, true); return list; #else setOkResult(ok, false); return list; #endif } QStringList AILabelHelper::getFileLabels(const QString &filePath, bool *ok) { QStringList list; #ifdef USE_AI if (!inited) { setOkResult(ok, false); return list; } QJsonObject obj; QStringList files; files << filePath; obj["files"] = QJsonArray::fromStringList(files); QJsonDocument doc(obj); char *results = nullptr; auto ret = data_management_get_tags_of_files(session, doc.toJson().constData(), &results); if (!ret.success) { setOkResult(ok, false); return list; } doc = QJsonDocument::fromJson(results); if (doc.isArray()) { QJsonArray resultsArray = doc.array(); for (auto result : resultsArray) { if (result.isObject()) { QJsonObject obj = result.toObject(); QJsonValue tagsValue = obj.value("tags"); if (tagsValue.isArray()) { QJsonArray tagsArray = tagsValue.toArray(); for (auto tag : tagsArray) { list << tag.toString(); } } break; } } } data_management_destroy_result(results); setOkResult(ok, true); return list; #else setOkResult(ok, false); return list; #endif } QStringList AILabelHelper::getFilesByLabels(const QStringList &labels, bool *ok) { QStringList list; #ifdef USE_AI if (!inited) { setOkResult(ok, false); return list; } qDebug() << labels; QJsonArray tagsArray = QJsonArray::fromStringList(labels); QJsonObject obj; obj["tags"] = tagsArray; QJsonDocument doc(obj); char *results = nullptr; qDebug() << doc; auto ret = data_management_search_files_about_tags(session, doc.toJson().constData(), &results); if (!ret.success) { setOkResult(ok, false); return list; } doc = QJsonDocument::fromJson(results); if (doc.isObject()) { obj = doc.object(); QJsonValue value = obj["files"]; if (value.isArray()) { QJsonArray filesArray = value.toArray(); for (auto file : filesArray) { list << file.toString(); } } } else if (doc.isArray()) { QJsonArray filesArray = doc.array(); for (auto file : filesArray) { list << file.toString(); } } data_management_destroy_result(results); setOkResult(ok, true); return list; #else setOkResult(ok, false); return list; #endif } void AILabelHelper::setOkResult(bool *ok, bool result) { if (ok) { *ok = result; } } peony-extensions/peony-intelligent-data-management-service/ai/aisearchtask.h0000664000175000017500000000461215156143275026374 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef AISEARCHTASK_H #define AISEARCHTASK_H #include #include #include #include #include #include #include namespace UKUI { namespace IDM { class FileInfo; } } class AISearchTask : public QObject { Q_OBJECT public: explicit AISearchTask(const QString &description, QObject *parent = nullptr); /*! * \brief setPharseConditions * 是否对输入的description进行语义解析转为条件之后再进行搜索 * \param newPharseConditions */ void setPharseConditions(bool newPharseConditions); /*! * \brief setFilterConditionDescription * 设置用于过滤结果的条件配置,只有在pharseConditions为false时才有效果 * \param conditionDescription */ void setFilterConditionDescription(const QVariant &conditionDescription); void setSpaceId(int spaceId); Q_SIGNALS: void finisheds(bool successed, const QList &results, const QJsonDocument &conditions, const QString &errorMsg); void finished(bool successed, const QList &results, const QJsonDocument &conditions, const QString &errorMsg); void searchcontentload(QString uri, QString modified, QString filetype,QString size); public Q_SLOTS: void cancel(); void doQuery(); void cancelWithSpaceId(int spaceId); private: std::atomic_bool cancelled; bool pharseConditions = false; QString m_description; QVariant m_conditionDescription; QList> resultInfos; int spaceId = -1; }; #endif // AISEARCHTASK_H peony-extensions/peony-intelligent-data-management-service/ai/ailabelhelper.h0000664000175000017500000000303115156143275026515 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef AILABELHELPER_H #define AILABELHELPER_H #include #ifdef USE_AI #include #endif class AILabelHelper : public QObject { Q_OBJECT public: explicit AILabelHelper(QObject *parent = nullptr); ~AILabelHelper(); bool isValid(); QStringList getTopLabels(int maxCount, bool *ok = nullptr); QStringList getSimilarLabels(const QString &label, bool *ok = nullptr); QStringList getFileLabels(const QString &filePath, bool *ok = nullptr); QStringList getFilesByLabels(const QStringList &labels, bool *ok = nullptr); private: void setOkResult(bool *ok, bool result); #ifdef USE_AI DataManagementSession *session = nullptr; #endif bool inited = false; }; #endif // AILABELHELPER_H ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service_zh_CN.tspeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000002130515156143275033236 0ustar fengfeng AISearchTask Failed to create ai search session. Search task cancelled. Condition File name contains "%1" File name without "%1" File type contains "%1" By suffix Folder Plain Text WPS Document PDF Image Video Audio File type contains %1 All Empty(0K) Tiny(0-16K) Small(16K-1M) Medium(1M-128M) Big(128M-1G) Large(1-4G) Great(>4G) File size contains %1 File size without %1 File content match %1 File location contains "%1" File location without "%1" Later than "%1" Earlier than "%1" DatabaseManager New Space %1 新建空间 %1 File %1 has areally been added to %2 File %1 doesn't exsit in any space. Failed to get space %1's id from query Failed to get %1's id from query ModelController Failed to rename space %1 to %2, %2 is existed. Failed to remove space %1, error message: %2 New Space %1 新建空间 %1 Failed to updatestatus space %1, error message: %2 QObject %1 %2 Intelligent Data Manager Introduction 智能空间介绍 exit UKUI::IDM::FileInfoJob Create Date: %1 Open Date: %1 peony-extensions/peony-intelligent-data-management-service/condition/0000775000175000017500000000000015156143275025153 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/condition/condition.h0000664000175000017500000000775615156143275027331 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef CONDITION_H #define CONDITION_H #include #include #include #include class Condition : public QObject { Q_OBJECT public: enum Type { FileType=1, FileSize, DateRange, FileName, FileLabel, ConditionMatchRule, FileContent, SearchLocation, }; enum MatchRule { NameOrContent, Name }; enum FilterFileModifyTime { ALL_TIME, TODAY, YESTERDAY, THIS_WEEK, LAST_WEEK, THIS_MONTH, LAST_MONTH, THIS_YEAR, LAST_YEAR }; enum FilterFileType { ALL_TYPE, FILE_FOLDER, PICTURE, VIDEO, TXT_FILE, AUDIO, WPS_FILE, OTHERS }; explicit Condition(Type type, QObject *parent = nullptr); static std::shared_ptr fromJsonObject(const QJsonObject &jsonObj); bool isValid(); /*! * 设置条件的与或逻辑, 默认逻辑为与逻辑 * \brief setIsAndCondition * \param isAnd */ void setIsAndCondition(bool isAnd); bool isAndCondition() const; bool setFileNameCondition(bool contains, const QStringList &keywords); QStringList getFileNameKeywords(); bool getFileNameIsContains(); bool isFileNameMatchedWithKeywords(const QString &fileName); bool setFileTypeCondition(const QStringList &suffixes); bool setFileTypeCondition(int fileType); int getFileTypeSelectionType(); QStringList getFileTypeSuffixes(); bool getFileTypeIsContains(); bool isFileTypeMatchedWithSuffixes(const QString &fileName); bool isFileTypeMatchedWithSubType(int type, const QString &fileName, const QString &mimeType); bool setFileSizeCondition(bool contains, int type); bool getFileSizeIsContains(); int getFileSizeType(); bool isFileSizeMatchedWithSize(quint64 size); bool setFileContentCondition(const QString &content); QString getFileContentContent(); bool setSearchLocationCondition(bool contains, const QString &path); bool getSearchLocationIsContains(); QString getSearchLocationPath(); bool isFileMatchedWithSearchLocation(const QString &filePath); bool setDateRangeCondition(int type, quint64 startMsec, quint64 endMsec); int getDateRangeType(); bool getDateRangeIsContains(); quint64 getDateRangeStartMsec(); quint64 getDateRangeEndMsec(); bool isFileMatchedWithDate(int type, quint64 timeMsec); bool setConditionMatchRule(MatchRule rule, bool contains = true); MatchRule getConditionMatchRule(); bool getConditionMatchRuleIsContains(); Type type() const; QJsonObject conditionObject(); QStringList getConditionLabels(); static QVariant pharseConditions(const QList > &conditions, const QStringList &labels = QStringList()); static QList> generateContitionsFromDescription(const QVariant &description); static QStringList getLabelsFromDescription(const QVariant &description); Q_SIGNALS: private: bool m_isAndCondition = true; // 设置是否为And类型条件 Type m_type; QVariant m_conditionDescription; // json数据结构 }; #endif // CONDITION_H peony-extensions/peony-intelligent-data-management-service/condition/condition.cpp0000664000175000017500000005151515156143275027654 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "condition.h" #include #include #include #include #include #include const QString Folder_Type = "inode/directory"; const QString Image_Type = "image/"; const QString Video_Type = "video/"; const QString Text_Type = "text/"; const QString Wps_Type = "application/wps-office"; const QString Audio_Type = "audio/"; const QStringList wpsSuffix = {"doc", "docx", "dot", "wps", "wpt", "xlx", "xlxs", "et", "ppt", "pptx", "wpp"}; QString getRelativeTime(QDateTime dateTime) { QString displayString; #ifdef COMPLIE_AS_PROJECT time_t lt; lt = time(NULL); struct tm m_tm = *localtime(<); QDate date = dateTime.date(); QTime qtime = dateTime.time(); m_tm.tm_year = date.year(); m_tm.tm_mon = date.month(); m_tm.tm_mday = date.day(); m_tm.tm_hour = qtime.hour(); m_tm.tm_min = qtime.minute(); m_tm.tm_sec = qtime.second(); auto ret = kdk_system_timeformat_transform(&m_tm); char* formatDate = kdk_system_tran_absolute_date(&m_tm); displayString = QObject::tr("%1 %2").arg(formatDate).arg(ret->timesec); kdk_free_timeinfo(ret); free(formatDate); #endif return displayString; } Condition::Condition(Type type, QObject *parent) : QObject(parent) { m_type = type; } std::shared_ptr Condition::fromJsonObject(const QJsonObject &jsonObj) { int type = jsonObj["condition type"].toInt(); auto condition = std::shared_ptr(new Condition(Type(type))); condition->m_conditionDescription = jsonObj; if (jsonObj["orCondition"].toBool()) { condition->m_isAndCondition = false; } return condition; } bool Condition::isValid() { return !m_conditionDescription.isNull(); } void Condition::setIsAndCondition(bool isAnd) { m_isAndCondition = isAnd; QJsonObject obj = m_conditionDescription.toJsonObject(); obj["orCondition"] = !isAnd; m_conditionDescription = QVariant(obj); } bool Condition::setFileNameCondition(bool contains, const QStringList &keywords) { if (m_type == FileName && !keywords.isEmpty()) { QJsonObject fileNameObj; fileNameObj["condition type"] = int(m_type); fileNameObj["contains"] = contains; fileNameObj["keywords"] = QJsonArray::fromStringList(keywords); fileNameObj["orCondition"] = !m_isAndCondition; m_conditionDescription = QVariant(fileNameObj); return true; } return false; } bool Condition::getFileNameIsContains() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["contains"].toBool(); } bool Condition::isFileNameMatchedWithKeywords(const QString &fileName) { bool match = false; QStringList keywords = this->getFileNameKeywords(); if (this->getFileNameIsContains()) { for (auto keyword : keywords) { if (fileName.contains(keyword)) { match = true; break; } } } else { // 当条件为不包含时, 如果存在包含的关键词则不匹配, 此时应该跳出循环 match = true; for (auto keyword : keywords) { if (fileName.contains(keyword)) { match = false; break; } } } return match; } QStringList Condition::getFileNameKeywords() { auto jsonObj = m_conditionDescription.toJsonObject(); auto jsonArray = jsonObj["keywords"].toArray(); QStringList keywords; for (auto value : jsonArray) { if(!value.toString().isEmpty()) keywords << value.toString(); } return keywords; } bool Condition::setFileTypeCondition(const QStringList &suffixes) { if (m_type == FileType && !suffixes.isEmpty()) { QJsonObject fileTypeObj; fileTypeObj["condition type"] = int(m_type); fileTypeObj["type"] = -1; fileTypeObj["suffixes"] = QJsonArray::fromStringList(suffixes); fileTypeObj["orCondition"] = !m_isAndCondition; m_conditionDescription = QVariant(fileTypeObj); return true; } return false; } bool Condition::setFileTypeCondition(int fileType) { if (m_type == FileType) { QJsonObject fileTypeObj; fileTypeObj["condition type"] = int(m_type); fileTypeObj["type"] = fileType; fileTypeObj["orCondition"] = !m_isAndCondition; m_conditionDescription = QVariant(fileTypeObj); return true; } return false; } int Condition::getFileTypeSelectionType() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["type"].toInt(); } QStringList Condition::getFileTypeSuffixes() { auto jsonObj = m_conditionDescription.toJsonObject(); auto jsonArray = jsonObj["suffixes"].toArray(); QStringList suffixes; for (auto value : jsonArray) { suffixes << value.toString(); } return suffixes; } bool Condition::getFileTypeIsContains() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["contains"].toBool(); } bool Condition::isFileTypeMatchedWithSuffixes(const QString &fileName) { auto suffixes = getFileTypeSuffixes(); bool match = false; for (auto suffix : suffixes) { if (fileName.endsWith("." + suffix)) { match = true; break; } } return match; } bool Condition::isFileTypeMatchedWithSubType(int type, const QString &fileName, const QString &mimeType) { bool match = false; bool contains = getFileTypeIsContains(); if (type == ALL_TYPE) return contains; switch (type) { case FILE_FOLDER: { match = mimeType == Folder_Type; break; } case PICTURE: { match = mimeType.startsWith(Image_Type); break; } case VIDEO: { match = mimeType.startsWith(Video_Type); break; } case TXT_FILE: { match = mimeType.startsWith(Text_Type); break; } case WPS_FILE: { match = mimeType.startsWith(Wps_Type); if (!match) { QString tmp = fileName; int index = tmp.lastIndexOf("."); if (index >= 0) { tmp = tmp.chopped(index); if (!tmp.isEmpty()) { match = wpsSuffix.contains(tmp); } } } break; } case AUDIO: { match = (mimeType.startsWith(Audio_Type)||mimeType.contains("application/x-smaf")); break; } case OTHERS: { //exclude classfied types, show the rest other types if ((mimeType != Folder_Type) && ! mimeType.startsWith(Image_Type) && !mimeType.startsWith(Video_Type) && ! mimeType.startsWith(Text_Type) && ! mimeType.startsWith(Wps_Type) && !mimeType.startsWith(Audio_Type) && !mimeType.contains("application/x-smaf")) return true; break; } default: break; } match = contains? match: !match; return match; } bool Condition::setFileSizeCondition(bool contains, int type) { if (m_type == FileSize) { QJsonObject fileSizeObj; fileSizeObj["condition type"] = int(m_type); fileSizeObj["contains"] = contains; fileSizeObj["type"] = type; fileSizeObj["orCondition"] = !m_isAndCondition; m_conditionDescription = QVariant(fileSizeObj); return true; } return false; } bool Condition::getFileSizeIsContains() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["contains"].toBool(); } int Condition::getFileSizeType() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["type"].toInt(); } bool Condition::isFileSizeMatchedWithSize(quint64 size) { bool match = false; int fileSizeType = this->getFileSizeType(); bool contains = this->getFileSizeIsContains(); bool sizeMatch = false; switch (fileSizeType) { case 0: { // "Empty(all)" sizeMatch = true; break; } case 1: { // "Empty(0K)" if (size == 0) { sizeMatch = true; } break; } case 2: { // "Tiny(0-16K)" if (size <= 16 * 1024 && size > 0) { sizeMatch = true; } break; } case 3: { // "Small(16K-1M)" if (size <= 1024 * 1024 && size > 16 * 1024) { sizeMatch = true; } break; } case 4: { // "Medium(1M-128M)" if (size <= 128 * 1024 * 1024 && size > 1024 * 1024) { sizeMatch = true; } break; } case 5: { // "Big(128M-1G)" if (size <= 1024 * 1024 * 1024 && size > 128 * 1024 * 1024) { sizeMatch = true; } break; } case 6: { // "Large(1-4G)" quint64 fourGiB = 1024 * 1024 * 1024; fourGiB *= 4; if (size <= fourGiB && size > 1024 * 1024 * 1024) { sizeMatch = true; } break; } case 7: { // "Great(>4G)" quint64 fourGiB = 1024 * 1024 * 1024; fourGiB *= 4; if (size > fourGiB) { sizeMatch = true; } break; } default: break; } match = contains? sizeMatch: !sizeMatch; return match; } bool Condition::setFileContentCondition(const QString &content) { if (m_type == FileContent && !content.isEmpty()) { QJsonObject fileContentObj; fileContentObj["condition type"] = int(m_type); fileContentObj["content"] = content; fileContentObj["orCondition"] = !m_isAndCondition; m_conditionDescription = QVariant(fileContentObj); return true; } return false; } QString Condition::getFileContentContent() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["content"].toString(); } bool Condition::setSearchLocationCondition(bool contains, const QString &path) { if (m_type == SearchLocation && !path.isEmpty()) { QJsonObject searchLocationObj; searchLocationObj["condition type"] = int(m_type); searchLocationObj["contains"] = contains; searchLocationObj["path"] = path; searchLocationObj["orCondition"] = !m_isAndCondition; m_conditionDescription = QVariant(searchLocationObj); return true; } return false; } bool Condition::getSearchLocationIsContains() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["contains"].toBool(); } QString Condition::getSearchLocationPath() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["path"].toString(); } bool Condition::isFileMatchedWithSearchLocation(const QString &filePath) { bool match = false; bool contains = getSearchLocationIsContains(); auto searchPath = getSearchLocationPath(); if (!searchPath.endsWith("/")) { searchPath.append("/"); } if (filePath.contains(searchPath)) { match = contains? true: false; } else { // 如果条件为不包含则认为匹配 match = !contains; } return match; } bool Condition::setDateRangeCondition(int type, quint64 startMsec, quint64 endMsec) { if (m_type == DateRange && (startMsec <= endMsec)) { QJsonObject dateRangObj; dateRangObj["condition type"] = int(m_type); dateRangObj["type"] = type; dateRangObj["start"] = QString::number(qulonglong(startMsec)); dateRangObj["end"] = QString::number(qulonglong(endMsec)); dateRangObj["orCondition"] = !m_isAndCondition; m_conditionDescription = QVariant(dateRangObj); return true; } return false; } int Condition::getDateRangeType() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["type"].toInt(); } bool Condition::getDateRangeIsContains() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["contains"].toBool(); } quint64 Condition::getDateRangeStartMsec() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["start"].toString().toULongLong(); } quint64 Condition::getDateRangeEndMsec() { auto jsonObj = m_conditionDescription.toJsonObject(); return jsonObj["end"].toString().toULongLong(); } bool Condition::isFileMatchedWithDate(int type, quint64 timeMsec) { bool contain = getDateRangeIsContains(); auto Datetype = FilterFileModifyTime(getDateRangeType()); if (Datetype == ALL_TIME) return contain; auto time = QDateTime::currentMSecsSinceEpoch(); auto dateTime = QDateTime::fromMSecsSinceEpoch(time); //qDebug() << "cur QDateTime:" < 1) { joinSuffixes = suffixes.join(", "); } else { joinSuffixes = suffixes.first(); } if(!joinSuffixes.isEmpty()) labels << tr("File type contains \"%1\"").arg(joinSuffixes); } else { QStringList selections = {tr("By suffix"), tr("Folder"), tr("Plain Text"), tr("WPS Document"), tr("PDF"), tr("Image"), tr("Video"), tr("Audio")}; int type = getFileTypeSelectionType(); if (type >= 0 && type < selections.count()) { QString fileType = selections.at(type); labels << tr("File type contains %1").arg(fileType); } } break; } case FileSize: { bool contains = getFileSizeIsContains(); int sizeType = getFileSizeType(); QStringList sizes = {tr("All"),tr("Empty(0K)"), tr("Tiny(0-16K)"), tr("Small(16K-1M)"), tr("Medium(1M-128M)"), tr("Big(128M-1G)"), tr("Large(1-4G)"), tr("Great(>4G)")}; if (sizeType >= 0 && sizeType < sizes.count()) { if (contains) { labels << tr("File size contains %1").arg(sizes.at(sizeType)); } else { labels << tr("File size without %1").arg(sizes.at(sizeType)); } } break; } case FileContent: { labels << tr("File content match %1").arg(getFileContentContent()); break; } case SearchLocation: { bool contains = getSearchLocationIsContains(); QFileInfo fileInfo(getSearchLocationPath()); QString fileName = fileInfo.fileName(); if (contains) { labels << tr("File location contains \"%1\"").arg(fileName); } else { labels << tr("File location without \"%1\"").arg(fileName); } break; } case DateRange: { qint64 time1 = getDateRangeStartMsec(); qint64 time2 = getDateRangeEndMsec(); QDateTime dateTime1 = QDateTime::fromMSecsSinceEpoch(time1); QDateTime dateTime2 = QDateTime::fromMSecsSinceEpoch(time2); QString startTime = tr("Later than \"%1\"").arg(getRelativeTime(dateTime1)); QString endTime = tr("Earlier than \"%1\"").arg(getRelativeTime(dateTime2)); labels << startTime << endTime; break; } default: break; } return labels; } bool Condition::isAndCondition() const { return m_isAndCondition; } QList> Condition::generateContitionsFromDescription(const QVariant &description) { QList> result; QJsonObject jsonObj = description.toJsonObject(); int count = jsonObj["conditions count"].toInt(); for (int i = 0; i < count; i++) { auto conditionObj = jsonObj[QString::number(i)].toObject(); auto condition = Condition::fromJsonObject(conditionObj); result << condition; } return result; } QStringList Condition::getLabelsFromDescription(const QVariant &description) { QStringList labels; QJsonObject jsonObj = description.toJsonObject(); QJsonValue labelsValue = jsonObj["labels"]; if (labelsValue.isArray()) { QJsonArray labelsArray = labelsValue.toArray(); for (auto label : labelsArray) { labels << label.toString(); } } return labels; } QVariant Condition:: pharseConditions(const QList > &conditions, const QStringList &labels){ QJsonObject configObj; configObj["conditions count"] = conditions.count(); for (int i = 0; i < conditions.count(); i++) { configObj[QString::number(i)] = conditions[i]->conditionObject(); } if (!labels.isEmpty()) { configObj["labels"] = QJsonArray::fromStringList(labels); } return configObj; } peony-extensions/peony-intelligent-data-management-service/model/0000775000175000017500000000000015156143275024265 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/model/spacessettings.cpp0000664000175000017500000001624715156143275030042 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "spacessettings.h" #include #include #include static SpacesSettings *instance = nullptr; SpacesSettings *SpacesSettings::globalInstance() { if (!instance) { instance = new SpacesSettings; } return instance; } SpacesSettings::SpacesSettings(QObject *parent) : QObject{parent} { delayer = new QTimer(this); delayer->setInterval(500); delayer->setSingleShot(true); QSettings settings; settings.beginGroup("Common"); sortType = settings.value("sortType", 0).toInt(); sortOrder = settings.value("sortAsc", false).toBool()? Qt::AscendingOrder: Qt::DescendingOrder; useGlobalSorting = settings.value("useGlobalSorting", true).toBool(); userAddedFirstType = settings.value("userAddedFirstType", 7).toInt(); isSpacesCollapsed = settings.value("isSpacesCollapsed").toBool(); isRecentCollapsed = settings.value("isRecentCollapsed").toBool(); isSpaceListMode = settings.value("isSpaceListMode").toBool(); isPreviewMode = settings.value("isPreviewMode").toBool(); settings.endGroup(); auto startTimer = [=]{delayer->start();}; connect(this, &SpacesSettings::sortOrderChanged, delayer, startTimer); connect(this, &SpacesSettings::sortTypeChanged, delayer, startTimer); connect(this, &SpacesSettings::useGlobalSortingChanged, delayer, startTimer); connect(this, &SpacesSettings::userAddedFirstChanged, delayer, startTimer); connect(delayer, &QTimer::timeout, this, [=]{ QSettings settingsToSync; settingsToSync.beginGroup("Common"); settingsToSync.setValue("sortType", sortType); bool sortAsc = (sortOrder == Qt::AscendingOrder); settingsToSync.setValue("sortAsc", sortAsc); settingsToSync.setValue("useGlobalSorting", useGlobalSorting); settingsToSync.setValue("userAddedFirstType", userAddedFirstType); settingsToSync.endGroup(); settingsToSync.sync(); qDebug() << settingsToSync.fileName(); Q_EMIT sortConfigChanged(); }); } Qt::SortOrder SpacesSettings::getSortOrder() const { return sortOrder; } void SpacesSettings::setSortOrder(Qt::SortOrder newSortOrder) { if (sortOrder == newSortOrder) return; sortOrder = newSortOrder; Q_EMIT sortOrderChanged(); } int SpacesSettings::getSortType() const { return sortType; } void SpacesSettings::setSortType(int newSortType) { if (sortType == newSortType) return; sortType = newSortType; Q_EMIT sortTypeChanged(); } bool SpacesSettings::getUseGlobalSorting() const { return useGlobalSorting; } void SpacesSettings::setUseGlobalSorting(bool newUseGlobalSorting) { if (useGlobalSorting == newUseGlobalSorting) return; useGlobalSorting = newUseGlobalSorting; if (useGlobalSorting) { QSettings settings; settings.beginGroup("Spaces"); settings.clear(); settings.endGroup(); settings.sync(); } Q_EMIT useGlobalSortingChanged(); } int SpacesSettings::getUserAddedFirst() const { return userAddedFirstType; } void SpacesSettings::setUserAddedFirst(int newUserAddedFirst) { if (userAddedFirstType == newUserAddedFirst) return; setSortType(1); userAddedFirstType = newUserAddedFirst; Q_EMIT userAddedFirstChanged(); } void SpacesSettings::setSpacesSortConfig(int spaceId, int sortType, int sortOrder, int userAddedFirst) { QSettings settings; settings.beginGroup("Spaces"); settings.beginWriteArray(QString::number(spaceId), 3); settings.setValue("sortType", sortType); settings.setValue("sortOrder", sortOrder); settings.setValue("userAddedFirst", userAddedFirst); settings.endArray(); settings.endGroup(); settings.sync(); } QVariantHash SpacesSettings::getSpacesSortConfig(int spaceId) { QVariantHash result; QSettings settings; settings.beginGroup("Spaces"); settings.beginReadArray(QString::number(spaceId)); QVariant value = settings.value("sortType"); if (value.isNull()) { result.insert("sortType", this->getSortType()); } else { result.insert("sortType", value); } value = settings.value("sortOrder"); if (value.isNull()) { result.insert("sortOrder", this->getSortOrder() == Qt::AscendingOrder? 0: 1); } else { result.insert("sortOrder", value); } value = settings.value("userAddedFirstType"); if (value.isNull()) { result.insert("userAddedFirstType", this->getUserAddedFirst()); } else { result.insert("userAddedFirstType", value); } settings.endArray(); settings.endGroup(); return result; } bool SpacesSettings::getIsSpacesCollapsed() const { return isSpacesCollapsed; } void SpacesSettings::setIsSpacesCollapsed(bool newIsSpacesCollapsed) { if (isSpacesCollapsed == newIsSpacesCollapsed) return; isSpacesCollapsed = newIsSpacesCollapsed; QSettings settings; settings.beginGroup("Common"); settings.setValue("isSpacesCollapsed", newIsSpacesCollapsed); settings.endGroup(); settings.sync(); Q_EMIT isSpacesCollapsedChanged(); } bool SpacesSettings::getIsRecentCollapsed() const { return isRecentCollapsed; } void SpacesSettings::setIsRecentCollapsed(bool newIsRecentCollapsed) { if (isRecentCollapsed == newIsRecentCollapsed) return; isRecentCollapsed = newIsRecentCollapsed; QSettings settings; settings.beginGroup("Common"); settings.setValue("isRecentCollapsed", newIsRecentCollapsed); settings.endGroup(); settings.sync(); Q_EMIT isRecentCollapsedChanged(); } bool SpacesSettings::getIsSpaceListMode() const { return isSpaceListMode; } void SpacesSettings::setIsSpaceListMode(bool newIsSpaceListMode) { if (isSpaceListMode == newIsSpaceListMode) return; isSpaceListMode = newIsSpaceListMode; QSettings settings; settings.beginGroup("Common"); settings.setValue("isSpaceListMode", newIsSpaceListMode); settings.endGroup(); settings.sync(); Q_EMIT isSpaceListModeChanged(); } bool SpacesSettings::getIsPreviewMode() const { return isPreviewMode; } void SpacesSettings::setIsPreviewMode(bool newIsPreviewMode) { if (isPreviewMode == newIsPreviewMode) return; isPreviewMode = newIsPreviewMode; QSettings settings; settings.beginGroup("Common"); settings.setValue("isPreviewMode", newIsPreviewMode); settings.endGroup(); settings.sync(); Q_EMIT isPreviewModeChanged(); } peony-extensions/peony-intelligent-data-management-service/model/spacessettings.h0000664000175000017500000000674315156143275027507 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef SPACESSETTINGS_H #define SPACESSETTINGS_H #include class QTimer; class SpacesSettings : public QObject { Q_OBJECT Q_PROPERTY(Qt::SortOrder sortOrder READ getSortOrder WRITE setSortOrder NOTIFY sortOrderChanged) Q_PROPERTY(int sortType READ getSortType WRITE setSortType NOTIFY sortTypeChanged) Q_PROPERTY(bool useGlobalSorting READ getUseGlobalSorting WRITE setUseGlobalSorting NOTIFY useGlobalSortingChanged) Q_PROPERTY(bool userAddedFirst READ getUserAddedFirst WRITE setUserAddedFirst NOTIFY userAddedFirstChanged) Q_PROPERTY(bool isSpacesCollapsed READ getIsSpacesCollapsed WRITE setIsSpacesCollapsed NOTIFY isSpacesCollapsedChanged) Q_PROPERTY(bool isRecentCollapsed READ getIsRecentCollapsed WRITE setIsRecentCollapsed NOTIFY isRecentCollapsedChanged) Q_PROPERTY(bool isSpaceListMode READ getIsSpaceListMode WRITE setIsSpaceListMode NOTIFY isSpaceListModeChanged) Q_PROPERTY(bool isPreviewMode READ getIsPreviewMode WRITE setIsPreviewMode NOTIFY isPreviewModeChanged) public: static SpacesSettings *globalInstance(); explicit SpacesSettings(QObject *parent = nullptr); Q_INVOKABLE Qt::SortOrder getSortOrder() const; Q_INVOKABLE void setSortOrder(Qt::SortOrder newSortOrder); Q_INVOKABLE int getSortType() const; Q_INVOKABLE void setSortType(int newSortType); Q_INVOKABLE bool getUseGlobalSorting() const; Q_INVOKABLE void setUseGlobalSorting(bool newUseGlobalSorting); Q_INVOKABLE int getUserAddedFirst() const; Q_INVOKABLE void setUserAddedFirst(int newUserAddedFirst); void setSpacesSortConfig(int spaceId, int sortType, int sortOrder, int userAddedFirst); QVariantHash getSpacesSortConfig(int spaceId); bool getIsSpacesCollapsed() const; void setIsSpacesCollapsed(bool newIsSpacesCollapsed); bool getIsRecentCollapsed() const; void setIsRecentCollapsed(bool newIsRecentCollapsed); bool getIsSpaceListMode() const; void setIsSpaceListMode(bool newIsSpaceListMode); bool getIsPreviewMode() const; void setIsPreviewMode(bool newIsPreviewMode); Q_SIGNALS: void sortOrderChanged(); void sortTypeChanged(); void useGlobalSortingChanged(); void userAddedFirstChanged(); void sortConfigChanged(); void isSpacesCollapsedChanged(); void isRecentCollapsedChanged(); void isSpaceListModeChanged(); void isPreviewModeChanged(); private: Qt::SortOrder sortOrder = Qt::DescendingOrder; int sortType = 0; bool useGlobalSorting = true; int userAddedFirstType = 7; bool isSpacesCollapsed = false; bool isRecentCollapsed = false; bool isSpaceListMode = false; bool isPreviewMode = false; QTimer *delayer = nullptr; }; #endif // SPACESSETTINGS_H peony-extensions/peony-intelligent-data-management-service/model/searchresultmodel.cpp0000664000175000017500000005062215156143275030523 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "searchresultmodel.h" #include "modelcontroller.h" #include "file/fileinfojob.h" #include "ai/aisearchtask.h" #include #include #include #include #include //SearchResultModel *SearchResultModel::globalInstance() //{ // if (!instance) { // instance = new SearchResultModel; // } // return instance; //} SearchResultModel::SearchResultModel(QObject *parent) : QObject(parent) { delayer = new QTimer; delayer->setSingleShot(true); delayer->setInterval(500); connect(delayer, &QTimer::timeout, this, &SearchResultModel::doUpdateInternal); connect(this, &SearchResultModel::searchKeywordChanged, this, &SearchResultModel::delayUpdate); // connect(this, &SearchResultModel::searchInSpaceChanged, this, &SearchResultModel::delayUpdate); // connect(this, &SearchResultModel::searchConditionsChanged, this, &SearchResultModel::delayUpdate); // connect(this, &SearchResultModel::labelsChanged, this, &SearchResultModel::delayUpdate); // connect(this, &SearchResultModel::currentSpaceIdChanged, this, &SearchResultModel::delayUpdate); // connect(this, &SearchResultModel::aiSearchChanged, this, &SearchResultModel::delayUpdate); // connect(this, &SearchResultModel::updateSearchResult, this, &SearchResultModel::delayUpdate); searchTask = new SearchTask; QString currentSearhPath = ModelController::globalInstance()->getCurrentSearchpath(); if(!currentSearhPath.isEmpty()){ searchTask->setSearchPath(currentSearhPath); } connect(ModelController::globalInstance(), &ModelController::cancelSearchRequest, searchTask, &SearchTask::cancel); connect(searchTask, &SearchTask::searchFinished, this, [=]{ Q_EMIT searchTask->searchLoadBatch(true); }); connect(searchTask, &SearchTask::searchLoadBatch, this, [=](bool finish){ int count = searchTask->resultInfos.size(); int start = count - SEARCH_LOAD_BATCH; int length = SEARCH_LOAD_BATCH; if (!searchInSpace) { if(finish){ if(count < SEARCH_LOAD_BATCH){ start = 0; length = count; }else{ if(count%SEARCH_LOAD_BATCH != 0){ length = count%SEARCH_LOAD_BATCH; }else { length = 0; } } m_loadNameMatchflefinish = true; m_allCount = searchTask->resultInfos.size(); qDebug() << "match files count" << count; } }else{ if(finish){ m_loadNameMatchflefinish = true; m_allCount += spaceBatchFileinfoList.size(); } } auto info = searchInSpace? spaceBatchFileinfoList:searchTask->resultInfos.mid(start,length); QSet> set(info.begin(), info.end()); auto searchJob = UKUI::IDM::FileInfoJob::fromInfos(set.values(),false); searchJob->setAutoDelete(true); searchJob->setConditions(this->searchConditions); connect(this, &SearchResultModel::aboutToUpdate, searchJob, &UKUI::IDM::FileInfoJob::cancel); connect(ModelController::globalInstance(), &ModelController::cancelSearchRequest, searchJob, &UKUI::IDM::FileInfoJob::cancel); connect(searchJob, &UKUI::IDM::FileInfoJob::finished, this, [=]{ nameMatchFiles = searchJob->getInfos(); m_loadNameMatchFileCount += nameMatchFiles.size(); QList list; if (nameMatchFiles.count() > 0){ setHasResult(true); setLoading(false); for(auto fileinfo:nameMatchFiles){ QStringList fileinfolist; fileinfolist<uri()<modifyDateString()<fileType()<sizeFormat()<displayName()<path() <iconName()<mimeType()<size())<timeModifiedMsec()); list.append(fileinfolist); } } if(m_allCount == m_loadNameMatchFileCount){ qDebug()<<"########"<fileReloadByBatch(list,m_loadNameMatchflefinish && m_allCount == m_loadNameMatchFileCount); }, Qt::BlockingQueuedConnection); searchJob->start(); }); connect(searchTask, &SearchTask::searchContentFinished, this, [=](bool successed){ if (!successed){ if(searchContentFinish){ QList list; list = getContentResult(); ModelController::globalInstance()->SearchContentLoad(list); } searchTextContentFinish = true; return; } auto searchContentJob = UKUI::IDM::FileInfoJob::fromInfos(searchTask->contentResultInfos,false); searchContentJob->setAutoDelete(true); searchContentJob->setConditions(this->searchConditions); connect(this, &SearchResultModel::aboutToUpdate, searchContentJob, &UKUI::IDM::FileInfoJob::cancel); connect(searchContentJob, &UKUI::IDM::FileInfoJob::finished, this, [=]{ textContentMatchFiles = searchContentJob->getInfos(); searchTextContentFinish = true; QList list; if(searchContentFinish){ list = getContentResult(); ModelController::globalInstance()->SearchContentLoad(list); } }, Qt::BlockingQueuedConnection); searchContentJob->start(); }); } SearchResultModel::~SearchResultModel() { currentContentMatchFiles.clear(); nameMatchFiles.clear(); contentMatchFiles.clear(); textContentMatchFiles.clear(); } bool SearchResultModel::getSearchInSpace() const { return searchInSpace; } void SearchResultModel::setSearchInSpace(bool newSearchInSpace) { if (searchInSpace == newSearchInSpace) return; searchInSpace = newSearchInSpace; Q_EMIT searchInSpaceChanged(); } const QString &SearchResultModel::getSearchKeyword() const { return searchKeyword; } void SearchResultModel::setSearchKeyword(const QString &newSearchKeyword) { if (searchKeyword == newSearchKeyword) return; searchKeyword = newSearchKeyword; reversedConditions.clear(); auto condition = std::shared_ptr(new Condition(Condition::ConditionMatchRule)); condition->setConditionMatchRule(Condition::NameOrContent); condition->setIsAndCondition(false); auto condition1 = std::shared_ptr(new Condition(Condition::FileName)); condition1->setFileNameCondition(true, QStringList() << newSearchKeyword); condition1->setIsAndCondition(false); auto condition2 = std::shared_ptr(new Condition(Condition::FileContent)); condition2->setFileContentCondition(newSearchKeyword); condition2->setIsAndCondition(false); reversedConditions << condition; reversedConditions << condition1; if(aiIndexenable && isAiSubsystemavaliable) reversedConditions << condition2; Q_EMIT searchKeywordChanged(); currentlabels.clear(); } const QList> &SearchResultModel::getSearchConditions() const { return searchConditions; } void SearchResultModel::setSearchConditions(const QList> &newSearchConditions) { if (searchConditions == newSearchConditions) return; searchConditions.clear(); searchConditions = newSearchConditions; Q_EMIT searchConditionsChanged(); } int SearchResultModel::getCurrentSpaceId() const { return currentSpaceId; } void SearchResultModel::setCurrentSpaceId(int newCurrentSpaceId) { if (currentSpaceId == newCurrentSpaceId) return; currentSpaceId = newCurrentSpaceId; Q_EMIT currentSpaceIdChanged(); } const QList> &SearchResultModel::getReversedConditions() const { return reversedConditions; } void SearchResultModel::delayUpdate() { aiConditionDescription = QJsonDocument(); setLoading(true); Q_EMIT aboutToUpdate(); delayer->start(); } void SearchResultModel::doUpdateInternal() { //this->beginResetModel(); nameMatchFiles.clear(); contentMatchFiles.clear(); setHasResult(false); //this->endResetModel(); if (searchKeyword.isEmpty()) { return; } Q_EMIT ModelController::globalInstance()->searchKeywordChanged(searchKeyword); setLoading(true); //qDebug() << "do update internal" << searchInSpace << currentSpaceId << searchKeyword << reversedConditions << searchConditions; QList> conditions; conditions << reversedConditions; conditions << searchConditions; auto conditionsDescription = Condition::pharseConditions(conditions, labels); // fixme: 调用ai接口获取搜索数据,并且获取语义配置的json文件 qDebug() << "start ai search"; auto thread = new QThread; auto task = new AISearchTask(searchKeyword); // 将输入的关键词解析成条件再进行搜索 task->setPharseConditions(true); task->moveToThread(thread); connect(thread, &QThread::started, task, &AISearchTask::doQuery); connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(this, &SearchResultModel::aboutToUpdate, task, &AISearchTask::cancel); connect(ModelController::globalInstance(), &ModelController::cancelSearchRequest, task, &AISearchTask::cancel); connect(task, &AISearchTask::finisheds, this, [=](bool successed, const QList &results, const QJsonDocument &conditions, const QString &errorMsg){ /*thread->quit();*/}, Qt::QueuedConnection ); connect(task, &AISearchTask::finished, this, [=](bool successed, const QList &results, const QJsonDocument &conditions, const QString &errorMsg){ thread->quit(); if (!successed) { QList list; if(searchTextContentFinish){ list = getContentResult(); ModelController::globalInstance()->SearchContentLoad(list); } searchContentFinish = true; return; } aiConditionDescription = conditions; auto job = UKUI::IDM::FileInfoJob::fromPaths(results); QList> realConditions = searchConditions; QString currentSearhPath = ModelController::globalInstance()->getCurrentSearchpath(); if(!currentSearhPath.isEmpty()){ auto condition = std::shared_ptr(new Condition(Condition::SearchLocation)); condition->setConditionMatchRule(Condition::NameOrContent); condition->setSearchLocationCondition(true,currentSearhPath); condition->setIsAndCondition(false); realConditions.append(condition); } job->setConditions(realConditions); job->setAutoDelete(true); connect(this, &SearchResultModel::aboutToUpdate, job, &UKUI::IDM::FileInfoJob::cancel); connect(ModelController::globalInstance(), &ModelController::cancelSearchRequest, job, &UKUI::IDM::FileInfoJob::cancel); connect(job, &UKUI::IDM::FileInfoJob::finished, this, [=]{ contentMatchFiles = job->getInfos(); QList list; searchContentFinish = true; if(searchTextContentFinish){ list = getContentResult(); ModelController::globalInstance()->SearchContentLoad(list); } QSet uris; if (searchInSpace) { for (auto path : searchInSpacePaths) { QUrl url = QUrl::fromLocalFile(path); uris << url.toEncoded(); } QList> spacesMatchedInfos; for (auto info : contentMatchFiles) { if (uris.contains(info->uri())) { spacesMatchedInfos << info; } } contentMatchFiles = spacesMatchedInfos; } if (contentMatchFiles.count() > 0) { setHasResult(true); } setLoading(false); }, Qt::BlockingQueuedConnection); job->start(); }); if(ModelController::globalInstance()->getAinotAvailabletype() == ModelController::allFit) { thread->start(); }else{ searchContentFinish = true; } // if (!aiSearch) { if (!searchInSpace) { searchTask->cancel(); searchTask->setConditionsDescription(conditionsDescription); searchTask->start(); } else { searchTask->setConditionsDescription(conditionsDescription); searchTask->setNeedSearchFile(false); searchTask->start(); QList> infos; for (auto path:this->searchInSpacePaths) { auto info = UKUI::IDM::FileInfo::fromPath(path); infos<> allConditions; QList> conditions; allConditions << reversedConditions << searchConditions; // for (auto condition:allConditions) { // if(condition->type() == Condition::FileName){ // conditions<setConditions(allConditions); //job->setLabels(labels); job->setAutoDelete(true); connect(this, &SearchResultModel::aboutToUpdate, job, &UKUI::IDM::FileInfoJob::cancel); connect(ModelController::globalInstance(), &ModelController::cancelSearchRequest, job, &UKUI::IDM::FileInfoJob::cancel); connect(job, &UKUI::IDM::FileInfoJob::finished, this, [=]{ nameMatchFiles = job->getInfos(); int i = 0; for (auto fileinfo:nameMatchFiles) { spaceBatchFileinfoList<searchLoadBatch(false); spaceBatchFileinfoList.clear(); }else if(i == nameMatchFiles.size()){ Q_EMIT searchTask->searchLoadBatch(true); } } }, Qt::BlockingQueuedConnection); job->start(); } /* } else { // fixme: 调用ai接口获取搜索数据,并且获取语义配置的json文件 qDebug() << "start ai search"; auto thread = new QThread; auto task = new AISearchTask(searchKeyword); // 将输入的关键词解析成条件再进行搜索 task->setPharseConditions(true); task->moveToThread(thread); connect(thread, &QThread::started, task, &AISearchTask::doQuery); connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(this, &SearchResultModel::aboutToUpdate, task, &AISearchTask::cancel); connect(task, &AISearchTask::finished, this, [=](bool successed, const QList> &results, const QJsonDocument &conditions, const QString &errorMsg){ thread->quit(); if (!successed) { return; } aiConditionDescription = conditions; auto job = UKUI::IDM::FileInfoJob::fromInfos(results); job->setAutoDelete(true); connect(this, &SearchResultModel::aboutToUpdate, job, &UKUI::IDM::FileInfoJob::cancel); connect(job, &UKUI::IDM::FileInfoJob::finished, this, [=]{ this->beginResetModel(); contentMatchFiles = job->getInfos(); if (contentMatchFiles.count() > 0) { setHasResult(true); } this->endResetModel(); setLoading(false); }, Qt::BlockingQueuedConnection); job->start(); }); thread->start(); }*/ } void SearchResultModel::saveSearchAsSpace(const QString &spaceName) { // fixme: 当搜索结未完成时不应该保存 QList> infos; infos << nameMatchFiles; QList> nameMatcheFilesWithoutfilename; for(auto info:contentMatchFiles) { if(!nameMatchFiles.contains(info)) { nameMatcheFilesWithoutfilename<> conditions; conditions << reversedConditions; conditions << searchConditions; auto conditonsDescription = Condition::pharseConditions(conditions).toJsonObject(); QJsonDocument doc(conditonsDescription); QString jsonStr = doc.toJson(QJsonDocument::Indented); QStringList paths; for (auto info:infos) { paths<path(); } ModelController::globalInstance()->onSpaceCreateRequestWithFiles(spaceName, jsonStr, true, paths); } bool SearchResultModel::getHasResult() const { return hasResult; } void SearchResultModel::setHasResult(bool newHasResult) { if (hasResult == newHasResult) return; hasResult = newHasResult; Q_EMIT hasResultChanged(); } QList SearchResultModel::getContentResult() { //取智能内容识别和文本内容并集 QList list; QList> result; result<path(); if(searchInSpace && !this->searchInSpacePaths.contains(path)){//search in space continue; } QStringList fileinfolist; fileinfolist<uri()<modifyDateString()<fileType()<sizeFormat()<displayName() <path()<iconName()<mimeType()<size())<timeModifiedMsec()); list.append(fileinfolist); } QSet set(list.begin(), list.end()); return set.values(); } const QStringList &SearchResultModel::getLabels() const { return labels; } void SearchResultModel::setLabels(const QStringList &newLabels) { if (labels == newLabels) return; labels = newLabels; Q_EMIT labelsChanged(); } const QStringList &SearchResultModel::getcurrentLabels() const { return currentlabels; } void SearchResultModel::setcurrentLabels(const QStringList &newLabels) { if (currentlabels == newLabels) return; currentlabels = newLabels; } bool SearchResultModel::getLoading() const { return loading; } void SearchResultModel::setLoading(bool newLoading) { if (loading == newLoading) return; loading = newLoading; Q_EMIT loadingChanged(); } bool SearchResultModel::getAiSearch() const { return aiSearch; } void SearchResultModel::setAiSearch(bool newAiSearch) { if (aiSearch == newAiSearch) return; aiSearch = newAiSearch; Q_EMIT aiSearchChanged(); } const QList > &SearchResultModel::getContentMatchFiles() const { return contentMatchFiles; } void SearchResultModel::setSearchInSpacePaths(const QStringList &paths) { searchInSpacePaths = paths; } const QList > &SearchResultModel::getNameMatchFiles() const { return nameMatchFiles; } peony-extensions/peony-intelligent-data-management-service/model/spacesfilesfetcher.cpp0000664000175000017500000003545315156143275030645 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #include "spacesfilesfetcher.h" #include "../database/databasemanager.h" #include "../condition/condition.h" #ifdef COMPLIE_AS_PROJECT #include "file/fileinfojob.h" #include "file/fileinfomanager.h" #include "modelcontroller.h" #endif #include #include #include #include #include #include #include #include SpacesFilesFetcher::SpacesFilesFetcher(const QString &dbName,const QString &filedbname, QObject *parent) : QObject(parent), dbName(dbName),filedbName(filedbname) { delayer = new QTimer(this); delayer->setInterval(300); delayer->setSingleShot(true); connect(delayer, &QTimer::timeout, this, &SpacesFilesFetcher::handleFilesCreatedInternal); } QStringList SpacesFilesFetcher::getAllSpacesName() { QReadLocker r(&rwLock); return spaces.values(); } int SpacesFilesFetcher::getSpaceIdByName(const QString &spaceName) { QReadLocker r(&rwLock); return spaces.key(spaceName, -1); } QString SpacesFilesFetcher::getSpaceNameById(int spaceId) { QReadLocker r(&rwLock); return spaces.value(spaceId); } QStringList SpacesFilesFetcher::getFilesInSpace(const QString &spaceName, bool isUserAdded) { int spaceId = getSpaceIdByName(spaceName); return getFilesInSpaceById(spaceId, isUserAdded); } QStringList SpacesFilesFetcher::getFilesInSpaceById(int spaceId, bool isUserAdded) { QReadLocker r(&rwLock); return isUserAdded? userAddedInfos.value(spaceId): infos.value(spaceId); } QList> SpacesFilesFetcher::getSpaceconditon(QString spaceName){ QReadLocker r(&rwLock); int spaceId = getSpaceIdByName(spaceName); return spacesConditions.value(spaceId); } QStringList SpacesFilesFetcher::getSpacelabels(QString spaceName){ QReadLocker r(&rwLock); int spaceId = getSpaceIdByName(spaceName); return spacesLabels.value(spaceId); } void SpacesFilesFetcher::DeleteTargetFiles(QList files) { QStringList allSpaceName = getAllSpacesName(); QMap m_delete; for(auto spaceName:allSpaceName){ QStringList allFiles; QStringList nowSpaceFiles; int spaceId = getSpaceIdByName(spaceName); auto useraddfiles = getFilesInSpace(spaceName,false); auto autoaddfiles = getFilesInSpace(spaceName,true); allFiles<spaceRemovedWithFilesRequest(spaceId,infosToBeRemoved); // 清空视图文件 //Q_EMIT this->requestClearSpace(spaceId); Q_EMIT this->requestClearSpace(spaceId); return; } // todo: 和旧的条件做比较,尝试尽量减少不必要的遍历和匹配 bool needClearAndResearch = true; { QWriteLocker w(&rwLock); spacesConditions.insert(spaceId, conditions); spacesLabels.insert(spaceId, labels); Q_EMIT handleSpaceConditionsChangedDone(spaceId); } if (needClearAndResearch) { // 清空空间内的文件,并且发送请求重新搜索的信号 // DatabaseManager dbManager; // for (auto info : infosToBeRemoved) { // dbManager.removeFileFromSpace(info, spaceId); // } Q_EMIT ModelController::globalInstance()->spaceRemovedWithFilesRequest(spaceId,infosToBeRemoved); { QWriteLocker w(&rwLock); this->infos[spaceId] = QStringList(); } Q_EMIT this->requestRescanSpaceWithConditionsDescritpion(spaceId, conditionsDescription); } else { // 对当前的信息做过滤,得出需要移除的文件,请求更新 } #endif } void SpacesFilesFetcher::handleFileCreated(const QString &newFile) { if (movedFiles.contains(newFile)) { // 搜索索引服务发出的信号会和peony发出的信号有冲突,这里为了解决冲突加上此流程 movedFiles.remove(newFile); return; } if (deletedFilesToHandle.contains(newFile)) { deletedFilesToHandle.remove(newFile); } else { createdFilesToHandle.insert(newFile); } if (!delayer->isActive()) { delayer->start(); } } void SpacesFilesFetcher::handleFileIndexCreated(const QString &newFile) { needChecklabel = true; if (movedFiles.contains(newFile)) { // 搜索索引服务发出的信号会和peony发出的信号有冲突,这里为了解决冲突加上此流程 movedFiles.remove(newFile); return; } if (deletedFilesToHandle.contains(newFile)) { deletedFilesToHandle.remove(newFile); } else { createdFilesToHandle.insert(newFile); } if (!delayer->isActive()) { delayer->start(); } } void SpacesFilesFetcher::handleFileDeleted(const QString &deletedFile) { // fixme: 同步数据库 if (createdFilesToHandle.contains(deletedFile)) { createdFilesToHandle.remove(deletedFile); } else { deletedFilesToHandle.insert(deletedFile); } if (!delayer->isActive()) { delayer->start(); } } void SpacesFilesFetcher::handleFileMoved(const QString &oldFile, const QString &newFile) { movedFiles.remove(oldFile); movedFiles.insert(newFile); createdFilesToHandle.insert(newFile); deletedFilesToHandle.insert(oldFile); if (!delayer->isActive()) { delayer->start(); } } void SpacesFilesFetcher::handleAfterFilesReloaded(const QStringList &infos, int spaceId) { this->infos.remove(spaceId); this->infos.insert(spaceId, infos); QString spacename = getSpaceNameById(spaceId); Q_EMIT updateSpacefileinfo(spacename); DatabaseManager dbManager; for (auto info : infos) { if (info.isEmpty()) { bool ok = dbManager.addFileToSpace(info, spaceId); if(!ok){ return; } } else { dbManager.addFileToSpace(info, spaceId); } } } void SpacesFilesFetcher::handleAfterFilesReloadedBycreate(const QStringList &infos, int spaceId){ this->infos.insert(spaceId, infos); QString spacename = getSpaceNameById(spaceId); Q_EMIT spaceCreateFinish(spacename,infos); DatabaseManager dbManager; for (auto info : infos) { if (info.isEmpty()) { bool ok = dbManager.addFileToSpace(info, spaceId); if(!ok){ return; } } else { dbManager.addFileToSpace(info, spaceId); } } } void SpacesFilesFetcher::handleSpaceRemoved(int spaceId, const QStringList &infosToBeRemoved) { DatabaseManager dbManager; for (auto info : infosToBeRemoved) { dbManager.removeFileFromSpace(info, spaceId); } } void SpacesFilesFetcher::handleFilesCreatedInternal() { #ifdef COMPLIE_AS_PROJECT QSet targetFiles; QSet targetDeletedFiles; createdFilesToHandle.swap(targetFiles); deletedFilesToHandle.swap(targetDeletedFiles); { QReadLocker r(&rwLock); for (auto spaceId : spaces.keys()) { if(!getSpaceStatus(spaceId))//当自动更新关闭则跳过 continue; auto conditions = spacesConditions.value(spaceId); auto labels = spacesLabels.value(spaceId); if (conditions.isEmpty() && labels.isEmpty()) { // 空白空间跳过匹配流程 continue; } auto job = UKUI::IDM::FileInfoJob::fromPaths(targetFiles.values()); job->setConditions(conditions); job->setLabels(labels); // 需要处理文件内容条件 job->setHandleContentCondition(true); job->setAutoDelete(true); connect(job, &UKUI::IDM::FileInfoJob::finished, this, [=]{ auto targetInfos = job->getInfos(); if (!targetInfos.isEmpty()) { Q_EMIT this->handleFilesCreatedProgressed(targetInfos, spaceId); DatabaseManager dbManager; auto spaceName = this->getSpaceNameById(spaceId); for (auto info : targetInfos) { auto path = info->path(); bool ok = dbManager.addFileToSpace(path, spaceName); if(ok){ updateSpaceFiles(spaceName,path,false,Add); } } } }, Qt::BlockingQueuedConnection); // 如果在处理变更时空间条件发生改变,则取消此次处理,这里可能会丢失一部分数据,需要考虑结合其他场景优化 connect(this, &SpacesFilesFetcher::handleSpaceConditionsChangedDone, job, [=](int changedSpaceId){ if (changedSpaceId == spaceId) { job->cancel(); } }); job->start(); } } if (!targetDeletedFiles.isEmpty()) { DeleteTargetFiles(targetDeletedFiles.values()); DatabaseManager dbManager; for (auto file : targetDeletedFiles) { dbManager.removeFileFromAllSpace(file); } } #endif } void SpacesFilesFetcher::fetch() { DatabaseManager manager(dbName); auto allSpaces = manager.getAllSpaceNames(); for (auto space : allSpaces) { int id = manager.getSpaceIdByName(space); spaces.insert(id, space); // 获取条件配置 // 加载标签和条件配置 QString jsonPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/spaces/" + QString::number(id) + ".json"; QFile file(jsonPath); file.open(QFile::ReadOnly|QFile::Text); QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); file.close(); auto conditions = Condition::generateContitionsFromDescription(doc.object()); auto labels = Condition::getLabelsFromDescription(doc.object()); spacesConditions.insert(id, conditions); spacesLabels.insert(id, labels); bool status = manager.getSpacestatus(space); spaceStatus.insert(id,status); } QStringList filesNeedRemove; auto userAddedFiles = manager.getAllFilesFromSpacesBySpaceIds(true); for (int spaceId : userAddedFiles.keys()) { if (!this->spaces.keys().contains(spaceId)) { // fixme: 清除多余的数据 continue; } QStringList files = userAddedFiles.value(spaceId); QList> fileinfoList; QStringList infoList; for (auto file : files) { infoList << file; auto info = UKUI::IDM::FileInfo::fromPath(file,true); fileinfoList << info; } userAddedFileInfos.insert(spaceId, fileinfoList); userAddedInfos.insert(spaceId, infoList); } auto autoAddedFiles = manager.getAllFilesFromSpacesBySpaceIds(false); for (int spaceId : autoAddedFiles.keys()) { if (!this->spaces.keys().contains(spaceId)) { // fixme: 清除多余的数据 continue; } QStringList files = autoAddedFiles.value(spaceId); QStringList infoList; QList> fileinfoList; for (auto file : files) { infoList << file; auto info = UKUI::IDM::FileInfo::fromPath(file,true); fileinfoList << info; } infos.insert(spaceId, infoList); fileInfos.insert(spaceId,fileinfoList); } Q_EMIT fetched(true); // // 在fetched之后,此处的缓存不再使用,数据转到各个model中处理 // userAddedInfos.clear(); // infos.clear(); } peony-extensions/peony-intelligent-data-management-service/model/modelcontroller.h0000664000175000017500000001700215156143275027642 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #ifndef MODELCONTROLLER_H #define MODELCONTROLLER_H #include #include #include #include #include "model/searchresultmodel.h" #include "file/fileinfo.h" #include "ai/ailabelhelper.h" #include "spacessettings.h" #include "file/fileinfomanager.h" #if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__)|| defined(_M_ARM64) #define ARCH_AVAILABLE_AI #endif class SpacesFilesFetcher; class Condition; class ModelController : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface","org.peony.idm.modelcontroller") public: enum AIConditionType{ allFit=0, aiIndexNotFit, aiSubsystemNotFit }; static ModelController *globalInstance(); explicit ModelController(const QString &dbName = "peony-idm.db", const QString &filedbname = "peony-idm-file.db",QObject *parent = nullptr); ~ModelController(); void spaceCreateFinish(QString spaceName, QStringList fileinfos); public Q_SLOTS: //加载所有空间 void startLoadAllSpaces(); //获取空间名字 QStringList getAllSpacesName(); //根据空间名获取spaceid int getSpaceIdByName(const QString &spaceName); //创建空白空间请求 bool onSpaceCreateRequest(const QString &spaceName, const QString &conditionDescription, bool isCreatedFromSearch); //创建带有文件的空间 从搜索 bool onSpaceCreateRequestWithFiles(const QString &spaceName, const QString &conditionDescription, bool isCreatedFromSearch, QStringList infos); bool renameSpace(int spaceId, const QString &spaceName); //条件变化时点击保存 空间内容重新开始搜素 void editSpaceCondition(QString spacename,QString conditons); bool removeSpace(int spaceId); //删除空间需要 告知删除空间的文件 void spaceRemovedWithFiles(int spaceId, const QStringList &filesToBeRemoved); //根据空间名获取对应空间中的文件 QStringList getFilesInSpaceByName(QString spacename, bool isUserAdded = false); //手动添加文件到空间中 bool userAddFilesToSpace(QStringList paths,QString Spacename); //开始搜索关键字 void startsearch(QString keyword,QString SpaceName,QString ClientId); //搜索完成通知插件更新文件名文件信息 void fileReload(QList infos); //搜索完成通知插件更新文件内容搜索文件信息 void SearchContentLoad(QList infolist); void fileTextContentLoad(QList infolist); //搜索界面条件更新 通知插件根据条件筛选文件 void updateSearchCondition(QString conditions); //获取空间条件 QString getConditionsBySpaces(QString spacename); //更新空间自动更新状态 bool updateSpacestatus(QString spacename,bool status); //获取空间自动更新状态 bool getSpacestatus(QString spacename); //根据搜素关键字获取相似标签 没有关键字则获取top50 QStringList getIntelligentLabel(QString keyword=""); QString getFileSummery(QString filepath); //通知空间重新更新文件视图界面 void spaceUpdatefinish(QString spacename); //有符合要求的文件 通知空间自动更新 包括新增和删除 void filesCreated(const QList> &newFiles, int spaceId); void fileDeleted(QMap deleteSpacesFile); //移除文件从空间中 bool removeFileFromSpace(const QString &SpacesName, const QStringList &filepaths); //取消搜索 通知搜索线程 取消搜索流程 void cancelSearch(); //根据标签重新搜索 void reSearchWithLabels(QString keyWord, QStringList labels, QString SpaceName); //检测AI环境接口 void requestShowAISubsystemDialog(); bool isAisubsysteminited(); int getAinotAvailabletype(); bool isUserAddFile(const QString &spaceName,const QString &FileName); void setCurrentSearchPath(const QString &path); QString getCurrentSearchpath(); QStringList getFileLabels(QString filepath); qint64 getSpaceAccesstime(QString spaceName); qint64 getSpaceModifytime(QString spaceName); void setSpacesSortConfig(int spaceId, int sortType, int sortOrder, int userAddedFirst); QVariantHash getSpacesSortConfig(int spaceId); void userAddFilesToSpaceBySpaceplugin(const QStringList &filePath,const QString &spaceName,bool isUserAdd); void editSpaceConditionBySpaceplugin(const QString &uri); void fileReloadByBatch(QList infolist,bool finish = false); Q_SIGNALS: void allModelCreated(); void spaceRemoved(int spaceId); // 空间条件更新 返回结果给前端 void filesReloaded(const QStringList &infos, int spaceId); void filesReloadedByCreate(const QStringList &infos, int spaceId); void spaceConditionsChanged(int spaceId, const QString &spaceName, const QVariant &conditionsDescription); void cancelSpaceSearch(int spaceId); void spaceRemovedWithFilesRequest(int spaceId, const QStringList &filesToBeRemoved); void setSpaceConditionRequest(int spaceId, const QString &spaceName); void SpaceCreatefinished(const QString &spaceName,const QStringList &filelist); void fileDeletedRequest(const QString &deletedFile); void fileCreateRequest(const QString &newFile); void fileMovedRequest(const QString &oldFile, const QString &newFile); void searchfileReload(QList infos,QString clientId); void searchKeywordChanged(QString keyword); void searchContentReload(QList infolist,QString clientId); void searchTextContentReload(QList infolist,QString clientId); void searchfileReloadByBatch(QList infolist,QString clientId,bool finish = false); void searchConditionChanged(QString conditions); void spacefileloadfinish(QString Spacename); void spacesFilesAutoInsert(QString Spacename,QStringList infos,bool needGotoUri = false); void spacesFilesAutoDelete(QMap deleteSpacesFile); void cancelSearchRequest(); void userAddFilesToSpaceBySpacepluginSignal(const QStringList &filePath,const QString &spaceName,bool isUserAdd); void editSpaceConditionBySpacepluginSignal(const QString &uri); void spaceDataChanged(const QString &spaceName,const QStringList &filelist); private Q_SLOTS: void initModels(); void handleRescanSpaceWithCondition(int spaceId,const QVariant &conditionsDescription); void handleFileEvent(UKUI::IDM::FileInfoManager::FileEventType type, const QString &arg1, const QString arg2); private: std::atomic_bool fetched; SpacesFilesFetcher *fetcher; QThread *thread; QList contentFileinfo; bool Aisearchfinish; bool searchfinish; QString m_clientId; QString m_currentPath; SearchResultModel* m_searchmodel; }; #endif // MODELCONTROLLER_H peony-extensions/peony-intelligent-data-management-service/model/spacesfilesfetcher.h0000664000175000017500000001035715156143275030306 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef SPACESFILESFETCHER_H #define SPACESFILESFETCHER_H #include #include #include #include #include "file/fileinfo.h" #include class Condition; class QTimer; class SpacesFilesFetcher : public QObject { enum OperationType { Add=1, Remove, }; friend class ModelController; friend class PreviewHelper; Q_OBJECT public: explicit SpacesFilesFetcher(const QString &dbName = "peony-idm.db", const QString &filedbname = "peony-idm-file.db",QObject *parent = nullptr); QStringList getAllSpacesName(); int getSpaceIdByName(const QString &spaceName); QString getSpaceNameById(int spaceId); // 仅在fetched阶段调用,fetched之后需要从FileItemModel中获取空间文件信息 QStringList getFilesInSpace(const QString &spaceName, bool isUserAdded = false); QStringList getFilesInSpaceById(int spaceId, bool isUserAdded = false); QList> getSpaceconditon(QString SpaceName); QStringList getSpacelabels(QString SpaceName); void DeleteTargetFiles(QListfiles); void updateSpaceFiles(QString SpaceName, QString file,bool isUserRemove ,SpacesFilesFetcher::OperationType type); bool getSpaceStatus(int spaceId); void updateSpaceStatus(int spaceId,bool statu); bool isUserAddFile(const QString &spaceName,const QString &FileName); Q_SIGNALS: void fetched(bool successed); void handleSpaceConditionsChangedDone(int spaceId); void handleFilesCreatedProgressed(const QList> &newFiles, int spaceId); void handleSpaceConditionsProgressed(bool needReload, const QList> &resultFiles, int spaceId); void requestRescanSpaceWithConditionsDescritpion(int spaceId, const QVariant &conditionsDescription); void requestClearSpace(int spaceId); void updateSpacefileinfo(QString spacename); QList> requestFilesInSpaceById(int spaceId); void FileDeleted(QMap deleteSpacesFiles); void spaceCreateFinish(QString spaceName,const QStringList &infosToBeRemoved); public Q_SLOTS: void fetch(); void updateSpaceConditions(int spaceId, const QString &spaceName, const QVariant &conditionsDescription); void handleFileCreated(const QString &newFile); void handleFileIndexCreated(const QString &newFile); void handleFileDeleted(const QString &deletedFile); void handleFileMoved(const QString &oldFile, const QString &newFile); void handleAfterFilesReloaded(const QStringList &infos, int spaceId); void handleAfterFilesReloadedBycreate(const QStringList &infos, int spaceId); void handleSpaceRemoved(int spaceId, const QStringList &infosToBeRemoved); protected Q_SLOTS: void handleFilesCreatedInternal(); private: QReadWriteLock rwLock; QTimer *delayer = nullptr; QSet createdFilesToHandle; QSet deletedFilesToHandle; QSet movedFiles; QMap spaces; QMap spacesLabels; QMap>> spacesConditions; QMap infos; QMap userAddedInfos; QMap>> fileInfos; QMap>> userAddedFileInfos; QMap spaceStatus; QString dbName; QString filedbName; bool needChecklabel = false; }; #endif // SPACESFILESFETCHER_H peony-extensions/peony-intelligent-data-management-service/model/modelcontroller.cpp0000664000175000017500000010534015156143275030200 0ustar fengfeng/* * Peony Intelligent Spaces Service * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: He JinQin * */ #include "modelcontroller.h" #include "spacesfilesfetcher.h" #include "database/databasemanager.h" #ifdef COMPLIE_AS_PROJECT #include "condition/condition.h" #include "search/searchtask.h" #include "file/fileinfojob.h" #include "ai/aisearchtask.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include"kylin-ai/common/common.h" #include #include #include #include #include #include "file/fileinfomanager.h" #include "file/fileinfo.h" #include #include #include"kylin-ai/common/common.h" #include "ai/aipreviewhelper.h" #include "global-settings.h" #include static ModelController *instance = nullptr; static bool modelInited = false; ModelController *ModelController::globalInstance() { if (!instance) { instance = new ModelController; } return instance; } ModelController::ModelController(const QString &dbName, const QString &filedbname ,QObject *parent) : QObject(parent) { fetched = false; fetcher = new SpacesFilesFetcher(dbName,filedbname); thread = new QThread; fetcher->moveToThread(thread); connect(thread, &QThread::started, fetcher, &SpacesFilesFetcher::fetch); connect(fetcher, &SpacesFilesFetcher::fetched, this, &ModelController::initModels, Qt::BlockingQueuedConnection); QDBusConnection sessionBus = QDBusConnection::sessionBus(); if (!sessionBus.registerService("com.peony.idm.service")) { qCritical() << "QDbus register service failed reason:" << sessionBus.lastError(); } if(!sessionBus.registerObject("/org/peony/idm/space/service",this,QDBusConnection::ExportAllSignals|QDBusConnection::ExportAllSlots)) { qCritical() << "peony-idm-transformer dbus register object failed reason:" << sessionBus.lastError(); } qRegisterMetaType>("QList"); qDBusRegisterMetaType>(); qRegisterMetaType>("QSharedPointer"); qRegisterMetaType>>("QList>"); qRegisterMetaType>("QMap"); qDBusRegisterMetaType>(); startLoadAllSpaces(); auto infoManager = UKUI::IDM::FileInfoManager::globalInstance(); connect(infoManager, &UKUI::IDM::FileInfoManager::fileChanged, this, &ModelController::handleFileEvent, Qt::UniqueConnection); } void ModelController::startLoadAllSpaces() { if (!thread->isRunning()) thread->start(); else { while (!fetched) { qApp->processEvents(); } } } ModelController::~ModelController() { disconnect(thread); disconnect(fetcher); disconnect(this); thread->quit(); // fixme: 在线程运行中退出会导致崩溃 thread->wait(); delete fetcher; delete thread; } void ModelController::initModels() { fetched = true; // auto spaces = fetcher->getAllSpacesName(); // for (auto space : spaces) { // int id = fetcher->getSpaceIdByName(space); // auto infos = fetcher->getFilesInSpaceById(id, true); // QStringList userAddedInfos = infos; // auto autoAddedInfos = fetcher->getFilesInSpaceById(id, false); // infos << autoAddedInfos; // } // 从ukui-search获取文件创建事件 // 定义 Unix 域套接字地址 const std::string serverUnixPath = "unix:abstract=/tmp/.kylin-ai-business-unix/" + std::to_string(getuid()) + "/DataManagement.sock"; // 连接到 Unix 域套接字 QDBusConnection connection = QDBusConnection::connectToPeer( QString::fromStdString(serverUnixPath), "FileIndexFinished"); // 检查连接是否成功 if (!connection.isConnected()) { qDebug() << "Failed to connect to Unix domain socket:" << connection.lastError().message(); } // 连接到 D-Bus 信号 connection.connect("", "/com/kylin/AiBusiness/DataManagement", "com.kylin.AiBusiness.DataManagement", "FileIndexFinished", this->fetcher, SLOT(handleFileIndexCreated(QString))); connect(UKUI::IDM::FileInfoManager::globalInstance(), &UKUI::IDM::FileInfoManager::remoteFileEvent, this, [=](int eventType, const QString &arg1, const QString &arg2){ switch (eventType) { case 1: case 101: Q_EMIT this->fileCreateRequest(arg1); break; case 102:{ UKUI::IDM::FileInfoManager::globalInstance()->fileChanged(UKUI::IDM::FileInfoManager::FileDeleted, arg1, nullptr); Q_EMIT this->fileDeletedRequest(arg1); break; } case 103: UKUI::IDM::FileInfoManager::globalInstance()->fileChanged(UKUI::IDM::FileInfoManager::FileMoved, arg1, arg2); Q_EMIT this->fileMovedRequest(arg1, arg2); break; case 104: UKUI::IDM::FileInfoManager::globalInstance()->fileChanged(UKUI::IDM::FileInfoManager::FileAttrChanged, arg1, nullptr); break; default: break; } }); // 在线程中处理新增文件和空间条件改变 connect(this, &ModelController::spaceRemovedWithFilesRequest, this->fetcher, &SpacesFilesFetcher::handleSpaceRemoved,Qt::QueuedConnection); connect(this, &ModelController::spaceConditionsChanged, this->fetcher, &SpacesFilesFetcher::updateSpaceConditions); connect(this, &ModelController::fileCreateRequest, this->fetcher, &SpacesFilesFetcher::handleFileCreated); connect(this, &ModelController::filesReloaded, this->fetcher, &SpacesFilesFetcher::handleAfterFilesReloaded); connect(this, &ModelController::fileDeletedRequest, this->fetcher, &SpacesFilesFetcher::handleFileDeleted); connect(this, &ModelController::fileMovedRequest, this->fetcher, &SpacesFilesFetcher::handleFileMoved); connect(this, &ModelController::filesReloadedByCreate, this->fetcher, &SpacesFilesFetcher::handleAfterFilesReloadedBycreate); connect(this->fetcher, &SpacesFilesFetcher::updateSpacefileinfo, this, &ModelController::spaceUpdatefinish); connect(this->fetcher, &SpacesFilesFetcher::requestRescanSpaceWithConditionsDescritpion, this, &ModelController::handleRescanSpaceWithCondition); connect(this->fetcher,&SpacesFilesFetcher::FileDeleted,this,&ModelController::fileDeleted); connect(this->fetcher,&SpacesFilesFetcher::spaceCreateFinish,this,&ModelController::spaceCreateFinish); // 线程结果返回信号 connect(this->fetcher, &SpacesFilesFetcher::handleFilesCreatedProgressed, this, &ModelController::filesCreated); } QStringList ModelController::getAllSpacesName(){ QStringList spaces = fetcher->getAllSpacesName(); return spaces; } int ModelController::getSpaceIdByName(const QString &spaceName){ int spaceid = fetcher->getSpaceIdByName(spaceName); return spaceid; } bool ModelController::onSpaceCreateRequest(const QString &spaceName, const QString &conditionDescription, bool isCreatedFromSearch) { return this->onSpaceCreateRequestWithFiles(spaceName, conditionDescription, isCreatedFromSearch, QStringList()); } bool ModelController::onSpaceCreateRequestWithFiles(const QString &spaceName, const QString &conditionDescription, bool isCreatedFromSearch, QStringList infos) { DatabaseManager manager; QString errMsg; QString realSpaceName; bool hasInitFiles = !infos.isEmpty(); if (manager.createSpace(spaceName, QStringList(), &errMsg, &realSpaceName)) { int spaceId = manager.getSpaceIdByName(realSpaceName); QJsonDocument doc = QJsonDocument::fromJson(conditionDescription.toUtf8()); auto jsonobj = doc.object(); QString jsonPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/spaces/" + QString::number(spaceId) + ".json"; QFile file(jsonPath); if (file.open(QFile::WriteOnly|QFile::Text)) { file.write(doc.toJson()); fetcher->rwLock.lockForWrite(); fetcher->spaces.insert(spaceId, realSpaceName); #ifdef COMPLIE_AS_PROJECT auto conditions = Condition::generateContitionsFromDescription(jsonobj); fetcher->spacesConditions.insert(spaceId, conditions); auto labels = Condition::getLabelsFromDescription(jsonobj); fetcher->spacesLabels.insert(spaceId, labels); #endif fetcher->rwLock.unlock(); qDebug() << "space created" << spaceId << realSpaceName; Q_EMIT filesReloadedByCreate(infos, spaceId); if (!isCreatedFromSearch) { this->handleRescanSpaceWithCondition(spaceId, jsonobj); } } sd_journal_print(LOG_ERR,"create space successful"); return true; } else { // kdk::KMessageBox questionBox; // questionBox.setIconPixmap(QPixmap (5,5)); // questionBox.setIcon(kdk::KMessageBox::Icon::Warning); // auto okButton = new QPushButton(tr("OK")); // questionBox.addButton(okButton, kdk::KMessageBox::AcceptRole); // questionBox.setText(tr("Failed to create space %1,%1 is existed").arg(spaceName)); // okButton->setProperty("isImportant", true); // questionBox.createWinId(); // questionBox.windowHandle()->setTransientParent(rootwindow); // questionBox.exec(); qDebug() << "create space failed" << realSpaceName<<" "<getSpaceNameById(spaceId); if (manager.renameSpace(oldSpaceName, spaceName, &errorMsg)) { fetcher->rwLock.lockForWrite(); fetcher->spaces[spaceId] = spaceName; fetcher->rwLock.unlock(); // Q_EMIT spaceNameUpdated(spaceId, spaceName); return true; } else { // kdk::KMessageBox questionBox; // questionBox.setIconPixmap(QPixmap (5,5)); // questionBox.setIcon(kdk::KMessageBox::Icon::Warning); // auto okButton = new QPushButton(tr("OK")); // questionBox.addButton(okButton, kdk::KMessageBox::AcceptRole); // questionBox.setText(tr("Failed to rename space %1 to %2, %2 is existed.").arg(oldSpaceName).arg(spaceName)); // okButton->setProperty("isImportant", true); // questionBox.createWinId(); // questionBox.windowHandle()->setTransientParent(rootwindow); // questionBox.exec(); qDebug() <getSpaceNameById(spaceId); if (manager.removeSpace(spaceName, &errorMsg)) { fetcher->rwLock.lockForWrite(); fetcher->spaces.remove(spaceId); fetcher->rwLock.unlock(); qDebug() << "space removed" << spaceId; // 同时删除配置文件 QString jsonPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/spaces/" + QString::number(spaceId) + ".json"; QFile file(jsonPath); file.remove(); Q_EMIT spaceRemoved(spaceId); return true; } else { qCritical()<type() == Condition::ConditionMatchRule) { needcontent = condition->getConditionMatchRule() == 0?true:false; } if(condition->type() == Condition::FileName){ keywords = condition->getFileNameKeywords(); fileContentObj["condition type"] = int(Condition::FileContent); fileContentObj["content"] = keywords.first(); fileContentObj["orCondition"] = false; aijsonObj[QString::number(i)] = fileContentObj; } } if (needcontent && !keywords.isEmpty()) { if (true) { // if(!condition->isAndCondition()) // isAndCondition = false; auto aiSearchTask = new AISearchTask(keywords.first()); aiSearchTask->setFilterConditionDescription(aijsonObj); aiSearchTask->setSpaceId(spaceId); auto aiSearchThread = new QThread; aiSearchTask->moveToThread(aiSearchThread); connect(aiSearchThread, &QThread::started, aiSearchTask, &AISearchTask::doQuery); connect(aiSearchThread, &QThread::finished, aiSearchThread, &QThread::deleteLater); connect(aiSearchTask, &SearchTask::destroyed, aiSearchThread, &QThread::quit); connect(aiSearchTask, &AISearchTask::finisheds, this, [=](bool successed, const QList &results, const QJsonDocument &conditions, const QString &errorMsg){ /*thread->quit();*/}, Qt::QueuedConnection ); connect(aiSearchTask, &AISearchTask::finished, this, [=,this](bool successed, const QList &result){ if (successed) { QList> infos; for (auto path:result) { auto info = UKUI::IDM::FileInfo::fromPath(path); infos<setSpaceId(spaceId); job->setAutoDelete(true); connect(this, &ModelController::cancelSpaceSearch, job, &UKUI::IDM::FileInfoJob::handleCancelWithSpaceId); connect(this, &ModelController::spaceRemoved, job, &UKUI::IDM::FileInfoJob::handleCancelWithSpaceId); connect(job, &UKUI::IDM::FileInfoJob::finished, this, [=,this](){ auto infos = job->getInfos(); QList realResult; for (QSharedPointer info:infos) { auto path = info->path(); realResult< set(realResult.begin(), realResult.end()); Q_EMIT this->filesReloaded(set.values(), spaceId); }else{ contentFileinfo = realResult; //或条件当AI搜索结束后 普通搜索还没结束暂不更新UI //Q_EMIT this->filesReloaded(realResult, spaceId); } Aisearchfinish = true; }, Qt::BlockingQueuedConnection); job->start(); }else{ Aisearchfinish = true; } // 安全删除 aiSearchThread->quit(); aiSearchTask->deleteLater();; }); // 如果此空间的条件被改变或者被编辑则取消此次search connect(this, &ModelController::cancelSpaceSearch, aiSearchTask, &AISearchTask::cancelWithSpaceId); connect(this, &ModelController::spaceRemoved, aiSearchTask, &AISearchTask::cancelWithSpaceId); if(getAinotAvailabletype() == allFit){ Aisearchfinish = false; aiSearchThread->start(); } // if(isAndCondition) // return; } } // fixme: 操作完成后释放资源 auto searchTask = new SearchTask; searchTask->setSpaceId(spaceId); searchTask->setConditionsDescription(conditionsDescription); QThread *searchThread = new QThread; searchTask->moveToThread(searchThread); connect(searchThread, &QThread::started, searchTask, &SearchTask::start); connect(searchThread, &QThread::finished, searchThread, &QThread::deleteLater); connect(searchTask, &SearchTask::destroyed, searchThread, &QThread::quit); connect(searchTask, &SearchTask::finished, this, [=,this](){ auto result = searchTask->resultInfos; result << searchTask->contentResultInfos; auto job = UKUI::IDM::FileInfoJob::fromInfos(result); job->setSpaceId(spaceId); job->setAutoDelete(true); connect(this, &ModelController::cancelSpaceSearch, job, &UKUI::IDM::FileInfoJob::handleCancelWithSpaceId); connect(this, &ModelController::spaceRemoved, job, &UKUI::IDM::FileInfoJob::handleCancelWithSpaceId); connect(job, &UKUI::IDM::FileInfoJob::finished, this, [=,this](){ searchfinish = true; auto infos = job->getInfos(); QList realResult; for (QSharedPointer info:infos) { auto path = info->path(); realResult< set(realResult.begin(), realResult.end()); Q_EMIT this->filesReloaded(set.values(), spaceId); }else{ contentFileinfo = realResult; if(!needcontent){ Q_EMIT this->filesReloaded(contentFileinfo, spaceId); } //Q_EMIT this->filesReloaded(realResult, spaceId); } searchfinish = true; }, Qt::BlockingQueuedConnection); job->start(); searchThread->quit(); searchTask->deleteLater();; }); //如果此空间的条件被改变或者被编辑则取消此次search connect(this, &ModelController::cancelSpaceSearch, searchTask, &SearchTask::handleCancelWithSpaceId); connect(this, &ModelController::spaceRemoved, searchTask, &SearchTask::handleCancelWithSpaceId); searchThread->start(); #endif } void ModelController::handleFileEvent(UKUI::IDM::FileInfoManager::FileEventType type, const QString &arg1, const QString arg2) { using namespace UKUI::IDM; QStringList filePaths; filePaths.append(arg1); QString path = arg1; QStringList spaceNames = getAllSpacesName(); switch (type) { // fixme: 使用pluma等文本编辑器修改文件后无法监听到后续事件 case FileInfoManager::FileEventType::FileIgnoredAutomatic: { if (!arg2.isEmpty()) { // 文件夹改变事件,跳过 return; } auto info = UKUI::IDM::FileInfo::fromPath(path, false); for (auto name : spaceNames) { QStringList infos = getFilesInSpaceByName(name); if (infos.contains(path)) { auto infoJob = FileInfoJob::fromPaths(QStringList()<getInfos(); for (auto info : infos) { FileInfoManager::globalInstance()->addFileToWatcher(info->uri()); } delete infoJob; }); if(!QFile::exists(path)){ removeFileFromSpace(name,filePaths); } connect(infoJob, &FileInfoJob::errored, this, [=]{ // 删除文件 //removeFile(path); removeFileFromSpace(name,filePaths); }); infoJob->start(); } } break; } case FileInfoManager::FileEventType::UserAddFile: { auto info = FileInfo::fromPath(path); auto infoJob = FileInfoJob::fromPaths(QStringList()<start(); break; } case FileInfoManager::FileEventType::FileDeleted: { if (!arg2.isEmpty()) { // 文件夹改变事件,跳过 return; } QStringList spaceNames = getAllSpacesName(); for (auto name : spaceNames) { QStringList infos = getFilesInSpaceByName(name); if (infos.contains(path)) { removeFileFromSpace(name,filePaths); } } break; } case FileInfoManager::FileEventType::FileMoved: { QStringList spaceNames = getAllSpacesName(); QStringList newFilePathList; newFilePathList.append(arg2); for (auto name : spaceNames) { QStringList filelist = this->fetcher->getFilesInSpace(name,true); QStringList infos = getFilesInSpaceByName(name); bool isUserRemove = filelist.contains(path); if (infos.contains(path)) { removeFileFromSpace(name,filePaths); if(isUserRemove){ userAddFilesToSpace(newFilePathList,name); } } } break; } case FileInfoManager::FileEventType::FileAttrChanged: case FileInfoManager::FileEventType::FileModified: { if (!arg2.isEmpty()) { // 文件夹改变事件,跳过 return; } // 更新文件信息 auto info = UKUI::IDM::FileInfo::fromPath(path, false); for (auto name : spaceNames) { QStringList infos = getFilesInSpaceByName(name); if (infos.contains(path)) { auto infoJob = FileInfoJob::fromPaths(QStringList()<getInfos(); Q_EMIT spaceDataChanged(name,filePaths); delete infoJob; }); infoJob->start(); } } break; } default: break; } } QStringList ModelController::getFilesInSpaceByName(QString spacename, bool isUserAdded){ int id = fetcher->getSpaceIdByName(spacename); auto infos = fetcher->getFilesInSpaceById(id, true); auto autoAddedInfos = fetcher->getFilesInSpaceById(id, false); infos << autoAddedInfos; return infos; } bool ModelController::userAddFilesToSpace(QStringList paths,QString SpaceName) { // fixme: run in thread DatabaseManager manager; int spaceId = getSpaceIdByName(SpaceName); bool ok = true; for (auto path : paths) { QString errMsg; ok = manager.addFileToSpace(path, spaceId, true, &errMsg); if(ok){ handleFileEvent(UKUI::IDM::FileInfoManager::UserAddFile, path, nullptr); this->fetcher->updateSpaceFiles(SpaceName,path,true,SpacesFilesFetcher::OperationType::Add); } } return ok; } QString ModelController::getConditionsBySpaces(QString spacename){ int spaceId = getSpaceIdByName(spacename); // QString jsonPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/spaces/" + QString::number(spaceId) + ".json"; // QJsonDocument doc; // QFile jsonFile(jsonPath); // QString conditionobj; // if (jsonFile.open(QFile::ReadOnly | QFile::Text)) { // doc = QJsonDocument::fromJson(jsonFile.readAll()); // conditionobj = doc.toJson(QJsonDocument::Indented); // jsonFile.close(); // } QList> conditions = this->fetcher->getSpaceconditon(spacename); QStringList labels = this->fetcher->getSpacelabels(spacename); auto obj = Condition::pharseConditions(conditions,labels); QJsonDocument doc(obj.toJsonObject()); QString conditionsobj = doc.toJson(); return conditionsobj; } bool ModelController::updateSpacestatus(QString spacename,bool status){ DatabaseManager manager; QString errorMsg; QString realSpaceName = spacename; if (spacename.isEmpty()) {//适配创建默认无名字的空间时 auto currentSpaceNames = getAllSpacesName(); int count = 1; while (true) { auto currentSpaceName = tr("New Space %1").arg(count); realSpaceName = tr("New Space %1").arg(count-1); if (currentSpaceNames.contains(currentSpaceName)) { count++; } else { break; } } } int spaceId = getSpaceIdByName(realSpaceName); if (manager.updateSpacestatus(realSpaceName, status)) { this->fetcher->updateSpaceStatus(spaceId,status); return true; } else { qCritical()<fetcher->getSpaceStatus(spaceId); } bool ModelController::removeFileFromSpace(const QString &SpacesName, const QStringList &filepaths) { DatabaseManager manager; QString errorMsg; QStringList filelist = this->fetcher->getFilesInSpace(SpacesName,true); int spaceId = getSpaceIdByName(SpacesName); for (auto path : filepaths) { this->fetcher->updateSpaceFiles(SpacesName,path,true,SpacesFilesFetcher::OperationType::Remove); bool isUserRemove = filelist.contains(path); bool ok = manager.removeFileFromSpace(path,spaceId,isUserRemove,&errorMsg); if(!ok) return false; } Q_EMIT this->spacefileloadfinish(SpacesName); return true; } void ModelController::startsearch(QString keyword,QString SpaceName,QString ClientId){ m_clientId = ClientId; if (m_searchmodel) { delete m_searchmodel; } m_searchmodel = new SearchResultModel; if(!SpaceName.isEmpty()){ auto paths = getFilesInSpaceByName(SpaceName); m_searchmodel->setSearchInSpace(true); m_searchmodel->setSearchInSpacePaths(paths); }else{ m_searchmodel->setSearchInSpace(false); m_searchmodel->setSearchInSpacePaths(QStringList()); } m_searchmodel->setSearchKeyword(keyword); } void ModelController::reSearchWithLabels(QString keyword,QStringList Labels,QString SpaceName){ cancelSearch(); auto searchmodel = new SearchResultModel; if(!SpaceName.isEmpty()){ auto paths = getFilesInSpaceByName(SpaceName); searchmodel->setSearchInSpace(true); searchmodel->setSearchInSpacePaths(paths); }else{ searchmodel->setSearchInSpace(false); searchmodel->setSearchInSpacePaths(QStringList()); } searchmodel->setLabels(Labels); searchmodel->setSearchKeyword(keyword); } void ModelController::fileReload(QList infolist){ Q_EMIT searchfileReload(infolist,m_clientId); } void ModelController::SearchContentLoad(QList infolist){ Q_EMIT searchContentReload(infolist,m_clientId); } void ModelController::fileReloadByBatch(QList infolist,bool finish){ Q_EMIT searchfileReloadByBatch(infolist,m_clientId,finish); } void ModelController::fileTextContentLoad(QList infolist) { Q_EMIT searchTextContentReload(infolist,m_clientId); } void ModelController::updateSearchCondition(QString conditions){ Q_EMIT searchConditionChanged(conditions); } void ModelController::spaceUpdatefinish(QString spacename){ Q_EMIT spacefileloadfinish(spacename); } void ModelController::filesCreated(const QList> &infos, int spaceId){ QString spaceName = this->fetcher->getSpaceNameById(spaceId); QStringList spacefiles = getFilesInSpaceByName(spaceName,false); bool needGotoUri = spacefiles.isEmpty(); QStringList filelist; for (auto info:infos) { filelist<path(); } Q_EMIT spacesFilesAutoInsert(spaceName,filelist,needGotoUri); } void ModelController::fileDeleted(QMap deleteSpacesFile){ Q_EMIT spacesFilesAutoDelete(deleteSpacesFile); } void ModelController::cancelSearch(){ Q_EMIT cancelSearchRequest(); } void ModelController::spaceCreateFinish(QString spaceName,QStringList fileinfos){ Q_EMIT SpaceCreatefinished(spaceName,fileinfos); } void ModelController::requestShowAISubsystemDialog() { int type = getAinotAvailabletype(); if(type==1) QProcess::startDetached("ukui-control-center", {"-m", "search"}); else QProcess::startDetached("ukui-control-center", {"-m", "aisubsystem"}); } bool ModelController::isAisubsysteminited() { return is_ai_subsystem_inited(); } int ModelController::getAinotAvailabletype() { bool flage = false; const QByteArray id("org.ukui.search.settings"); if (QGSettings::isSchemaInstalled(id)) { auto settings = new QGSettings(id, QByteArray(), nullptr); QStringList keys = settings->keys(); if (keys.contains("aiIndexEnable")) { flage = settings->get("aiIndexEnable").toBool(); } delete settings; } if(!isAisubsysteminited()) return aiSubsystemNotFit; if(!flage) return aiIndexNotFit; return allFit; } bool ModelController::isUserAddFile(const QString &spaceName, const QString &FileName) { return this->fetcher->isUserAddFile(spaceName,FileName); } void ModelController::setCurrentSearchPath(const QString &path) { m_currentPath = path; } QString ModelController::getCurrentSearchpath() { return m_currentPath; } QStringList ModelController::getIntelligentLabel(QString keyword){ QStringList labels; bool isAiavailable = getAinotAvailabletype()==allFit ?true:false; if(!isAiavailable){ return labels; } AILabelHelper labelHelper; if (keyword.isEmpty()) { labels = labelHelper.getTopLabels(50); } else { labels = labelHelper.getSimilarLabels(keyword); } return labels; } QString ModelController::getFileSummery(QString filepath) { QString fileSummery; bool isAiavailable = getAinotAvailabletype()==allFit ?true:false; if(!isAiavailable){ return fileSummery; } AIPreviewHelper aiPreviewHelper; if (!filepath.isEmpty()) { fileSummery = aiPreviewHelper.getFileSummary(filepath); } return fileSummery; } QStringList ModelController::getFileLabels(QString filepath){ QStringList fileLabelList; bool isAiavailable = getAinotAvailabletype()==allFit ?true:false; if(!isAiavailable){ return fileLabelList; } AIPreviewHelper aiPreviewHelper; if (!filepath.isEmpty()) { fileLabelList = aiPreviewHelper.getFileLabels(filepath); } return fileLabelList; } qint64 ModelController::getSpaceAccesstime(QString spaceName) { int spaceId = fetcher->getSpaceIdByName(spaceName); QString configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); QString jsonPath = QString("%1/spaces/%2.json").arg(configPath).arg(spaceId); QFileInfo spaceConfigFile(jsonPath); auto createTime = spaceConfigFile.birthTime(); auto createSec = createTime.toSecsSinceEpoch(); return createSec; } qint64 ModelController::getSpaceModifytime(QString spaceName) { int spaceId = fetcher->getSpaceIdByName(spaceName); QString configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); QString jsonPath = QString("%1/spaces/%2.json").arg(configPath).arg(spaceId); QFileInfo spaceConfigFile(jsonPath); auto modifiedTime = spaceConfigFile.lastModified(); auto modifiedSec = modifiedTime.toSecsSinceEpoch(); return modifiedSec; } QVariantHash ModelController::getSpacesSortConfig(int spaceId) { QVariantHash values; values = SpacesSettings::globalInstance()->getSpacesSortConfig(spaceId); return values; } void ModelController::userAddFilesToSpaceBySpaceplugin(const QStringList &filePath, const QString &spaceName, bool isUserAdd) { Q_EMIT userAddFilesToSpaceBySpacepluginSignal(filePath,spaceName,isUserAdd); } void ModelController::editSpaceConditionBySpaceplugin(const QString &uri) { Q_EMIT editSpaceConditionBySpacepluginSignal(uri); } void ModelController::setSpacesSortConfig(int spaceId, int sortType, int sortOrder, int userAddedtype){ SpacesSettings::globalInstance()->setSpacesSortConfig(spaceId, sortType, sortOrder == Qt::AscendingOrder? 0: 1, userAddedtype); } peony-extensions/peony-intelligent-data-management-service/model/searchresultmodel.h0000664000175000017500000001755715156143275030202 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef SEARCHRESULTMODEL_H #define SEARCHRESULTMODEL_H #include #include #include #include "file/fileinfo.h" #include "condition/condition.h" #include "search/searchtask.h" class QTimer; class SearchResultModel : public QObject { Q_OBJECT public: static SearchResultModel *globalInstance(); explicit SearchResultModel(QObject *parent = nullptr); ~SearchResultModel(); bool getSearchInSpace() const; void setSearchInSpace(bool newSearchInSpace); const QString &getSearchKeyword() const; void setSearchKeyword(const QString &newSearchKeyword); const QList> &getSearchConditions() const; void setSearchConditions(const QList> &newSearchConditions); int getCurrentSpaceId() const; void setCurrentSpaceId(int newCurrentSpaceId); const QList> &getReversedConditions() const; const QList > &getNameMatchFiles() const; const QList > &getContentMatchFiles() const; void setSearchInSpacePaths(const QStringList &paths); bool getAiSearch() const; void setAiSearch(bool newAiSearch); bool getLoading() const; void setLoading(bool newLoading); const QStringList &getLabels() const; void setLabels(const QStringList &newLabels); const QStringList &getcurrentLabels() const; void setcurrentLabels(const QStringList &newLabels); bool getHasResult() const; void setHasResult(bool newHasResult); QList getContentResult(); Q_SIGNALS: void searchInSpaceChanged(); void searchKeywordChanged(); void searchConditionsChanged(); void currentSpaceIdChanged(); void updateSearchResult(); void aboutToUpdate(); void aiSearchChanged(); void loadingChanged(); void labelsChanged(); void hasResultChanged(); protected: protected Q_SLOTS: void delayUpdate(); void doUpdateInternal(); void saveSearchAsSpace(const QString &spaceName); private: QList> reversedConditions; bool searchInSpace = false; QString searchKeyword = nullptr; QList> searchConditions; QStringList labels; QStringList currentlabels; int currentSpaceId = -1; QJsonDocument aiConditionDescription; QList> currentContentMatchFiles; bool aiSearch = true; bool loading = false; bool hasResult = false; bool searchContentFinish = false; bool searchTextContentFinish = false; Q_PROPERTY(bool searchInSpace READ getSearchInSpace WRITE setSearchInSpace NOTIFY searchInSpaceChanged) Q_PROPERTY(QString searchKeyword READ getSearchKeyword WRITE setSearchKeyword NOTIFY searchKeywordChanged) //Q_PROPERTY(QList> searchConditions READ getSearchConditions WRITE setSearchConditions NOTIFY searchConditionsChanged) Q_PROPERTY(QStringList labels READ getLabels WRITE setLabels NOTIFY labelsChanged) Q_PROPERTY(int currentSpaceId READ getCurrentSpaceId WRITE setCurrentSpaceId NOTIFY currentSpaceIdChanged) Q_PROPERTY(bool aiSearch READ getAiSearch WRITE setAiSearch NOTIFY aiSearchChanged) Q_PROPERTY(bool loading READ getLoading WRITE setLoading NOTIFY loadingChanged) Q_PROPERTY(bool hasResult READ getHasResult WRITE setHasResult NOTIFY hasResultChanged) QList> nameMatchFiles; QList> contentMatchFiles; QList> textContentMatchFiles; QList> spaceBatchFileinfoList; QStringList searchInSpacePaths; QTimer *delayer = nullptr; int m_loadNameMatchFileCount = 0; bool m_loadNameMatchflefinish = false; int m_allCount = 0; SearchTask *searchTask = nullptr; bool aiIndexenable = false; bool isAiSubsystemavaliable = false; }; //class SearchResultProxyModel : public QSortFilterProxyModel //{ // Q_OBJECT //public: // static SearchResultProxyModel *globalInstance(); // explicit SearchResultProxyModel(); // bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; // bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; // bool getSearchInSpace() const; // void setSearchInSpace(bool newSearchInSpace); // const QString &getSearchKeyword() const; // void setSearchKeyword(const QString &newSearchKeyword); // const QList > getSearchConditions() const; // void setSearchConditions(const QList > &newSearchConditions); // Q_INVOKABLE void saveSearchAsSpace(const QString &spaceName); // Q_INVOKABLE void clearCondition(); // Q_INVOKABLE void updateSearchResult(); // int getCurrentSpaceId() const; // void setCurrentSpaceId(int newCurrentSpaceId); // /*! // * \brief setSearchInSpacePaths // * 搜索界面切换空间时设置的数据源,用于进行空间内数据匹配 // * \param paths // * 需要进行筛选的文件路径,一般是从空间页面的model获取 // */ // Q_INVOKABLE void setSearchInSpacePaths(const QStringList &paths); // bool getAiSearch() const; // void setAiSearch(bool newAiSearch); // bool getLoading() const; // void setLoading(bool newLoading); // const QStringList &getLabels() const; // void setLabels(const QStringList &newLabels); // const QStringList &getcurrentLabels() const; // void setcurrentLabels(const QStringList &newLabels); // bool getHasResult() const; // void setHasResult(bool newHasResult); // Q_INVOKABLE void setAiavaliabtype(int type); //Q_SIGNALS: // void searchInSpaceChanged(); // void searchKeywordChanged(); // void searchConditionsChanged(); // void currentSpaceIdChanged(); // void setSearchKeywordReqeust(const QString &keyword); // void aiSearchChanged(); // void loadingChanged(); // void labelsChanged(); // void hasResultChanged(); // //void searchend(QString path, QIcon icon,bool isuseadd,bool hasResult); //private: // SearchResultModel *sourceModel = nullptr; // int currentSpaceId = -1; // QStringList labels; // Q_PROPERTY(bool searchInSpace READ getSearchInSpace WRITE setSearchInSpace NOTIFY searchInSpaceChanged) // Q_PROPERTY(QString searchKeyword READ getSearchKeyword WRITE setSearchKeyword NOTIFY searchKeywordChanged) // Q_PROPERTY(QList> searchConditions READ getSearchConditions WRITE setSearchConditions NOTIFY searchConditionsChanged) // Q_PROPERTY(int currentSpaceId READ getCurrentSpaceId WRITE setCurrentSpaceId NOTIFY currentSpaceIdChanged) // Q_PROPERTY(bool aiSearch READ getAiSearch WRITE setAiSearch NOTIFY aiSearchChanged) // Q_PROPERTY(bool loading READ getLoading WRITE setLoading NOTIFY loadingChanged) // Q_PROPERTY(QStringList labels READ getLabels WRITE setLabels NOTIFY labelsChanged) // Q_PROPERTY(bool hasResult READ getHasResult WRITE setHasResult NOTIFY hasResultChanged) //}; #endif // SEARCHRESULTMODEL_H ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service_zh_HK.tspeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000002172315156143275033242 0ustar fengfeng AISearchTask Failed to create ai search session. 無法創建 ai 搜索工作階段。 Search task cancelled. 搜索任務已取消。 Condition File name contains "%1" 檔名包含「%1」 File name without "%1" 不帶「%1」的檔名 File type contains "%1" 檔案類型包含「%1」 By suffix 按後綴 Folder 資料夾 Plain Text 純文本 WPS Document WPS文件 PDF PDF格式 Image 圖像 Video 視頻 Audio 音訊 File type contains %1 檔案類型包含 %1 All Empty(0K) 空(0K) Tiny(0-16K) 小型(0-16K) Small(16K-1M) 小號(16K-1M) Medium(1M-128M) 中型(1M-128M) Big(128M-1G) 大(128M-1G) Large(1-4G) 大號(1-4G) Great(>4G) 很棒(>4G) File size contains %1 檔案大小包含 %1 File size without %1 檔案大小(不含 %1) File content match %1 檔案內容匹配 %1 File location contains "%1" 檔案位置包含「%1」 File location without "%1" 沒有%1 的檔案位置 Later than "%1" 晚於“%1” Earlier than "%1" 早於“%1” DatabaseManager New Space %1 新空間 %1 File %1 has areally been added to %2 檔案 %1 已真正添加到 %2 File %1 doesn't exsit in any space. 檔案 %1 不會在任何空間中消失。 Failed to get space %1's id from query 無法從查詢中獲取空間 %1 的 ID Failed to get %1's id from query 無法從查詢中獲取 %1 的 ID ModelController Failed to rename space %1 to %2, %2 is existed. 無法將空間 %1 重新命名為 %2,存在 %2。 Failed to remove space %1, error message: %2 無法刪除空間 %1,錯誤訊息:%2 New Space %1 新空間 %1 Failed to updatestatus space %1, error message: %2 無法更新狀態空間 %1,錯誤訊息:%2 QObject %1 %2 %1 %2 Intelligent Data Manager Introduction 智能數據管理員介紹 peony-intelligent-data-management-service 牡丹智能數據管理服務 exit 退出 UKUI::IDM::FileInfoJob Create Date: %1 創建日期:%1 Open Date: %1 開放日期:%1 peony-extensions/peony-intelligent-data-management-service/fileeventhandler.rep0000664000175000017500000000017015156143275027212 0ustar fengfeng#include class FileEventHandler { SLOT(void handleFileEvent(int eventType, QString arg1, QString arg2)) } peony-extensions/peony-intelligent-data-management-service/search/0000775000175000017500000000000015156143275024432 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/search/searchtask.h0000664000175000017500000000632715156143275026743 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: Yue Lan * */ #ifndef SEARCHTASK_H #define SEARCHTASK_H #include #include #include #include #include "condition/condition.h" #include "file/fileinfo.h" #include #include #define SEARCH_LOAD_BATCH 100 class QReadWriteLock; namespace UKUI { namespace IDM { class FileInfo; } } class SearchTask : public QObject { friend class SearchResultModel; friend class ModelController; Q_OBJECT public: explicit SearchTask(QObject *parent = nullptr); void setSpaceId(int spaceId); ~SearchTask(); const QVariant &getConditionsDescription() const; void setConditionsDescription(const QVariant &newConditionsDescription); QStringList getLabels(); void updateSearchDirs(); void setSearchPath(const QString path); void setNeedSearchFile(bool flag); Q_SIGNALS: void conditionsDescriptionChanged(); void searchFinished(bool successed); void searchContentFinished(bool successed); void searchWithLabelsFinished(bool successed); void finished(); void fileReload(QString uri, QString modified, QString filetype,QString size); void searchLoadBatch(bool finish); public Q_SLOTS: void start(); void cancel(); void handleCancelWithSpaceId(int spaceId); protected: void handleSearchResult(); private: UkuiSearch::UkuiSearchTask *ukuiSearchTask; UkuiSearch::DataQueue *queue; UkuiSearch::UkuiSearchTask *ukuiSearchContentTask; UkuiSearch::DataQueue *contentQueue; QList> resultInfos; QList> contentResultInfos; QDBusInterface* m_interface; bool needSearchFile = false; bool needSearchFileContent = false; bool hasContentSearchAndCondition = false; bool searchDone = false; bool searchContentDone = false; bool cancelled = false; int spaceId = -1; QReadWriteLock rwlock; QVariant conditionsDescription; QList> conditions; QList> andConditions; QList> orConditions; QStringList labels; QStringList indexDirs; QSet filesMatchedWithLabels; QString currentSearchpath; QString m_searchKeyword; Q_PROPERTY(QVariant conditionsDescription READ getConditionsDescription WRITE setConditionsDescription NOTIFY conditionsDescriptionChanged) }; #endif // SEARCHTASK_H peony-extensions/peony-intelligent-data-management-service/search/searchtask.cpp0000664000175000017500000003603715156143275027277 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #include "searchtask.h" #include "condition/condition.h" #include "file/fileinfojob.h" #include "model/modelcontroller.h" #include "ai/ailabelhelper.h" #include #include #include #include #include #include SearchTask::SearchTask(QObject *parent) : QObject{parent} { m_interface = new QDBusInterface("com.ukui.search.fileindex.service", "/org/ukui/search/privateDirWatcher", "org.ukui.search.fileindex", QDBusConnection::sessionBus(), this); // 检查服务是否可用 if (!m_interface->isValid()) { qWarning() << "DBus interface not valid:" << QDBusConnection::sessionBus().lastError().message(); } ukuiSearchTask = new UkuiSearch::UkuiSearchTask; ukuiSearchTask->setInformNum(100); ukuiSearchTask->setMaxResultNum(); ukuiSearchTask->initSearchPlugin(UkuiSearch::SearchProperty::SearchType::File); queue = ukuiSearchTask->init(); connect(ukuiSearchTask, &UkuiSearch::UkuiSearchTask::searchError, this, [=](){ Q_EMIT searchFinished(false); }); connect(ukuiSearchTask, &UkuiSearch::UkuiSearchTask::reachInformNum, this, &SearchTask::handleSearchResult); connect(ukuiSearchTask, &UkuiSearch::UkuiSearchTask::searchFinished, this, [=](){ qInfo() <<"finish search keyword:=="<setInformNum(100); ukuiSearchContentTask->setMaxResultNum(); ukuiSearchContentTask->initSearchPlugin(UkuiSearch::SearchProperty::SearchType::FileContent); contentQueue = ukuiSearchContentTask->init(); connect(ukuiSearchContentTask, &UkuiSearch::UkuiSearchTask::searchError, this, [=](size_t searchId, QString msg){ qCritical() << "search content failed," << searchId << msg; Q_EMIT searchContentFinished(false); searchContentDone = true; if (searchDone || !needSearchFile) { if (!cancelled) Q_EMIT finished(); } }); connect(ukuiSearchContentTask, &UkuiSearch::UkuiSearchTask::reachInformNum, this, &SearchTask::handleSearchResult); connect(ukuiSearchContentTask, &UkuiSearch::UkuiSearchTask::searchFinished, this, [=](){ handleSearchResult(); searchContentDone = true; Q_EMIT searchContentFinished(true); if (searchDone || !needSearchFile) { if (!cancelled) Q_EMIT finished(); } }); } void SearchTask::setSpaceId(int spaceId) { this->spaceId = spaceId; } SearchTask::~SearchTask() { ukuiSearchTask->stop(); queue->clear(); delete ukuiSearchTask; ukuiSearchContentTask->stop(); contentQueue->clear(); delete ukuiSearchContentTask; resultInfos.clear(); contentResultInfos.clear(); } const QVariant &SearchTask::getConditionsDescription() const { return conditionsDescription; } void SearchTask::setConditionsDescription(const QVariant &newConditionsDescription) { if (conditionsDescription == newConditionsDescription) return; needSearchFile = false; needSearchFileContent = true; hasContentSearchAndCondition = false; conditionsDescription = newConditionsDescription; auto newConditions = Condition::generateContitionsFromDescription(newConditionsDescription); auto newLabels = Condition::getLabelsFromDescription(newConditionsDescription); rwlock.lockForWrite(); andConditions.clear(); orConditions.clear(); conditions = newConditions; labels = newLabels; if (ukuiSearchTask->isSearching(UkuiSearch::SearchProperty::SearchType::File)) { ukuiSearchTask->stop(); } ukuiSearchTask->clearAllConditions(); if (ukuiSearchContentTask->isSearching(UkuiSearch::SearchProperty::SearchType::FileContent)) { ukuiSearchContentTask->stop(); } ukuiSearchContentTask->clearAllConditions(); UkuiSearch::SearchResultProperties properties = {UkuiSearch::SearchProperty::FilePath}; bool hasDateRangeCondition = false; bool hasSearchLocationCondition = false; updateSearchDirs(); int FileNameIndex = -1; int i = 0; for (auto condition : conditions) { bool shouldAddCondition = true; switch (condition->type()) { case Condition::FileName: { auto keywords = condition->getFileNameKeywords(); FileNameIndex = i; if (keywords.isEmpty()) { shouldAddCondition = false; break; }else{ m_searchKeyword = keywords.at(0); } if (condition->getFileNameIsContains()) { needSearchFile = true; for (auto keyword : keywords) { ukuiSearchTask->addKeyword(keyword); } } else{ needSearchFile = true; #ifdef UKUI_SEARCH_HAS_MATCH_ALL ukuiSearchTask->setMatchAllIfNoKeyword(true); #endif } break; } case Condition::FileSize: case Condition::FileType: { needSearchFile = true; #ifdef UKUI_SEARCH_HAS_MATCH_ALL ukuiSearchTask->setMatchAllIfNoKeyword(true); #endif break; } case Condition::FileContent: { auto contentKeyword = condition->getFileContentContent(); if (contentKeyword.isEmpty()) { shouldAddCondition = false; break; } if (condition->isAndCondition()) { hasContentSearchAndCondition = true; } needSearchFileContent = true; ukuiSearchContentTask->addKeyword(condition->getFileContentContent()); break; } case Condition::SearchLocation: { auto path = condition->getSearchLocationPath(); if (path.isEmpty()) { shouldAddCondition = false; break; } needSearchFile = true; //ukuiSearchTask->setMatchAllIfNoKeyword(true); if (condition->getSearchLocationIsContains()) { ukuiSearchTask->addSearchDir(path); ukuiSearchContentTask->addSearchDir(path); } break; } case Condition::DateRange: { // 在线程中查询时间信息 needSearchFile = true; #ifdef UKUI_SEARCH_HAS_MATCH_ALL ukuiSearchTask->setMatchAllIfNoKeyword(true); #endif hasDateRangeCondition = true; break; } case Condition::ConditionMatchRule: { needSearchFile = true; needSearchFileContent = condition->getConditionMatchRule()==0?true:false; break; } default: break; } if (condition->isAndCondition()) { andConditions << condition; } else { orConditions << condition; } i++; } if(needSearchFileContent && FileNameIndex !=-1){ auto nameCondition = conditions[FileNameIndex]; auto keywords = nameCondition->getFileNameKeywords(); ukuiSearchContentTask->addKeyword(keywords.first()); } if (hasContentSearchAndCondition) { // 如果包含了与逻辑的文件内容关键字条件,则不进行文件名搜索 needSearchFile = false; } rwlock.unlock(); // 根据条件对搜索task进行设置 if (hasDateRangeCondition) properties.append(UkuiSearch::SearchProperty::ModifiedTime); ukuiSearchTask->setResultProperties(UkuiSearch::SearchProperty::SearchType::File, properties); ukuiSearchContentTask->setResultProperties(UkuiSearch::SearchProperty::SearchType::FileContent, properties); ukuiSearchTask->setRecurse(true); ukuiSearchContentTask->setRecurse(true); // 清空旧条件的结果 queue->clear(); contentQueue->clear(); resultInfos.clear(); contentResultInfos.clear(); Q_EMIT conditionsDescriptionChanged(); } QStringList SearchTask::getLabels() { return labels; } void SearchTask::start() { cancelled = false; filesMatchedWithLabels.clear(); // 如果条件不完全,比如文件名条件未设置文件名,搜索路径未设置路径,则不需要执行搜索 if (!needSearchFile && !needSearchFileContent && labels.isEmpty()) { if (!cancelled) Q_EMIT finished(); return; } if (!labels.isEmpty()) { bool basicConditionsOnlyHasSearchLocation = true; for (auto condition : andConditions) { if (condition->type() != Condition::SearchLocation) { basicConditionsOnlyHasSearchLocation = false; } } for (auto condition : orConditions) { if (condition->type() != Condition::SearchLocation) { basicConditionsOnlyHasSearchLocation = false; } } // fixme标签搜索异步实现,并且和文件以及内容搜索取交集 AILabelHelper helper; QStringList files = helper.getFilesByLabels(labels); filesMatchedWithLabels = QSet(files.begin(), files.end()); if (!needSearchFile && !needSearchFileContent || basicConditionsOnlyHasSearchLocation) { for (auto path : filesMatchedWithLabels) { auto info = UKUI::IDM::FileInfo::fromPath(path); contentResultInfos << info; } Q_EMIT finished(); } } qInfo() <<"start search keyword:=="<startSearch(UkuiSearch::SearchProperty::SearchType::File); if (needSearchFileContent) ukuiSearchContentTask->startSearch(UkuiSearch::SearchProperty::SearchType::FileContent); } void SearchTask::cancel() { cancelled = true; if (needSearchFile) ukuiSearchTask->stop(); if (needSearchFileContent) ukuiSearchContentTask->stop(); resultInfos.clear(); contentResultInfos.clear(); } void SearchTask::handleCancelWithSpaceId(int spaceId) { if (spaceId == this->spaceId) { cancel(); deleteLater(); } } void SearchTask::handleSearchResult() { while (!queue->isEmpty()) { if(cancelled){ return; } auto item = queue->dequeue(); QString path = item.getValue(UkuiSearch::SearchProperty::FilePath).toString(); if (!labels.isEmpty()) { if (!filesMatchedWithLabels.contains(path)) continue; } QVariant timeVar = item.getValue(UkuiSearch::SearchProperty::ModifiedTime); if (timeVar.isValid()) { QDateTime modificationTimeDate = timeVar.toDateTime(); // fixme: 判断日期相关条件 } // fixme: 判断大小相关条件 // fixme: 判断类型相关条件 if (spaceId >= 0) { QFileInfo fileinfo(path); if (UKUI::IDM::FileInfoJob::infoMatchCondtions(fileinfo, andConditions, orConditions)) { auto info = UKUI::IDM::FileInfo::fromPath(path); //ModelController::globalInstance()->fileReload(info->uri(),info->modifyDateString(),info->fileType(),info->sizeFormat()); resultInfos << info; } } else { auto info = UKUI::IDM::FileInfo::fromPath(path); resultInfos << info; if(resultInfos.size() % SEARCH_LOAD_BATCH == 0){ Q_EMIT searchLoadBatch(false); } } } while (!contentQueue->isEmpty()) { if(cancelled){ return; } auto item = contentQueue->dequeue(); QString path = item.getValue(UkuiSearch::SearchProperty::FilePath).toString(); if (!labels.isEmpty()) { if (!filesMatchedWithLabels.contains(path)) continue; } QVariant timeVar = item.getValue(UkuiSearch::SearchProperty::ModifiedTime); if (timeVar.isValid()) { QDateTime modificationTimeDate = timeVar.toDateTime(); // fixme: 判断日期相关条件 } // fixme: 判断大小相关条件 // fixme: 判断类型相关条件 if (spaceId >= 0) { QFileInfo fileinfo(path); //file content result don't need check filename condition QList> realandConditions; QList> realOrConditions; for (auto condition:andConditions) { if(condition->type()!= Condition::FileName){ realandConditions<type()!= Condition::FileName){ realOrConditions<clearSearchDir(); //ukuiSearchContentTask->clearSearchDir(); QDBusReply indexDirsRpl = m_interface->call("currentSearchDirs"); if (indexDirsRpl.isValid()) { indexDirs = indexDirsRpl.value(); } if(currentSearchpath.isEmpty()){ ukuiSearchTask->addSearchDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); ukuiSearchContentTask->addSearchDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); } else{ ukuiSearchTask->addSearchDir(currentSearchpath); ukuiSearchContentTask->addSearchDir(currentSearchpath); } // for (auto dir:indexDirs) { // ukuiSearchTask->addSearchDir(dir); // } } void SearchTask::setSearchPath(const QString path) { currentSearchpath = path; } void SearchTask::setNeedSearchFile(bool flag) { needSearchFile = flag; } ././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service_ug.tspeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000002376615156143275033253 0ustar fengfeng AISearchTask Failed to create ai search session. ai ئىزدەش مەشغۇلاتىنى قۇرۇش مەغلۇب بولدى. Search task cancelled. ئىزدەش ۋەزىپىسى ئەمەلدىن قالدۇرۇلدى. Condition File name contains "%1" ھۆججەت نامى «٪1» نى ئۆز ئىچىگە ئالىدۇ File name without "%1" «٪1» بولمىغان ھۆججەت نامى File type contains "%1" ھۆججەت تىپى «٪1» نى ئۆز ئىچىگە ئالىدۇ By suffix قوشۇمچىسى Folder ھۆججەت قىسقۇچ Plain Text ساپ تېكىست WPS Document WPS ھۆججىتى PDF PDF Image رەسىم Video سىن Audio ئۈن File type contains %1 ھۆججەت تىپى ٪1 نى ئۆز ئىچىگە ئالىدۇ All بارلىق Empty(0K) قۇرۇق(0K) Tiny(0-16K) كىچىككىنە (0-16K) Small(16K-1M) كىچىك (16K-1M) Medium(1M-128M) ئوتتۇراھال (1M-128M) Big(128M-1G) چوڭ(128M-1G) Large(1-4G) چوڭ(1-4G) Great(>4G) قالتىس(>4G) File size contains %1 ھۆججەت چوڭلۇقى ٪1 نى ئۆز ئىچىگە ئالىدۇ File size without %1 ھۆججەت چوڭلۇقى ٪1 يوق File content match %1 ھۆججەت مەزمۇنى ٪1 ماس كېلىدۇ File location contains "%1" ھۆججەت ئورنى «٪1» نى ئۆز ئىچىگە ئالىدۇ File location without "%1" ھۆججەت ئورنى «٪1» يوق Later than "%1" «٪1» دىن كېيىن Earlier than "%1" «٪1» دىن بۇرۇنقى DatabaseManager New Space %1 يېڭى بوشلۇق ٪1 File %1 has areally been added to %2 ھۆججەت ٪1 ھەقىقەتەن ٪2 گە قوشۇلدى File %1 doesn't exsit in any space. ھۆججەت ٪1 ھىچقانداق بوشلۇقتا چىقمايدۇ. Failed to get space %1's id from query سۈرۈشتۈرۈشتىن بوشلۇق ٪1 نىڭ ID غا ئېرىشكىلى بولمىدى Failed to get %1's id from query سۈرۈشتۈرۈشتىن ٪1 نىڭ كىملىكىنى ئېلىش مەغلۇپ بولدى ModelController Failed to rename space %1 to %2, %2 is existed. بوشلۇقنىڭ نامىنى ٪1 گە ٪2 قىلىپ ئۆزگەرتىش مەغلۇب بولدى، ٪2 مەۋجۇت. Failed to remove space %1, error message: %2 ٪1 بوشلۇقنى چىقىرىۋېتىش مەغلۇب بولدى، خاتالىق ئۇچۇرى: ٪2 New Space %1 يېڭى بوشلۇق ٪1 Failed to updatestatus space %1, error message: %2 ٪1 ھالەت بوشلۇقىنى يېڭىلاش مەغلۇب بولدى، خاتالىق ئۇچۇرى: ٪2 QObject %1 %2 %1 %2 Intelligent Data Manager Introduction ئەقلىي ئىقتىدارلىق سانلىق مەلۇمات باشقۇرغۇچنىڭ تونۇشتۇرۇشى peony-intelligent-data-management-service مودەنگۈل ئەقلىي ئىقتىدارلىق سانلىق مەلۇمات باشقۇرۇش مۇلازىمىتى exit چېكىنىش UKUI::IDM::FileInfoJob Create Date: %1 چېسلا قۇرۇش: ٪1 Open Date: %1 ئېچىلغان ۋاقتى: ٪1 ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service_bo_CN.tspeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000002716315156143275033246 0ustar fengfeng AISearchTask Failed to create ai search session. འཚོལ་ཞིབ་ཚོགས་འདུ་གསར་འཛུགས་བྱེད་ཐབས་མེད། Search task cancelled. འཚོལ་ཞིབ་ཀྱི་ལས་འགན་མེད་པར་བཟོས་ཟིན། Condition File name contains "%1" ཡིག་ཆའི་མིང་ལ་"1%"འདུས་ཡོད། File name without "%1" "1%"ཡི་ཡིག་ཆའི་མིང་འཁྱེར་མི་དགོས། File type contains "%1" ཡིག་ཆའི་རིགས་ལ་"བརྒྱ་ཆ་1"འདུས་ཡོད། By suffix རྗེས་སུ་缀དགོས། Folder ཡིག་སྣོད་ Plain Text ཡིག་དེབ་དངོས་ WPS Document WPSགྱི་ཡིག་ཚགས། PDF PDFཡི་རྣམ་གཞག Image བརྙན་རིས Video བརྙན་ཕབ། Audio སྒྲ་ཕབ། File type contains %1 ཡིག་ཆའི་རིགས་སུ་བརྒྱ་ཆ་1ཚད་ཡོད། All ཚང་མ། Empty(0K) སྟོང་།(0K) Tiny(0-16K) ཆུང་གྲས། (0-16K) Small(16K-1M) ཨང་གྲངས་ཆུང་བ། (16K-1M)། Medium(1M-128M) འབྲིང་རིམ་(1M—128M) Big(128M-1G) ཆེ།(128M—1G) Large(1-4G) ཤིན་ཏུ་ཆེ་བ།(1-4G) Great(>4G) ཆེ།4G File size contains %1 ཡིག་ཆའི་ཆེ་ཆུང་ནང་བརྒྱ་ཆ་1ཚད་ཡོད། File size without %1 ཡིག་ཆའི་ཆེ་ཆུང་(བརྒྱ་ཆ་1མི་འདུས། ) File content match %1 ཡིག་ཆའི་ནང་དོན་ཆ་མཐུན་ཡིན་པ། File location contains "%1" ཡིག་ཆའི་གོ་གནས་སུ་"1%ཚུད་ཡོད། File location without "%1" "1"ཡི་ཡིག་ཆའི་གོ་གནས་མེད། Later than "%1" "བརྒྱ་ཆ་1"ལས་འཕྱི་བ་རེད། Earlier than "%1" "བརྒྱ་ཆ་1"ལས་སྔ་བ་ཡོད། DatabaseManager New Space %1 བར་སྟོང་གསར་པ། བརྒྱ་ཆ་1ཡོད། File %1 has areally been added to %2 ཡིག་ཆའི་1%བརྒྱ་ཆ་2ལ་དངོས་འབྲེལ་ཁ་སྣོན་བྱས་ཡོད། File %1 doesn't exsit in any space. ཡིག་ཆའི་བརྒྱ་ཆ་1ནི་བར་སྣང་གང་རུང་དུ་མེད་པར་འགྱུར་མི་སྲིད། Failed to get space %1's id from query འདྲི་རྩད་ཁྲོད་ནས་བར་སྟོང་བརྒྱ་ཆ་1ཟིན་པའི་IDཐོབ་ཐབས་མེད། Failed to get %1's id from query འདྲི་རྩད་ཁྲོད་ནས་1%ཡི་IDཐོབ་ཐབས་མེད། ModelController Failed to rename space %1 to %2, %2 is existed. བར་སྟོང་བརྒྱ་ཆ་1གི་མིང་ལ་མིང་བཏགས་ནས་བརྒྱ་ཆ་2ཡོད་པ་དང་བརྒྱ་ཆ་2ཡོད་མི་ཐུབ། Failed to remove space %1, error message: %2 བར་སྟོང་གི་བརྒྱ་ཆ་1མེད་པར་བཟོས་པ་དང་། ནོར་འཁྲུལ་གྱི་གནས་ཚུལ། བརྒྱ་ཆ་2ཡིན། New Space %1 བར་སྟོང་གསར་པ། བརྒྱ་ཆ་1ཡོད། Failed to updatestatus space %1, error message: %2 རྣམ་པ་གསར་སྒྱུར་བྱེད་ཐབས་བྲལ་བའི་བར་སྟོང་བརྒྱ་ཆ་1དང་། ནོར་འཁྲུལ་གྱི་གནས་ཚུལ། 2%བཅས་ཡིན། QObject %1 %2 %1 %2 Intelligent Data Manager Introduction རིག་ནུས་གཞི་གྲངས་དོ་དམ་ཡོ་བྱད་ངོ་སྤྲོད་བྱས་པ་རེད peony-intelligent-data-management-service 牡་ཏན་རིག་ནུས་ཀྱི་གཞི་གྲངས་དོ་དམ་ཞབས་ཞུ་བྱེད་དགོས། exit འབུད་པ། UKUI::IDM::FileInfoJob Create Date: %1 གསར་འཛུགས་བྱས་པའི་ཚེས་གྲངས།བརྒྱ་ཆ་1ཡིན། Open Date: %1 སྒོ་འབྱེད་པའི་དུས་ཚོད། བརྒྱ་ཆ་1ཡིན། ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service.propeony-extensions/peony-intelligent-data-management-service/peony-intelligent-data-management-service0000664000175000017500000001032615156143275033237 0ustar fengfengQT += core gui dbus sql network remoteobjects greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app TARGET = peony-intelligent-data-management-service VERSION = 1.0.0 DEFINES += VERSION='\\"$${VERSION}\\"' #CONFIG += c++11 no_keywords CONFIG += create_pc create_prl no_install_prl CONFIG += link_pkgconfig no_keywords c++11 lrelease hide_symbols embed_translations # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += COMPLIE_AS_PROJECT DEFINES += USE_AI UKUI_SEARCH_HAS_MATCH_ALL QMAKE_CXXFLAGS += -Werror=return-type -Werror=return-local-addr -Werror=uninitialized # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #include(../libpeony-qt/libpeony-qt-header.pri) # ukui-search PKGCONFIG += ukui-search kysdk-alm peony libsystemd kysdk-filesystem kysdk-systime kyai-data-management-client kysdk-ai-common LIBS += -lukui-log4qt ## 包含目录 #INCLUDEPATH += $$shell pkg-config --cflags-only-I kysdk-alm \ # kysdk-systime \ # ukui-search \ #INCLUDEPATH += $$shell pkg-config --cflags-only-I kyai-data-management-client #include exists(/usr/include/applications/kabase/log.hpp) { message ("kysdkbase found") DEFINES += KY_SDK_KABASE } TRANSLATIONS += peony-intelligent-data-management-service_zh_CN.ts\ peony-intelligent-data-management-service_zh_HK.ts\ peony-intelligent-data-management-service_mn.ts\ peony-intelligent-data-management-service_ug.ts\ peony-intelligent-data-management-service_kk.ts\ peony-intelligent-data-management-service_ky.ts\ peony-intelligent-data-management-service_bo_CN.ts SOURCES += \ ./main.cpp\ ./model/modelcontroller.cpp \ ./model/spacesfilesfetcher.cpp\ ./database/databasemanager.cpp\ ./condition/condition.cpp\ ./search/searchtask.cpp\ ./ai/ailabelhelper.cpp\ ./ai/aiconditionshelper.cpp\ ./ai/aipreviewhelper.cpp\ ./ai/aisearchtask.cpp\ ./file/fileinfojob.cpp\ ./file/fileinfomanager.cpp\ ./file/fileinfo.cpp \ ./file/qtremoteobject/fileeventhandlerimpl.cpp\ ./model/searchresultmodel.cpp\ ./model/spacessettings.cpp\ ./3rd-parties/qtsinglecoreapplication.cpp\ ./3rd-parties/qtlocalpeer.cpp\ ./3rd-parties/qtlockedfile.cpp\ ./3rd-parties/qtlockedfile_unix.cpp\ ./3rd-parties/qtlockedfile_win.cpp HEADERS += \ ./model/modelcontroller.h \ ./model/spacesfilesfetcher.h\ ./database/databasemanager.h\ ./condition/condition.h\ ./search/searchtask.h\ ./ai/ailabelhelper.h\ ./ai/aiconditionshelper.h\ ./ai/aipreviewhelper.h\ ./ai/aisearchtask.h\ ./file/fileinfojob.h\ ./file/fileinfomanager.h\ ./file/fileinfo_p.h\ ./file/fileinfo.h\ ./file/qtremoteobject/fileeventhandlerimpl.h\ ./model/searchresultmodel.h\ ./model/spacessettings.h\ ./3rd-parties/qtsinglecoreapplication.h\ ./3rd-parties/qtlocalpeer.h\ ./3rd-parties/qtlockedfile.h REPC_SOURCE = fileeventhandler.rep REPC_REPLICA = fileeventhandler.rep inst1.files += conf/peony-intelligent-data-management-service.service inst1.path = /usr/lib/systemd/user inst2.files +=conf/com.peony.idm.service inst2.path = /usr/share/dbus-1/services target.files +=$$OUT_PWD/$$TARGET target.path = /usr/bin intro.files +=data/智能空间使用手册.pdf intro.files +=data/智能空间引导手册.pdf intro.path = /usr/share/peony-intelligent-data-management-service/ INSTALLS += \ target \ inst1 \ inst2 \ intro QM_FILES_INSTALL_PATH = /usr/share/peony-intelligent-data-management-service peony-extensions/peony-intelligent-data-management-service/3rd-parties/0000775000175000017500000000000015156143275025322 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/3rd-parties/qtlocalpeer.h0000664000175000017500000000144515156143275030012 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #ifndef QTLOCALPEER_H #define QTLOCALPEER_H #include #include #include #include "qtlockedfile.h" class QtLocalPeer : public QObject { Q_OBJECT public: QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); bool isClient(); bool sendMessage(const QString &message, int timeout); QString applicationId() const { return id; } Q_SIGNALS: void messageReceived(const QString &message); protected Q_SLOTS: void receiveConnection(); protected: QString id; QString socketName; QLocalServer* server; QtLP_Private::QtLockedFile lockFile; private: static const char* ack; }; #endif // QTLOCALPEER_H peony-extensions/peony-intelligent-data-management-service/3rd-parties/qtlockedfile.cpp0000664000175000017500000001020215156143275030467 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtlockedfile.h" /*! \class QtLockedFile \brief The QtLockedFile class extends QFile with advisory locking functions. A file may be locked in read or write mode. Multiple instances of \e QtLockedFile, created in multiple processes running on the same machine, may have a file locked in read mode. Exactly one instance may have it locked in write mode. A read and a write lock cannot exist simultaneously on the same file. The file locks are advisory. This means that nothing prevents another process from manipulating a locked file using QFile or file system functions offered by the OS. Serialization is only guaranteed if all processes that access the file use QLockedFile. Also, while holding a lock on a file, a process must not open the same file again (through any API), or locks can be unexpectedly lost. The lock provided by an instance of \e QtLockedFile is released whenever the program terminates. This is true even when the program crashes and no destructors are called. */ /*! \enum QtLockedFile::LockMode This enum describes the available lock modes. \value ReadLock A read lock. \value WriteLock A write lock. \value NoLock Neither a read lock nor a write lock. */ /*! Constructs an unlocked \e QtLockedFile object. This constructor behaves in the same way as \e QFile::QFile(). \sa QFile::QFile() */ QtLockedFile::QtLockedFile() : QFile() { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Constructs an unlocked QtLockedFile object with file \a name. This constructor behaves in the same way as \e QFile::QFile(const QString&). \sa QFile::QFile() */ QtLockedFile::QtLockedFile(const QString &name) : QFile(name) { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Opens the file in OpenMode \a mode. This is identical to QFile::open(), with the one exception that the Truncate mode flag is disallowed. Truncation would conflict with the advisory file locking, since the file would be modified before the write lock is obtained. If truncation is required, use resize(0) after obtaining the write lock. Returns true if successful; otherwise false. \sa QFile::open(), QFile::resize() */ bool QtLockedFile::open(OpenMode mode) { if (mode & QIODevice::Truncate) { qWarning("QtLockedFile::open(): Truncate mode not allowed."); return false; } return QFile::open(mode); } /*! Returns \e true if this object has a in read or write lock; otherwise returns \e false. \sa lockMode() */ bool QtLockedFile::isLocked() const { return m_lock_mode != NoLock; } /*! Returns the type of lock currently held by this object, or \e QtLockedFile::NoLock. \sa isLocked() */ QtLockedFile::LockMode QtLockedFile::lockMode() const { return m_lock_mode; } /*! \fn bool QtLockedFile::lock(LockMode mode, bool block = true) Obtains a lock of type \a mode. The file must be opened before it can be locked. If \a block is true, this function will block until the lock is aquired. If \a block is false, this function returns \e false immediately if the lock cannot be aquired. If this object already has a lock of type \a mode, this function returns \e true immediately. If this object has a lock of a different type than \a mode, the lock is first released and then a new lock is obtained. This function returns \e true if, after it executes, the file is locked by this object, and \e false otherwise. \sa unlock(), isLocked(), lockMode() */ /*! \fn bool QtLockedFile::unlock() Releases a lock. If the object has no lock, this function returns immediately. This function returns \e true if, after it executes, the file is not locked by this object, and \e false otherwise. \sa lock(), isLocked(), lockMode() */ /*! \fn QtLockedFile::~QtLockedFile() Destroys the \e QtLockedFile object. If any locks were held, they are released. */ peony-extensions/peony-intelligent-data-management-service/3rd-parties/qtlockedfile.h0000664000175000017500000000254715156143275030151 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #ifndef QTLOCKEDFILE_H #define QTLOCKEDFILE_H #include #ifdef Q_OS_WIN #include #endif #if defined(Q_OS_WIN) # if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) # define QT_QTLOCKEDFILE_EXPORT # elif defined(QT_QTLOCKEDFILE_IMPORT) # if defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # endif # define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) # elif defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) # endif #else # define QT_QTLOCKEDFILE_EXPORT #endif namespace QtLP_Private { class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile { public: enum LockMode { NoLock = 0, ReadLock, WriteLock }; QtLockedFile(); QtLockedFile(const QString &name); ~QtLockedFile(); bool open(OpenMode mode); bool lock(LockMode mode, bool block = true); bool unlock(); bool isLocked() const; LockMode lockMode() const; private: #ifdef Q_OS_WIN Qt::HANDLE wmutex; Qt::HANDLE rmutex; QVector rmutexes; QString mutexname; Qt::HANDLE getMutexHandle(int idx, bool doCreate); bool waitMutex(Qt::HANDLE mutex, bool doBlock); #endif LockMode m_lock_mode; }; } #endif peony-extensions/peony-intelligent-data-management-service/3rd-parties/qtsinglecoreapplication.h0000664000175000017500000000126515156143275032422 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #ifndef QTSINGLECOREAPPLICATION_H #define QTSINGLECOREAPPLICATION_H #include class QtLocalPeer; class QtSingleCoreApplication : public QCoreApplication { Q_OBJECT public: QtSingleCoreApplication(int &argc, char **argv); QtSingleCoreApplication(const QString &id, int &argc, char **argv); bool isRunning(); QString id() const; public Q_SLOTS: bool sendMessage(const QString &message, int timeout = 5000); Q_SIGNALS: void messageReceived(const QString &message); private: QtLocalPeer* peer; }; #endif // QTSINGLECOREAPPLICATION_H peony-extensions/peony-intelligent-data-management-service/3rd-parties/qtlockedfile_unix.cpp0000664000175000017500000000305415156143275031541 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include "qtlockedfile.h" bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; int cmd = block ? F_SETLKW : F_SETLK; int ret = fcntl(handle(), cmd, &fl); if (ret == -1) { if (errno != EINTR && errno != EAGAIN) qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = F_UNLCK; int ret = fcntl(handle(), F_SETLKW, &fl); if (ret == -1) { qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); } peony-extensions/peony-intelligent-data-management-service/3rd-parties/qtlocalpeer.cpp0000664000175000017500000001174715156143275030353 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtlocalpeer.h" #include #include #include #include #if defined(Q_OS_WIN) #include #include typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); static PProcessIdToSessionId pProcessIdToSessionId = 0; #endif #if defined(Q_OS_UNIX) #include #include #include #endif namespace QtLP_Private { #include "qtlockedfile.cpp" #if defined(Q_OS_WIN) #include "qtlockedfile_win.cpp" #else #include "qtlockedfile_unix.cpp" #endif } const char* QtLocalPeer::ack = "ack"; QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) : QObject(parent), id(appId) { QString prefix = id; if (id.isEmpty()) { id = QCoreApplication::applicationFilePath(); #if defined(Q_OS_WIN) id = id.toLower(); #endif prefix = id.section(QLatin1Char('/'), -1); } prefix.remove(QRegularExpression("[^a-zA-Z]")); prefix.truncate(6); QByteArray idc = id.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib("kernel32"); pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); } if (pProcessIdToSessionId) { DWORD sessionId = 0; pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); socketName += QLatin1Char('-') + QString::number(sessionId, 16); } #else socketName += QLatin1Char('-') + QString::number(::getuid(), 16); #endif server = new QLocalServer(this); QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); lockFile.setFileName(lockName); lockFile.open(QIODevice::ReadWrite); } bool QtLocalPeer::isClient() { if (lockFile.isLocked()) return false; if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) return true; bool res = server->listen(socketName); #if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) // ### Workaround if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); res = server->listen(socketName); } #endif if (!res) qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); return false; } bool QtLocalPeer::sendMessage(const QString &message, int timeout) { if (!isClient()) return false; QLocalSocket socket; bool connOk = false; for(int i = 0; i < 2; i++) { // Try twice, in case the other instance is just starting up socket.connectToServer(socketName); connOk = socket.waitForConnected(timeout/2); if (connOk || i) break; int ms = 250; #if defined(Q_OS_WIN) Sleep(DWORD(ms)); #else struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; nanosleep(&ts, NULL); #endif } if (!connOk) return false; QByteArray uMsg(message.toUtf8()); QDataStream ds(&socket); ds.writeBytes(uMsg.constData(), uMsg.size()); bool res = socket.waitForBytesWritten(timeout); if (res) { res &= socket.waitForReadyRead(timeout); // wait for ack if (res) res &= (socket.read(qstrlen(ack)) == ack); } return res; } void QtLocalPeer::receiveConnection() { QLocalSocket* socket = server->nextPendingConnection(); if (!socket) return; while (true) { if (socket->state() == QLocalSocket::UnconnectedState) { qWarning("QtLocalPeer: Peer disconnected"); delete socket; return; } if (socket->bytesAvailable() >= qint64(sizeof(quint32))) break; socket->waitForReadyRead(); } QDataStream ds(socket); QByteArray uMsg; quint32 remaining; ds >> remaining; uMsg.resize(remaining); int got = 0; char* uMsgBuf = uMsg.data(); do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); delete socket; return; } QString message(QString::fromUtf8(uMsg)); socket->write(ack, qstrlen(ack)); socket->waitForBytesWritten(1000); socket->waitForDisconnected(1000); // make sure client reads ack delete socket; Q_EMIT messageReceived(message); //### (might take a long time to return) } peony-extensions/peony-intelligent-data-management-service/3rd-parties/qtlockedfile_win.cpp0000664000175000017500000001112115156143275031345 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtlockedfile.h" #include #include #define MUTEX_PREFIX "QtLockedFile mutex " // Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS #define MAX_READERS MAXIMUM_WAIT_OBJECTS #if QT_VERSION >= 0x050000 #define QT_WA(unicode, ansi) unicode #endif Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) { if (mutexname.isEmpty()) { QFileInfo fi(*this); mutexname = QString::fromLatin1(MUTEX_PREFIX) + fi.absoluteFilePath().toLower(); } QString mname(mutexname); if (idx >= 0) mname += QString::number(idx); Qt::HANDLE mutex; if (doCreate) { QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); return 0; } } else { QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { if (GetLastError() != ERROR_FILE_NOT_FOUND) qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); return 0; } } return mutex; } bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) { Q_ASSERT(mutex); DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); switch (res) { case WAIT_OBJECT_0: case WAIT_ABANDONED: return true; break; case WAIT_TIMEOUT: break; default: qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); } return false; } bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); if (!wmutex && !(wmutex = getMutexHandle(-1, true))) return false; if (!waitMutex(wmutex, block)) return false; if (mode == ReadLock) { int idx = 0; for (; idx < MAX_READERS; idx++) { rmutex = getMutexHandle(idx, false); if (!rmutex || waitMutex(rmutex, false)) break; CloseHandle(rmutex); } bool ok = true; if (idx >= MAX_READERS) { qWarning("QtLockedFile::lock(): too many readers"); rmutex = 0; ok = false; } else if (!rmutex) { rmutex = getMutexHandle(idx, true); if (!rmutex || !waitMutex(rmutex, false)) ok = false; } if (!ok && rmutex) { CloseHandle(rmutex); rmutex = 0; } ReleaseMutex(wmutex); if (!ok) return false; } else { Q_ASSERT(rmutexes.isEmpty()); for (int i = 0; i < MAX_READERS; i++) { Qt::HANDLE mutex = getMutexHandle(i, false); if (mutex) rmutexes.append(mutex); } if (rmutexes.size()) { DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), TRUE, block ? INFINITE : 0); if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { if (res != WAIT_TIMEOUT) qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky unlock(); return false; } } } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; if (m_lock_mode == ReadLock) { ReleaseMutex(rmutex); CloseHandle(rmutex); rmutex = 0; } else { foreach(Qt::HANDLE mutex, rmutexes) { ReleaseMutex(mutex); CloseHandle(mutex); } rmutexes.clear(); ReleaseMutex(wmutex); } m_lock_mode = QtLockedFile::NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); if (wmutex) CloseHandle(wmutex); } peony-extensions/peony-intelligent-data-management-service/3rd-parties/qtsinglecoreapplication.cpp0000664000175000017500000000661515156143275032761 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtsinglecoreapplication.h" #include "qtlocalpeer.h" /*! \class QtSingleCoreApplication qtsinglecoreapplication.h \brief A variant of the QtSingleApplication class for non-GUI applications. This class is a variant of QtSingleApplication suited for use in console (non-GUI) applications. It is an extension of QCoreApplication (instead of QApplication). It does not require the QtGui library. The API and usage is identical to QtSingleApplication, except that functions relating to the "activation window" are not present, for obvious reasons. Please refer to the QtSingleApplication documentation for explanation of the usage. A QtSingleCoreApplication instance can communicate to a QtSingleApplication instance if they share the same application id. Hence, this class can be used to create a light-weight command-line tool that sends commands to a GUI application. \sa QtSingleApplication */ /*! Creates a QtSingleCoreApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Creates a QtSingleCoreApplication object with the application identifier \a appId. \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this, appId); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Returns true if another instance of this application is running; otherwise false. This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session). \sa sendMessage() */ bool QtSingleCoreApplication::isRunning() { return peer->isClient(); } /*! Tries to send the text \a message to the currently running instance. The QtSingleCoreApplication object in the running instance will emit the messageReceived() signal when it receives the message. This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within \a timeout milliseconds, this function return false. \sa isRunning(), messageReceived() */ bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) { return peer->sendMessage(message, timeout); } /*! Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application. */ QString QtSingleCoreApplication::id() const { return peer->applicationId(); } /*! \fn void QtSingleCoreApplication::messageReceived(const QString& message) This signal is emitted when the current instance receives a \a message from another instance of this application. \sa sendMessage() */ peony-extensions/peony-intelligent-data-management-service/data/0000775000175000017500000000000015156143275024076 5ustar fengfengpeony-extensions/peony-intelligent-data-management-service/data/智能空间引导手册.pdf0000664000175000017500000055043715156143275035414 0ustar fengfeng%PDF-1.7 %³ 3 0 obj <> endobj 13 0 obj <> endobj 19 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?(wVmڃv€ڸص5KKEEr$+9QNױ)F pC甪J0qi 僔Sw: +#ķshM0Ay" YrH 1\>)n" \GĎpk85wP]1bxi` 6_$|8&ePv.rr}\}yy`C9$`\;u>"f*`h!rGm\lz;]ܯ0*$dň[銿oj 4`W_ڌLL^f0g|݆dռ@~a=so鮾 (\F5 N?Zu09Dy#cִ9Gn+t+Db54B\yh(2Hg&$OznK@(P 1F( Q(b(1F( Q(b(1F( Q(b(1F( Q(b(1F( Q(b(1F( Q(b(1F( Q(b(1F( Q(b(1F( Q(b(1F( Q(b(1F(wX{~D^*WhGxb=<3_O{Z^.mA9bQE QsgfT]]n@@@5$dI/P##E Q+bq>cբ]^Dnq1.(r7,ӕK(]㢖1]PMp#b Q@ E,S:ܫfI&l ==8iQE#}V}&RU852$&++X- EfB+78EhIVۂ!8@R(bQEQ@(P1E 1F( Q(b(1F( Q(b(bQEQ@(P1EbQEQ@(P1EbQEQ@b(Q@(P1EbQEQ@b(1F( Q(b(Q@(P1E 1F( Q(b(1F( W/FRc(I&!o+!FR_7| QByi+^[2 )0f hjAE#| Ԣ 1\)K-h_/p( (Nŷ|5lS? F?[z ~ρ49Z4R(SsI}N5 W:MtQ`+XCsq] (]x$z4Q@Q@Ύ}KEsϠ~tPs?:9-Ύ}KE'>A@ Ϡ~tPs?:9-Ύ}KE'>A@ Ϡ~tP~u^Nwp;bx~tyNbkE迕_OΟ;*(f_ʏU1??:ހ5>4o}Ro %O?(+GF 0GjuQ@G9"#W@h[Xmu#oiNx ҝ9T|"H\: +W{B+rZhR˔.c_zөFt䉅hTv-Q\-u+|1T()צkY|Gy]vW?g;JF%A1Y% -1"B/BmHEi]xx"gyXfFpr3C@-LJϣ#h~Vdzg|J~sAwv@1%A %i$M|/$b[Yʲ.dEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP!zZڀ(I$QY: 46Y e< 'kQ\ܚfƠ5H~{GHd(9ֺetWR #ݛi PdI NTզvd>lf#(ZϼbH\bgi4\D|q0T^Eg1ˊ}A`~XoXvX@N1\`JEda$[+N)CTFEPEPEPEPEPEPEPEPI1ԕ֤((Hs#}jR4+ği  /#/kDx=`_dZ[_͙?w3HEcxoFSuɔwnkf-Es~/5 " 6ZYJ1$PIEc=$`8ⵡfhcgټ-sҀE=@QE`\W)-ky 28 7g}ӝF(~ O7:ٕIؘ6~CGp{@ϵ-_ս[6U@Bp2w@lvS>4h䴑R2Jb Ҋ+oj'NKyIn"XdpapQ8P~P!a`*emEy/֖cuHŹO,mr x;>c5Sԡ[tO54aHYx}O Vԭة:s11%~q!UdsERtR+]Fi1Tef!NsU( I@}Ay$Ww7 H9:v[[ٴQJ`Ft豠$6As]l7z/%OݝRvP iQ[Zn.!GҊs`e$`F^U6Q^)܋`$f<FZ܉кE*]m 袀 ( ( ( ( ( ( ( ( ( ( ( ( ( ( (~E~XuHH2%/K<3[TP-{][iҴјm)؆%A'h$tOkvӭ-Se**(TP:0gjA>hJVM;`K*b+,*Fd0zWCEbj$vd>Z/&t8=CRXb34EU<ݒAֽw޸{teRWUϡV3@ #H:sPV& #(%Dk*:HЉf2s%QEQEQEQEQEQEQEQEnCRTp%QEQEI7ڥ'7ր1|Eoqqgn!xZxm ;St$5XR ɤkDGo0gWkEexnAP\#eRTzu5QԐnikiGzRRY] ?N??tP'.vһ mbw4uֺ{d1Z8ʢHUp:*Z((Zgmv4hV 37c{WSEeCk2T6 )#~@`Ax4P'nԡM.qk󵪪)Qv&,ROZVHbpw,'9?Z8}Xkgվnmgb% Qfwݍi?z6 ޕ},Av)򄡚IFvNLcwPID شUlmE,csE.ouAk`#[31bϷkgj_j~[RnӈdyCI@T8< 諸)Ziu孔gEz?(TA-sHق98ݜxo]HuGy ?ft}^[Ko I%BBk((?:2:n_/?:2:n_/?:2:n_/?:2:n_/?:2:2zLGF_?Qaҥt~te?:ߥf*\GF_?Qaҥt~te?:ߥf*\GA.v΀"0GهJ.G΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀lTܿGF_3dR}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0GهJ/ї/~}{r΀"0@ԹΌG@ QҖt~t)ΌG@t~tcį)r΀#:zgOV2΀#:zHtΌG@t~t)ΌG@t~t)ΌG@t~t)ΌG@t~t)ΌG@t~t)ΌG@t~t)ΌG@t~t)ΊuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@#}o~>-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE۽?×3I)e6/E{rE۸gW)j^$}N=[XKim w$x?qy/ι+=u `B mIV-:rUO6˙ǽnvd9cSCΖzqȋ(N> ~3kejZ寇ז)$bp(H6K׵m>N5 rXgrNJu[Oވ5 $ rE z`cc浼)yIIenF$POc:cdR\hXm?hN3֏mCO׮'{x fH8֊((((((((R7?JJZAGҖ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( +g֑j:ȍ!rpGV<[I:"rhjQYF|w+ '🇮5+E` vPx;+KU鷺nI{d()}|ImYi#GV(# ս;ψ*Fk$:s!3\G7Q=ʰ@ 1t-}kq,ԑlAw8U4,׃tWQPk KyUy?x~k=3O;jEXm\{ (=>)Xi)yS4Z]6TƤ9.1>^[񭗊o!lM)oeR'㏥tW|Sޕiӛָ.O.ܰXgƍ:[Ԭn%ū 19z~tV]xMyDvϲC2l{;ca64."5ghe|cߟB((;vmj8R Rx| {^?19Ҁ7WB߭|H `dߏj((((((((((((((((((((((((((((((((((o~OҀGҖ}(((((((((((((((((((%wIn$#R8ꥎs'bIBڬ",//?*M]ǣ],4\ ;`@; A8>zi:Zg>ߋ$?k[1O\a4DNtn.ǧn:W\7]{l5{iV4JB27w6ΟC\2O\23g3@ykbX|'m9{BV2܄%Av$j׵>MG{Ad~*4&v'k%XIzOk:5 o.|h̀>Js8(G'EuqYnudW?N}pqibZLkԝO^kxgUHْ8![ָ;Xּ k:46YS<%KpGq@N"iˡLIIzGxbg4i$m:{u?X_>%έs@,6\}ͧí" #hnȸ*I$d~"8>7oivkMv/"v^HN{i&ӴȼKQ#\n@8;Ǯ{Wa@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@AgMZdF*+FP[;煲~j?@: jȌY>Wn.-YKGu^U}Wi٬s5fs۵črrG2!1w]C8iCfP+[ڝwj##GW'ݽ rGAЌK&˨K.٭էD ,#8x@D O0x>SDWF 2 Q^kFO z,Bhr,6Fݠ9+OƑz{L2GTW#9(!SENi T;,uVڻ?-]B<2.y@'@]Q@Q@Q@Q@Q@Q@Q@#}o~>-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((((((((((((((((((h-ml!eQb;[sa엞PY"bʙ D@$}f((((((()-#}}t})h(((((((((((((#Y^+c8@D]4\0W*[$oW{a4 C6Hޠkk[#@Nx>w1jO@6T\6Hc'{T|F`iji%}.kd("u)ԭp#ip%KAv4.É3r 6RI4r|8'q7_bH0^s4R p}0z۠-1_aJ:}jZ}W3HxX6W=˼g fYH9q \^PW`N?3Q\"g^H<)ax0Y2"4=SeÆM `T}0(K)|3\8LF=I-Jos*ڊ;DJ:`:c?J͏E]4ءd; iH \e($gTKRԄ_]AyWlcqɪ(M g+PU#eqgi""I# 119?XԬZB I84Y$IJo9;@j.mHꤑpxLy U!?3gBxQ5\O4c #.L55ʙѾ쯙'=(azC)$V\o1I)u$1GgK%WV|EP͞q8V= i۸ BPOo5[>[`0cVon慭-IR9\c̸[| Oݑ=jya(2(l*ZMe^H`3ֿn T|zbHћ[t4a@ 'Y'WEK/:g<5Yr̋(P 㟭]dD9'T]J`U۔R7Jmu Ka%:r=~S],$ɲA8ăM ->rdm۰'Cq18Wl9='I|EnQΨ]9+q ;I.fcMĮGYJ QfAs #FLoϙt14YWv㜜g>#ihy;n-IIcc4Zcg(ۙF9T\U'?GmhmYp=4n@J%U ;fy$ -(v$HrH7i hwa9LMMwqxHmlm?t$`xۜ|:KyhH}@8sju,O&wt}jfKk ?,%7oasr$Qяt1>Yc*_ g}9{,>ܫA+w fA)OwS~TtƌAk3 QMgul%'hX{ە_k&%?K5 MT> jpApj ;6Uf0:gR÷eVJ]ߧQ fA)Oj*G܅3͟K5 Mk ƢE %?K5 McQG"{jgwS~G%?±}=_y fA)OwS~XQz?Ⱦگ?]ߧQ fA)Oj(_rmWlYJo(]ߧVCF̤+#6E ]jW8F Fx5OҼ?G՗wO+x(SkjNp3}t})k= ((((((((((((((((((ޡkb*T[!9뱁d_1|](č\ ecni"T#mBcDh];3Qy:q1))L1sW~5$6Pz)N:.3ǯZmce{7X`+!Glp\$Z{B%L~&X-@قdz]%}뎵Md7'{E&!v @[n,=J=BYV$|RinEp9'ֱfi u[U"M'<Mk5H'yyb`7 dt 9:F!rUtu}Z*~w2(Q0p#KslPH.kQr0=>ԭ KdTF>Y A3QqKts2U#d +-%@BBLMNI% ^z}鑯c=`ĘV`;dqZ=ܗv$Ur?Jչ-ª>w4sIJцA<-W%18pj,b>}ҡt$u*~#xVk<<{Lq gYֹB3ȹsyN4P@-w;~_xz}Bq% G k.l U@F7)a +hIu[b7yK@aj,q=X30;y^s/T1'9k{us$ 3W?IBk'YeAjj@,!" O]}T k^ۣc~KK[ȡ&f1}k&Vh吳+*WXtVLC}>t$ crE&FpCHkPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP#E/P"? W6|eQE%cF۽X"i^HߙXѣ6y)9=:.d,yVcէ$VJȁA=뎾S4YJa4nR op0AnjmLQ,< sswֹX$*4~`.A MX:dF_.Cr$du52s/B]rrFũY٥ixq Í$AXN}VԮ-OH"I7'xCFHp<8VV)n63Q%^[En9p* Qz3uS^gZ\2!WS#O8?sKoN]F[f34]1PIۻo3 EXb".FIIBpmVUJђi"Q]g0QEQEQEQETLgTTPN*:(`Al?s!yۯXtI4w%cP9fQN ͗9);]5e]^j OҼ㇣=|>-xQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEUO,eh_G>ZX,-i"k2?19Y*gY"۔"OcN%$,IcrMOESU M>Z7pF_jz(oN&q֧XQfy@'=qJ(R@B:fI`O++n9x*#Z|rf|ӽ] KHSg rSpMZE2v-z(k h@Aax?[ 7rvg5ZKyg3:1RCϡ)X@~>b$N~vy<Ӛhaf3#\=w .@(O=z}*z( 8a8ڡtϯNi 7K٘鞟MEW6V\:nR$Pn{RQ@ $a#P@1O(((((((((((((((((((f6S(uW- Uo /Ш?T ?.duTW) ?So R ?.u&uW# 'Q_О?T ?/huW5xo5!Z|e1 k tZ3fR%%tQEfPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEJjZzHcx'%tV<aiv6 e qĜOrO$}{[4w> .4.'  ̸֑oG!kGk@Rg=1ޗƺr]i"`lȃfoθgOM$qT3*;M^쪥[w q֞;i$V$*c/CZdhg72XJ9sV /nG@SJx\f0-^:Vn|Oh˩0cTpQO^_yikq}I-_*3+CyvR/ךcд%ۀѢ#9Q-ƒiVcׁREnw`D+_jմe^$8~>/o*(Vi$Wh'ҐL\0]ru[I11sW gcZp2vUQ얌%瞝:֕^lOgj4x 5C$(FEvk1k#3vTޅ $TV^$]B%oUzpk^R𭅙X~w߳# n}+7Xj5-E[F,hJu}L$]?vx^V։îE$A4h PMkxe.y[iNdJ$r1z<lızK Zh(|d 4ZG6+;VF k?{3/QEhQEQEQEQEQEQEQEQEQEQEQEQT X̓Hp;.NȨ(;YajZ+(((((((((((KsFp 1q"(ª>( Q G *ǧjҴ(XZ\U(9RPRFv( #P1,vdE685$TEUQo 1 ,qU@*J((((((((o~OҀGҖ}(((( H)(; ;jbi(m!%ltn#^3՗t;=;{_/ƶnk5'}W;t[nMr*9c*C!7qK>sI?-Kj}`R!V[̡eCpA4F.k}gG?-G$%XgH$1ɼ_F?OES.)}Gܿ-14gE7mb띭FzSDi?'G-r1{Nv,ڳJ&`@@P>":o?ArM4pbКXghU9Bґ㱓ﭻ|~]pOM4!^ O@֎\??@X4]ۜ.qqR:x.|l, I|%xqY@n6ۏ8Nzg֟./HEOhL0Bcڞcg`m2nGqY~u--Ȕi?e_ZQ~4 S@çvb\=H50]^#'Yi lghO>}*?Ǝ\/K_K0hG`Bq5UH:T>\/K_>i{G}>£i?__1m4ra?_ z+um[H,]g\圈=һҴ]7DM?(9okG{xuJ0ﻸrݖ誛{xuYn:]*?[oo΍*?[oo΍*?[oo΍*7@({xtn:M*w@&v{@~uo{xu!']$7-ߣ5eX#.@sUx0mW_C$ЩpU//&HpXa;j;65z/]ռ#Oo2)ٟМ. c^}<6z3+)o\cGAX0:m5vfo. yc(?aUkKt֟hUP`9j)V`N?erXIxAI h9nA׎ IU̮XV93Sxo#㰻Qۅ Gb8汵HM$!BIܿ*O^rqC[{M&u}Bigsm4-xG${ xǨӱ5.uVyF;׃^{+;Yʷ(vA=qҸ[P {sź A;W< WaÚͨE?@` ۛ:PEo;Y/f{KUnw,cwri>6P!8e ^A>w c/5^]K`*ŒړNfn-/sxg?R_dh~o[&sqqXtyԒREy$zQ+LȲ#Ŵ"CAc&RRm7FKn&{f[{ـ`ccס9]vZy6fӥkJyҽ z+m+>((((((((o~OҀGҖ}(((((((((((>Ө:hp;8q4oM$FF?c}7CQzʀ}*Jn*7CP6^-1FGlwjۦx?Gq5As|oI @@Qlc+pڙ'qW!Z5z4ouֱ/)%>_+'{Z|oO*bכS8vR}ںIU (czGTxmR-L>+-CkD;H?IGTŨxiFP?: ֲgY8ğ'$v>RQ'sz\ڻjηث#O*<ohoO*O*<ohoO*l󷎂]:IeyJŗiӵLli?irPgIA?O%y|'G$~?"z*d#)?O?̿W& f_ ,o :ԸTmz.强PƞhP +ߕ'{/Պ7ԭSOR-?7ՑEjO)GT  TRQ'CEM'RP4TRQ'CSO4ZS+; hG'=@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQG|Q@Q@Q@Q@Q@Q@Q@Q@Q@#}o~>-QEQEQEQEQEQEQEQEQEQEQE44ij+Uݤ0rɪX@G^$VKedYII5ɧAsgPXu!7 3 [wIg 3skY8S͍19:q:}*߰j+v. ˩ginEQEPHMRWp#c4e}('jgPHҀ"Q締 |#ɵAV@ 締oAQQ@Ř55Ubժ(((((((((((((a0 yx_n8ǧֵY/9e#H?.B?4SM]kع\P55> j!teH Ea?di#qb9>vk,G Nzp}W*v'S7_o}2%_S 4CWF(\JmCX7#6TOz +/Cp5ŘӢuu\\nE➠QEQEQEQEQEQEQEQEQEQEQEQEQEQE dzܱ;$c1VS5#%:-Oŕo6E20y^B? 麹ƞ#dz Ѵź?ev.a@pH֮uU qĤg,HȬCxu{zWXOǐ3;TN@"xZK . yYm)Jʻy<Z;y.^5f}JT*px# ⽦jt:{FV9T[)7ǞPEPEPEPEPEPEPEPHt)i- K@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ ?|} bk+nŵL (~lWn+9qJ4w:OXZWG J7?*Ж+ak! `GYrON u++hX;XZWG J7?㴸{/0+E V?z Zx,4[\6*ҿ?ƏXZWG>d&6Z4gd#`N3,bG[ J7??ai_\Ak" s[+I^Xv=7nGFGN,o?7GGVV.&GFGNGK-oAі$McteF[~t2EoAі-oAі-VP:)4c-?:2ރ6rXFcULzΖLzΖLzΖLzΖLzΖLzΖLzΖLzΖL鮂AziP?g}GQx/ME>f+">#)@6*JkQvèC ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( FKHt(t})iJZ(((((((((((ik\[X B<+?|} :ZtefgR*Gj[ Q Pߏʻ_oA]?X}G8VT¶?oAGނ,W~ 8Y¶?w1pI)ib_Q/쫼,W~ 8Y¶?wQo}G)+iQYXnޮqWܞI%dHhnUgZC^fjԊDVҜ?jcɕ3Z&>v_yxOQY1Bfx41Lԟbx](:@k*S(H n$z7qFX_màFWMS(.=GRf+c 8=kgQ:|m??ʹh Zk 2I׉ycvmwFeܛnk0גj o y[VB2ꏸZ[? YI8KfxAƬ4=Q^t+uזmr后WO2g#o p 3ړ<,9SWWRFiJz-L3+ӧɵk6%`9״4PeX_~{:%uK}wx_Vo&/ YȞFGQ]]EfT*`0K^-z֬ZgBvAEVFp`ۊoe潢۰ :+sU7}bF՛{x,8z*{_Q4y%nY@*i GB#yX2^i<=s 1XTkyEWit+ŵvx :;l-$m fm]Q1HJESz{Sq|71O;<5KXQ:ucRSZ-Et+7SӍB0N+J$ ՙʟ˹(Y|(Ԥ̾Dz ҺQ*כݑ젶FN2 YɏarO=kv"NE:i%S]w/:9뼜I>=w=#pxT}ʼ<'g?[d,׉4&;DRr}Z)Om̪TGyQ]&fVK|N GI,n8ϵG*k8jIwo MHG]H:ݰkK43SRPriZWEiE:wb:((((((* ۤL2nM sp%@PUNWjk$y4}=J QtQ[/J}W9~]sO!~fQ8Jn:8Š( ( ( ( ( ] k_[ùbT@,'(;N~s5ד̋`R( I{Nlۡr a@sWh ? 6hHmqֵh~_鳬L2_ z:>$^3ȥD1RJ[P?BLQhK"@N+(((((()-#}}t})h(([c̭=o?Sk2J?G_*G#oi܆_>/Jv Gҹ1}?Qh:B(((((>M?|} :ҫyV7FyV7F!E9RRo_΍I?:Z(kSAMJp(:}K<DrG*}MY2qwJEb[8 %6} ^VB`fE(cƏ($U?}jR1Q5h0-QEżWP<37ea{HmxPm žj) qwkS?ar!qVPQES]dS R kppjEA(( R \g>5EV\& t84Q@Q@()GAU~o (͟ʡT0=)hwkP(((((+3-_ʴ3-_ʵX:×牳1c9=f{m5浖&C{+rHړKд϶n˳p:(N8Q3Ү7sĒAdy1-E:Ayw`1B ]T--̑L#NWi9k :b(!QFR]ōuvfeH f `^[r30EEkimcnv(FNNM@Q@Q@Q@Q@Q@Q@Q@#}o~>-QEQEQEQEQEQEQEQEQEQEQE44ii$!#kz{~BQ_!}MBtLY2zB.-QEQE5})ào8tTW'}Z?eq@ީ{"vg4Rlơ~zTH++!8ESK7Na?Ug#U B(3/ifUxCE܂)~QWC4M6HawTRvxy YiX[y3n}w1ֺiܱR>-B?R[C4syOr }1kp5UL1^Mq%JB0B(#+ت1h+%lSAň.ܟgˑ#(NmV]eY^'A{ H l~2sk#u/!{kyD#T q[<\ais[^1}}EWQEQEQEQEQEQEQEQEQES_"EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPHt)i- K@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ ?|} :~u#OER܄_JMh4%(_Ҁ'FdjU0z((N5})à#iS  @o>:B@}v <b)R@vt%"P> (!Hdfg5Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@5)QEQEQEQEQEQEQEQEQEQEQEQEQEQEI,4E~F>_\|% n~42gxK)P3ןƺ(YhMw}01Œe*+̗pg;~OR>FI?5IoE$-2Pۊbay3帷Cl,sD$n7+a ecW[iPq/ܼQM$[;hIm?@rzs->Itk"G5Tt`Ch(((((((((o~OҀGҖ}(((((((((((>M?|} : n[}*[޿PATpjJ((kSAMJp((((((((((((((((((((E:w( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( FKHt(t})iJZ(((((((((((iigb)$G/i`z 0=f|@tVg/|4Efr>sZTQEqPEPEPEPEPEPEPEPEPEPEPEPEPsMFI]QRMg>3LY c/4/b5k6"2es=8Orߦ#CsF?0&5_MW)Q54K7o2A #V5?@ 6>x o&5oߏn?HoQ/4'`' דթ+ k溅&cgO:8ߖ CMIMosȋ7 {➺IlPΛ#O?]Q-'  ´#/vT7z}JCO- 'zH|{kqyiZ 6NCR=մq#MHWt#OG>ڈ>?Gw c@ ͹>r]dj6Pd)pi:ޑg'uYl*M>|7? &ϹHD? M?#zZ\Ļ$sBJ: 鴸 + q@(?iN%3ٕ!aq}+YmWM\ &ȀDyPgj(wnI X[lp~\rsұ;q8Tiy PKPe@.޼'z(5Ŷk}b8M{mx7}s<4Z]Co,>Q˺0g@-Q@Q@Q@Q@Q@Q@Q@Q@#}o~>-QEQEQEQEQEQEQEQEQEQEQE44ԩiCN cտ:>~u;OCZOߝcտ:_ZOߝcտ:zPqi9Q@Q@ Jp)NQEQEQEQEQEQEQEQEQEQEQEQEc#anc(o^)_fI>2VPz[T 35mUՔ0{U P)s sW:ey OZ 6lֱ" Ge=9Ud0LNyG/im؞` R{zԠB>X6lGM?|} :#֫/VIɦ@y/G?y@ L@A" ( (qS_7Ҝ: ((((((((((((ye >\r Pj4rZUƤI%dc?'%% ٢O_?i/#W_)A[S F.9y|R?yA·_r!s3>H|9{C] }fܿWg; W7 (_r!#2!C6|WME? /CF|S[ºQS+E[ʎ\KRR)6j]eW '$|!ݩIe#"+Rhb[6_QEhQEQEQEQEQEQEQEQEQEQEQEQEQEQEV,/ JʑzִsO,2 $lXz:BFXSkZU;/ù#^k6R >Z.;AЛM=6{,oh?W4 ᤏR v\ma9 ހ7袊(((()-#}}t})h(((((((((((ө1IRaGF~TRaGF~TRaGF~TRaGF~TRaGF~TLϵoQP\J0}*~TaG@0}* ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣ ?*0ޣE.~T`$((((((((((((((((((((((((((R7?JJZAGҖ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( KXaxeg"g wEO^;N5l4DuQvgI m%~@wXѝ*(3=MGnmO(e@@w98JG[YZ["0N=jz][ۭIr[e?t{dWIa}fPnĂa`G ¸[&XolEq[j31eUGt[ݤ%1N2Hs[Z֭+wCaMڌ1r*ߋ4>kRH!T#9-=jd< %If\ >6*F9ۜ+-bӮ$:3+-+p#$g.5І[X7 +ei#@5) sm WQ m,8Rs q9W*,\G X&Z IK0}xĂvTʞk1ʩc'Yg+&) %II=:Sjֺh{yr2^e[톰wHr^-2YsZw2NNs30dvupg((((((o~OҀGҖ}(((((((((((((((((((((((5o~-\Hd2:Tj/4W5[xY`5kam8ۨ$](btR8̑F#2 T0xkE 69⑤I~`N{1ZP3xO@y ڗ,\w~V,4+Kca{\VQEQEQEQEQER7?JZF@KH>REPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP|6V˘m& #Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@y=?^nC%m+}S?|ֵVfϦG-h2 gx+guq֗7TYxv؃\hl%ҡd{uhTep#,[׌)4jz2i(v""*]v4QEQEQEQEQEQEQEQEQER7?JZF@KH>REPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP? Wm&o'HcsԴ"I.eYMXdϜ2z}^Epuhj#Yڠ%9\]*ܹN0Md>d1HخA~Qz((((((((((o~OҀGҖ}((((((((((((((((((((K,pDꑠ3+ǺrL$ _^Q^_ӯMԨ癍Ό z, z߯yU?*G=n^X'וQGF*G=n^X'וQGF*G=n^X'וQGF*G=n^X'וQGF*G=n^X'וQGF*G=n^X'וQGF*G=n^Ɖu:&2ȘגQI{ipYkcߺY^֞Ř'ۊկI8=K*]Š((((((((((((((((((((((((((((((((((()-#}}t})h(((((((((((((((((((("kʫ~ ȲZ2?*啢]Cz jޫjmXkqAzj9k}F8}x*5t-8\oH82QVeۇI X%D{VG 3ڦ7Ъ\a:v?P 3a6w:BK-hpFlJzrn-V DB2_pE1vF̅ڮz'SM҅u'ִ2 =R8ʨbwjvдa TQN2s5_Y͕f!bP13HA%3i֌Ь2&sgw+$GW{)t}{1 W8Hi?iS8iG停.Wo?x~zUkE4ycyΘDNplvkIA">\ ⊸_z:M_LU;ۧgŢ^4sAp2E21 _0@-NIŤ!a[$m3XZw2/v1Z}/ >Yt,Gǟt1?R>QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE'\O^O^$i,maAImI =R{~cOMӚ{REPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPHt)i-4:|޿-zx~tRo_΍I?:7@ E&޿-zx~tRo_΍I?:7@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7@ E&޿-zx~tRo_΍I?:7@ E&޿-zx~tRo_΍I?:(h(((((((((((()-#}(ؿ(K@ ؿV\Z͕߭Q4%eS1 Xddf4/GF%8"ieuHeV>9+mvxi=q2?*6/GYqa}mm=и$I#U $f`:Xgjd]B2@{zO?*6/GYZz ;CH ` ~z7bXY  H?4?*6/GN?*6/GNbt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMQ?*Z(6GFhڿW򥢀jt~Tm_ʖMRl_ʝA u4݋Q?*]ѹ?:MQ?*]ѹ?:MQ?*]'xLkK{sr;nڠ{hT.ZjέhR\v:Q?*wh4~69ǸQRj q拺?*6/GNbt~T(EQEQEQEQEQEQEQEQEQEQEQEQEQEOҖP>R>W-u}q fF7/l@yGaש+^iBXÄbv:dt?e޿\F[~ջM;csL9:lV=y^EX 2?s֐pىFH rGwDrQv z+*` 8WiX6OMnb)Ķ (Š(((((((((((QE-PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP J;kLd9+S3? endstream endobj 6 0 obj <> /Font <> /XObject <>>> /Type /Page>> endobj 7 0 obj <> stream xVj0}/\vfB`e{C yHk&Hn4ZMc;͜9g${PC՟$AUqbcOW럊u?~xUVp69jgoZ}B(x !K@#&Kuy.{ N`<^XO>J; 2aEH"%!BsžfuKN[l5l-ZLHaB؎= b#bKNc%`iyG )1bWjXe޺DX:xYmB4"O^VV-vbWaZ,,cqnDZR(7;#calh s ~)#MBH(ǥNl 0.)hAa є׊eCIoC20{,ZLEΚ/8 g/O `i$+e)C[Plԑ> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!222222222222222222222222222222222222222222222222227" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ? ~)!bB(܀?k0*t@UdXay[;QK{U֩v"i$>giq늱i߶ gEпECosGUu>m.nVMps]qfiٗI] TkvXgϥj t4+7NNiJ3qִ E-R@ E-RnZh6\]%CCvg 4hc/kjޗf\.i 4;(y)䅂ʊ(H/-'"PFPso~]::QT%bqOs[MUqOSN,g?*f ?"oRqNQ^gDl|<?G#|?V7?g7_[pk\A|BKR#`wcI䰊A+;z S5 Nf6&nCykrS&V"QF[HyF$R y~fMHvbc=wJ,t}>qp+$D1ʥn Uۇc؜giu'cYZ8K*4;s*64ӮlDUR~Vc8RrWCkmeWm\m+$ws/y/h[|dܟFGO@[]Yx?Wy<$ F\RBtJmzr1@νgy-%W 2\@Gpb{4u8fW8k$UP~B@]Eލa7u 6~A|-jb*& ̇ߍیPQ@ p6jY_3ICfvr8V bA<>tƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~}(Aj+g1G btƢ~Q΃ )UtBVrܥQE5$I(8>:(()2PEQEQHK1Ojj~awhQ@Q@H'BnFx_֟7c?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>Gjc?}֫@>?#5nBIci? ,}֏VuR0Z>U ig?^Z>U ig?^Z>U ig?^Z>U ig?^Z>U ig?^Z>U ig?^֥GJ*EPEPEPdqKtS~jM-TQڥ p ! -^:eq;A Li ,1׊IV FPQEQER;BǠEPE !*ADuS  NM-"u;Wڔ72p2zZ)l{y8۞sOlr$7WSєS((ɥkƌQA,\xë`n޶2L@GfXln*Ci2M ՂO!Xgq+zfkXmy$vcܚ@Q@Ι{Ed66$^\鋨7W#Gt=/ #& u*~rpF87v|REQEG7U*To%1c"BDl,~\dsEF!N ȭ)Z+o0SxN\FOIy8/G[q%ضż3ݞ885n⼶}Y[^IybcaFۻ)Km`@V;>ctUnBU? FkJ>} _'z7 ;k*R4@l|CPƋDD c{d~^l[]v9Ukqv>{VEqjZpZdҤO $]]Ԇ| m .]a5wKy}]LdOn?"Ktm-(d06rAӟc_,4 /l (8VLd*Iv`^ke#)<1@!_Gmo*]rA$+,6#<]%ɖឡpgkу*LZIUw*;WVt_ǽe_,? >OYhįGm0!N8>SXgkU1G$kp+^y55 P]<ب-;qN ɫ@' {,IfB,bfyڵI-[yfP^\Pej)fMyGMJ|c2k`E2Io##v9v髴}7ؾ%+w/H2u'JI`n $YTZGiil*;̓ mnp}`YB(\ Uby-(Zk]PʗK}4,$&|Wi$mԴϵUݜD$nG4tW WVi0k~\]"eq"aX11ֹaMrEhN`*('pGJ +]桧%5ȯHg#g\/Rq]5QEQEQEQEQEQEQEQE7֖}EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE^Aֿ?^A[ {+.mEclU 0q giΕɨKm2{p L b2vGZ쨤3¶S sdwgqnY~"5=}^ +9{oȄ@k<]&t9Zx"X11mbbcdԡh, H}<ϿsEU-&Ai4Ugdq+[71Md-oZLmCgn~P7"Gԡxdrh3*]%PEPEPEPEPEPEPEPEPH>}iio-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@z8M~}8:>2լ-}.bɛ,ɐGU~zһOO h-wR-bE$Mμ/A$RMGĚ;G?٬DgPUu-H.uRXmE L\ gv=,xoJi$O>:msFkMY9vU|i?mϾWηsLw2j)s2>{7U->է]sl+5'oTB[3OIt|'1(pAg"|Z M"^[ʗfyU ;yh5O?|>w]G|k>-#$173#x,7Uz'WNë<^ݣP |Wũm5bE /a #`8Ol})V\N2~n K_s\jLrF%Jbڿ APB}^g>f@ۻvVӯ-e, eL ӎktxnfFtK4eHBv{Mel- ?=zq@AZ3{#ʖr@g c@4WOV]D.bHXK9` ;qוCimgě9G|C{w}CH٧۵wʰxc9[WG_,4HA"-׆5V:'fj=0A=zsW>-f̲LFIqcB>mt){v˵w=ڀ=~(((((((((((((((((((((( KXKX(n3]O1#5NGbUG8TYԒ}i Ѣ(AGzZ(Lg#QEQEQEQEQEQEQEQEQE7֖}EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQECc ׋x֭gOJuiѠ%`ͅ;}{UzN6kWV7$;[9r;WxD]oU` uErh5sD!?A9O4]^O'J)f'x98&>X<5֡tŮ/n9A**((2AN%`28[9׬St KeŰi`&oNν>C x P~6qGv^0K gߵk^"N;Y/#t1'WQ@CB|]/iPOmX]D4˗'O9Vi vPc x۠p3pP 9-y-cKso!q푁z|O6(\a /N3WR:,Q24{w/4OYsƜR g޶~Q['TpHyPHz7a/H2Xm.FvүMi:eźf-Z;pF19?ך{=, 7 H+T> M6K>C F[$6k ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (+>/b>/bKcJP0 Rq1d ݰ]yx m*K^ "Lr'˸N\w9a=Λt}m#%!\okY`v'rrʪ=}{\<=sj3'e^ܻ|}`I0(#z_chvz9,@'ޢqꗦQ{dW̐#rg""'=/AW[1< >=$&_s%崢ݦ[@LNܖ$:o'kdh${Eȓe-G ;{Vntm'N[3"n9Vpr9ks1KqlѲ9g݂j ?·6RY,Cf ]\זa@3g_Χ k:pgWq8s5 1wjukwHoT1*7dT~?6kKۙڶ.ѤM&ܿ\  $ifg|]dO3ɌΡdlq@Q@Q@Q@Q@Q@Q@Q@ yQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ\u*,bLRs?xCEdZ\>"\is5M5PN<Jk Bky`Q6dW8'_AUy5+*eLG& H8PCER[ed z1}pMePIs@4VN5.Ud`ϵI=V}Rtts$g8P~\G<AEgOt0CncbS0@<£.&#(*I㻩6tq Fb{q;oX)%#p@rE`I\:&)@ '[nnR|!_1BFONEb$!x7nVooʪvƨwr{;rOҀ4hS]DVRx AO[őY#$qP(((((((((((((((((((((c oU<-*|I㋍?S(ϒq#3tPԯ>Zf5kŒ9w4Wm~ Y?]/ҡz𰵟߳4m~JtWm~ Y?GF'*g^Y Y?G,-g/?yEyg,-g𰵟߳4db|ҡz𰵟߳4m~JtWm~ Y?GF'*g^Y Y?G,-g/?yEyg,-g𰵟߳4db|ҡz𰵟߳4m~JtWm~ Y?GF'*g^Y Y?G,-g/?yEyg,-gIMUfS46~e A#O(}CAPXGc Y@=EN>}kifw&(0((((((((((((((((((((zk[w>dޡIֺ(x~cF}3¨g\[G2v( gCdWEc[B.*3D[yϭV]>o"F4rwu>*(:緙Ϸ'ikc5-.;yȊݱEf9?:V3!y $*s!!Q)f%UN (2+R1dגg qT ns"ijӫG'~\5EbYEmq mBg$RXJK iQI8}ZP?um8k%3:s`Ro%*OOSҶ v\qC}X2;Uך|֯˕ # (.Nج$y>գb(fΟEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEAcxe:+K|K#.]׵^EW S167R>mu ƚ&%Gϴa}+*]8%sJTki[bM>~uʐ7!;8@p$cXN6qXkDz9`CRR:;3"]PN9l$H]BݹwqZ1[m-KyNeFH )+vSΉq{ ZǀY$zJuT7#lX ^KcrY&T1m7@T/}N:ؾ,I%5]4TIr(QEQEQEQEQN㸣gdWҿk~zWЫ*Hԣg*zk`h*3+n]ȣgdC: ?ZߡV渆&#HTw&}N&%6J;j3ò"ҿk~zWЫѬ:7!ƝGgȡ: ?ZߡWgdPҿk~zWЫQj3ò(a_B=+ֿU(??ZߡG@_*{ZpzWУJu~=Ox{8vE=+ֿQ: E֧?=;"@_)it,Xۣ!0R}X{8v AKH>}j ((((((((((i6:֢3ZSK*5ݔ#!$}*&4gVVAA3hKKLۻ}|Nvox[qip!'`3`N*b:@Qc \HE]Q@hTP*vE]Q@hTP*vE]Q@hTP*vE]Q@hTP*vE]Q@hTP+9.!$:$TvMT3$1~Ά+- "Q 2N>v[5mr@((((((((((((((( Kz<-*KbuPTn'#I3\gaaM4mm,_R1x9ϠQHa^mfެ?f B 0as@=k W :m36c8@7 M*T)m}u {u@srdu')=RIP~4v,Qٟ \Yϥ[ 4CNټB^5fi'x_w#64'6w p,V6C2y<ht]FU)mXa`S]:i5Ƃ-mkeO\ȗ1Gv$<g`3$ /_} mGw%y#%}R==-ZL/ӵt'4od[i&ddtyA8X"Mnݏ=:X;"UsQѤY9RҸ3'5z ( ( ( ( ( ( ( AKH>}hh(((((((((AotVR^ H$#T玕Xሣk|TxkSW܄T&͙ 9EfVDn򱴒H؟ƺͭQ*ǁc܌0ƴRn"ٳ|n]?diZү$nEp+A$qZG3oʍP-s)ƦoS #  (2OK;Yes3dr@JR捱|TTbl_4.ؿhEEK/6@QR捱|TTbl_4.ؿhEEK/6@QR怑4M-mIBe9RȮIv$u9'9?g>9_bz*9!4s;4Ob,U?w 'Ii?=OpU#iO&sLu4_pU,K՛ FGg2 d`|0RtjE]9&h^i6YE,i6@QS.bvFؿhI{;LL2s֛ FH6ؿhR[PeY cKS{hIV=? /6@Vm4+]pO¤[I-݊q}wn8T[KdNU"S$aԑC@((((((((((((((( KzcX(C ( ( ( ( ( ( ( ( ( ( ( ( AKH>}hh(((((((((AY5t셔QϘKwz8|Ɨp JfUY19W,4FPbc`8&Q{TǪ|חtBLl)*ے9nREqkףQVFe3I @x\Ny¾2O)*ﯵ]?RxTľo,'Mk57/=@?^yI*<kqD[ciKn>t?RxT 7?RxT 7?RxT 7?RxT 9>jO)*UCǵR\`=0iR2bN85-yXd,#nzS>7+M-9k{}ġpvFpx힦m 0mۈ#;udc~.U"ms:6a9 ;y'P$xRcIp#qq鍕)>u?9W/†S{Ko lÄW+sߧngAT'Ju)>yA#~>E!(`qIACH2W-Vu2ׂƩ ZLkk/g{1>XP =#/y;?%BêH)Rzʹ 5e¸$cڙ/@Ǻ5d(jg?CEM/G?CEM/G?CEM/G?=CVBg8S<m5H }?ZuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@m\Uc oʬS{lQE!A{{m=ܢ(S1*zϟĞ3L3?=e>l#g0ڷm`Tr:ՙs"KSG$RQ1,)ۥEKGj U/o4ZiדyH#SXƩ]&K-g.%hw6 $c?sđ閯5\I(Qq0}z#Fu-&c9- _r{DWOm%i2E21 ZxGK{;$~ \~$ ƹ]5B[Ycm.ŋ|I0~rǰ55tٵqL5-Wnz@Q@Q@Q@Q@Q@Q@Q@Q@ yQEQEQEQEQEQEQEQEQEQE KH>U-VRPP; ]$psI?T7z4%̐9u5CM$+\`}hܹp\^n-$&1pW;gүhZ%ޙuLf-9EQEQEU-溵1;BzDT=S c8G5Z@tro$cZHlc4ߴLaNIw1R QEQEQEQEQEQERfMQMQMQMQMgi~y㑏ƀ,jW˦:XSv޸###Lb=|t~*?LjfG{I"b^9'_Vm7^t(VV:OY o֥%m4V$ ЖabwRkGQ01^+ToTxwɯkV=# Z3y\r 1K^|k(nX++#UJ\z}2FFG^zRn?C\|mv{4nXCrc5{@.4.ZwV#opgM^ZߣT@(((((((((((((((N }X# Ѫ(TԴ}VɭnC$21WFR9nmW/0Y-f#,,m(#®sy$$P^X^tB+" pF~F_閐۬w7kQY5t9sWSEaҵ;.R-L${z Гe/?ui%]&SArG{QEQEQEQEQEQEQEQERZZA@ EPEPEPEPEPEPEPEPEPEPՓq}D8apkX};]2$12jOc_ ?/п?QQ bj|`Jס+yE³^?K/O_ [].+*KA¦F_zѓ*Ёs"bX@'+)eX9?if"J#BOQ ~?|9f.O&>SypxQiEn!P 9_zKjbDz/%K/Oy~ioj}B뻖BQBJՋHK/O_ *3?ȟ:VWڅen*85ElҺE5J3أ7:jO'ڟ(M)FOBBi6*J(=<JPB) (]7j(gw&욭J<@whɪk{bI<hɪk{qTgw&용=J'cPB욃~yOZɦ?4]&寥Cio&7SyI'@Ig+NvAET(-f-6\\1.7H?uo/.麜zNV <"@elHý^Ë6H:m,R2TZܢl_:KKrP2NiW.Q . ڲBOa@QEQEQEQEQEQEQEQE7֖}EQEQEQEQEQEQEQEQEQE5yΩZU!Fu%pNkчjZbapa_ )<-kO?^HPI*?=} uk<6_k~/ 3_CG|G|2[K&E:lG|- [=j?ֽ-ZJ?_pf<-k Zj*(y}G>[Mi>:GRnrݝTVZBp o~o{=OeUr㚎:7q4̍Њș (ߜZw_Y5 8!*{Aڬ_5rU#<ZeB\w^UV*|}+ :\< Q'Ye2yU*s <.Ƿ҄ܕc^7]+I+K}+f?[+i {Х趎z|+$3Y+SPf8JRYeV!T~^Ck>xY%Eh[kzwA0ķ3k{?MMoIϽt~֮R2H.k[D-|Jl?;=ԟOE7V"HTԵxY&JFI\5omF5-k}H*UZ sNf2jXXGZ秽z{j(ϞyU >z{秽V,GZ8Nm ׳ϖj*)f2OLۧOzsxjΜWOBgT5t9 3RV3?٭8ka>P:f:((((((((((((((( ȫה?Jg?d9pVT&(삊(W3\a V4>UfWMMwa4B DkFL9S}* |3OlK$!$}iio-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@>RԴ7)ޢQ@y *b}PEPEPHpR'OҖ'Xu}KJZ7ށT~_H/ Cl ŏJ.跗R\*X~2jV'}*̰R^wZ~y^g]ӯdfLקM7AW28YOS4y|g(?_?O槿hgk~5=Gɠ}ҏSMj{&ٞs}3OҬ? Fi#Fcc\ ?]t}iҼfS U_hKǖ79y#d@+~Rn ԕ(ʥGR\)Nt(((((((((((((((3Ä'$Zu4@U?; *FQEQEQEQEQEQEQEQEQEQEQEQE7֖}EQEQEQEQEQEQEQEQEQE5- K@ [cҪW( |\#;9( ( ( D>kLm[NkUx whS('.NJq +e]ǜc4s|$)gAAZ"3# 2$,>fT*$*|}*WBFA ^TԅCu=;Nҝ@XQz irD!j2kRp(;ĉ3VNmm"i6 naɩ*_-}(ҳJqTVH 4@-ҝME,q%pz榕@A4%W< TU_5h_ƀ-QU|EU_Ə5hW< TTP6rsR[FjykBFj$pPc ,Y".`S:( ( ( ( ( ( ( ( ( ( ( ( ( ( (21"^0k')h+Z(fk:z,pw +f~HF2OY!oѫ@~7k ` 5Z/bqk6 ?P]f_]ByXh{S "^m@u}3P9$Tpq%NOiuEn. !=HX_v>gn0[ƕ o<50XS]PAi eyf Kaʀ=]4 ={𦳴o`GHl`sr0X!Zk%%xt$d3A$]Ծ<1hN3ހ:X<_ϯK]kaܖzpFT4r5JrY-X\T [mPK]F Im$DLWp"^v I..o٭!C#FA!vtQEQEQEQEQEQEQE7֖}EQEQE2gA*[%p]W4b}:䤑rŘ4XO3Ga'O\}GdFENW)pm-HrJ2QEQ@Q@Q@>TW24QIJ>S&LI9 krx~U/G(E?*>/ROҏP_krx~U/G(D.~UU?[(>।O(h(((=Q)h`z Z(0=.1EAKE&R@ z B,p}* Sҝ@$⧛XԸ)QWp( TU 0(wҌJE]Ҁ)QWp(mo>N5:PRM^Zu5{hQEQEQEQEQEQEQEQEQUu+gܨC >N)ɤݕ3 gJ}~"xe9f9Y"?Cu?kG/WO?_گA 2 +.nt$gcP1Ӑ?:XwsU+((:7x ֬ ȣ׌zLQQR0n-ﭞ%ÃSQ@#JA7VEmpAjRQ@nt YQ@A@ϮQI2mo+ EV5+t+Hhm>6}? EWe[*+mM76BW EQEQEQEQEQEQEQE7֖}EQET7?soST7?soU [R# zǚ+#*+=5]ەgCI;KDp1]s'~+>+(((A}楠f 2iz{0%867O@G?ZV 2:R"FEPEPHpR'QEQEQEQEQEQEQEQEQE!^5 䔫`c?>^Q@y*Y*XX~{Q翵GEI翵{TtP{Q翵GEI翵{TtP{Q翵GEY=Jjo>N5:PRM^Zu5{hQEQEQEQEQEQEQEQEȻ׬ ׿]Կ_֔TEO#GSE(S_p|-լSn#) \ЃT5\Ow9B)@9秧UE99F쩤8z kk̾zO9G|Š(0 ( (2<+"^0#¿(hU?; *FQHHpRhhM{_]*{ (~׷ҔgEKcq$j:D7Xn]IV%$dEaxqoG$-]A^U|if NX 2o60W=zPOEs%./z.c< 'uklI)>g$dWEPEPEPEPEPEPEPEPH>}iio-Q@Q@CyS6U5#ten0igq=QQZSh ȼc骰}Ng>*EУ"yS]4OoV~MHA0; Ю RJ.1 (5 ( ( (}楤yh_q4r7S@(DIf@Q@Q@"}KHpPEQEQEQEQEQEQEQEQEz/@Ҕzi#MyBc&G@( 5gR;\4bt~Uh@K?*6/GQ}vbt~Uh@K?*6/GQ}vbt~Uh@Jߞ1}G7ZjtMG֚(QKH)h:( ( ( ( ( ( ( ( ׿^Կ_օG<)so, Q`B\M&jhZ+ -q~V7?}ڿ:ϙxZە;Et_x|'ҏkD?Ɵξ/VGzOxG$̪ܺ*kkB!TN (N((#¿'Z׬ xGE01GdQEH²|EKi5LJHIQҵ:χ i\0+)a N00}vq Ah'( ^bOͽW`CRin 1jTz:P!ߴflbxZXe;gNxgA ?vWBnA2XD)$ A1HQK@lrE*OPFA+V^-kog *=8,s!3խjR!bqV ӠQXb3Ѥz BjZ( ( ( ( ( ( ( ( AKH>}hh(((((((((A}楠e 0G)=)d}fA ।O(h((((((((CPLj cUZjp})5sր*ՉՏ(4Pq@^Rv)?@h^Rv)?@h^Rv)?@h^Rv)?@h^Rv)?@ *QW-G7ZjtQ'5.4=~nZ4fF4fF4fF4fF4fF4S7R@ ( ( ( ( ( ( ( (24<-+u[(AjVo|5[8ZUu>6L~QEAAEPEPEPEPEPEPEPEPEPEPEPEPH>}iio-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@>R3*{dBQy *_:/Gy *_:/GEڀu3΋ES।O(h((((((((CPLj cHjNNw2jXX篡_CU z<5^篡_CU z<5^篡_CU i |S<5)EMEC?QSQ@la@5-E' (Ӫ54@ EPEPEPEPEPEPEPEP tGmP*VIںօ`UN(+'@k#w1][1uS=CZx7riC)]S@/Tv5ɗNMN3d.pd=q[J[+[hKL.Bd(3Eמ[jo^>\}0jOkMO@$H7ESГT^n.ʶXv8PA^[mi5di_M]u::dE`hWV/1mC=d oEPEPEPEPEPEPEPEPEPH>}iio-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@>U{C>R +[hm2hmQz ɢAF((uI)h(O)i Z( ( ( ( ( ( ( ( (/SXS^%si?0@4@`oFM7Ozz=r#\p$1ꭡ=Qa\/;PQEQEQEVf@ ֤QEQEQEQEQEQEQEQEP+zUM ORKm$?(S0PQE%Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ yQEQEQEQEQEQEQEQEQEQE KH>R%bPypiBQ_!}MBtlY=iW ( ( D>((((((((( B1^%:JiIk,]Y@?xzqstW+qD/xg~=jOiTCq9h۟6VMc)f;TqM~>KkM?)EVn_wqOz쨮wNu#o4DIJ& 6T: Ɛ aP+㓜PSEPM5ȴy7uA2q)&u @\Zڀ +M,XڦҼK<F A׀_*kEfB y.%`d=&}jZ\UO~AEs֞'{sM8鞝zzU+O5֧ GU7uWiq,2HU'W5=QAHVl+I}(k ط3#4y(AtjnZ*#YU=u4EPEPPf@ ֤QEQEQEQEQEQEQEQERWj^d݁:*T&L>UkBKk u 4W==IM$NYnH y@\{%Q\޻ mc-J1"1ՔҀ:+<]k:ţݤv*bCūsxV]A5Ha5oK^%#<@P=M{UK9'fk^w^KU#N>V5.#k..?Z l-_ .Qʹ$RO>¡}rG-(W26 #`Q@fn"C4IrFG`qV M^[FH QEr?ig|JJfb9ےusú֑u4+‘ '?ʺ*(((fST3}ZTkR Z( ( ( ( ( ( ( ()2 {)GG~**f{E+iUMZLQ (57Yd0=As&[ۻ6جۼ hW-[k +.'Sa4q9% rx+9բF@QdF0yQkеMkŷS&U $29^Ep~ Hl5(6FYb3]3*A?<6 >-X\L2<\fQ^Er mN)ۻ߉dRIiIٗnÑutQ@Q@Q@Q@Q@Q@Q@Q@Q@ yQEQEQEQEQEQEQEQEQEQE KM"@Q@Q@Q@Q@Q@FRTd\P|ȿQEҢ-h_2/!EWJ#/EE7btQ)Pv/EE7btQ)Pv/EE7btQ)Pv/EE0tTNSX~$~GҔR&[RpR 2N(6(?O <k|B ?k5a>䈳dM}i$bt޸(?(?_AEOJ>@QSҏPTgk~?(?_RHٞsS@nA*0q\Ug(ժqrIGVtMeYEr7YzǁD5['w,8'8 ՟ /_] O; WN8O`ښۿc?oR;j:E $, 8#XkZΠmⵑb62>c wD%gj?+q5\hʒʗs%w2mTg 8<41}sU9X Ж :xRK$3`J yzKMO̲2j+e>߭KmZPO#1lr:Tk>-!+Ylxǔ0FYqۈw mpHԝ7hx>f{r6~0Gʽ}^M DIsPbdRр@K +jM>=>7g6:n ( ( o-E/Q@Ԃ@ EPEPEPEPEPEPEPEP_U?VekE]#jUKb[QR0(_xWT =]xsui f[[F+ż`m}7fX-,mxXC@u@ 9_}F{dWc5Kk{}Er 7ZRW60$=>Z^uk}$ʻ,f9We9; _h5}Q;VQ표}GJ٥ƛwsoă<:+o4-/ oYddWe,YU=*.a乳SXH1cCWg$ުm^dB!7Lv`$Gj cwwuS#g(gP,`^G!T`.{N%wiXF0\t@2ȬFvyqZZՎ#0 2@oA@<Mq.6gw$C{\ѫ<)#:8GYw^{J kx`-ThfFbx<XXj^mĖvΘUHvvt v|Lpi6q*BV2pb$9!O@k澶do9EGڧh<+C:vW:Uݭ2 , ONEPEPEPEPEPH>}iio-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@i*Ji v)ih]~tn_Q;(@n_Qѹ}GPeM}GFC6~u6Pۗ~tn_Q;(@n_Qѹ}GPeMzBGRHwp*t`QmMEAm>bѴ*F@(OOX>m>bѴ*KH(H-QEQEQEQEQEQEQEQEekE]#jV_V?VT&%QE# ( ( ( ( ( ( ( ( ( ( ( ( AKH>}hh((((((((((((((((((((((((((LQZ(((((((-PqFuݴmQ@ FuݴmQ@ FuݴmQ@ F)PqKZ((((((((((/_*+R5"^P*1-(QEQ\=e}-PiIc4k%wlp:cPEagqqI"IrlUR?W xzCmriK ڰRPB$`@Egzcm+g>e>~Y]ri2I#m߹O$x=;zfנSOE3͏z/NAQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEeoEm#jVgWVT&%QE# (0 ϖN_AP-ozn-#loʝ0x KE]M"DВMyK2l Ŷ zcxW.>nR΂RKz&'(:/jck8Ťy,<~ܚRΡx[YnW˓ #~ƻj(5]3U,8uS,P)en3souǗZB5"CU@>SӜצ@4WF +x,*w1B08$׿J袀 ( ( ( ( ( AKH>}hh(((((((((((((((((((((((+ɥ7{&92-1v}qxf_~y9}PKxv@M|?Gc%` Ww ?#P:_P.-zJv%1}kZ: PEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE4@YgJ8ZUSQE# ( ( c.$Re*LPVWPAQEQEQEQEQEQEQEQEQERZZA@ EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPgYҿ/Vfw CiUOb[p=xv^].VEPJș Ѵ[-O[;ʦr9{R3BĚ},6rP[D|*ӆ#S^X> ۼ [vV,:S@F|Cbܖ`Ȝ 1oJR]ס<Ɗsd|G<"!w r v<}Y8 KfWw.͞&Y𮣩i3ۦ<XAYU*\3|ry ;V10n-5^;;3]a5^]^\YM/mLX76~\QEQEQEQEQEQEQEQEQE7֖}EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEq,U,yFۑPyCs*գF<;:+ɿ;Q ޻=B|Gu|?:=4WwxУw{~+ɿ;Q ޻=B|Gu|Nfz?(]ߡG>#AG=foNwxУ_ ӣٞEy7'z ?;QiYw{~?(w_?g^M ޻=BNc;t{3h*s+Jʀ+=: ؁ *#\N '=CNj;:B((((((((((((((((((((( cÚ`HZ5CC4N"Ob[QR0((((((((((((y7ր((((((((((((((((((((({?6_a/+ɿ/Oni%3 *{KoJc33mJVWgݑ^ t4 CORLsӎk# FA+:ucQ]:nQ;1DhXxEF(pf86'2ˌqN1ӭ2F,%ئ܏_cn}O5HTcHC5s5xݤ1m+p2`|ğ^UmoRGխoCtI'`3AӎRQԩaҍr2Fȥ]NOPhDiQ1d]jFbFO0M;pvygQyfՕ$Ќ[j=/KߩAbq9MG]0 !ʏ5zcGynay]ex2F #ԏWKiLZژ3UnRGQqA3b@,=+ x?BZ@?7g'1PA "Ԓ0m̀16Mj~A]匋wy12=̤c?2UԽȫMB ȣeCjȣeCju3+3QQE'QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQERц4;N"=$cF*T&%QA55F]s\uޗNoz~oDG?ҤgTMsN iYeb[p \i41i,Ip4[eewzd3\hVr}hh((((((((((((((((((((+Ǽc#e w췥m! UdJO#FrQE}1gi-m-Հ9&QE$mg[Ek Swԡ.,[vNG>Pg E79PCF2;V]/:6Ɂ*~EF% AVmGo͉ՓۗUf NN~JkW&kq (`NO#j΢F [lY7sZ\nc2,$Dp9EqbL)nF'qȱƥݎ&VbURXzI^Ns%cq~վ#Z(=(((((((((((((((((((((1ُ`uWM]qO*9nĶ zEX!I;A$y$'պ) + W;-ԍci%@r}?wkv,-2ܫ|b"A'CQqxͥ2\œ8 ]=QyYe;,.Fz((((((((((y7ր((((((((((((((((((((()WUe=C u[>| ?4Us˹| ?4Q.o/C(ϲ;?¬G<,{>| EVϲ;?/C*s˸rDZ[>| ?4Q.o/C(ϲ;?¬G<,{im n$oU@ MEoqERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWM]qOU**9n%QEQEQEQEQEQEQEQEQEQEQEQEQE7֖}EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWLUqOU**9n%QEQEQEQEQEQEQEQEQEQEQEQEQEqOybxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQ/:oyN/bxSG(bxQN* UtYEZ-Ķ (Š((((((((((((}ZZ(+_Oso{[66,cQzjet'N8$~^OZ]kmO;Ybf >ýIlt[}ɖKZ',`:@đ 0A5ޓl$ 鞀z@jƟ&j#{"[YF>DZ uc\IMigl.-<Ȣ*c#?C@x-,=\ʩ$6*@^y ^K+%`cZ A!hD!(iy9[:{AVL è 8# K>8VHanietk&s須TG:ܟz6s49O@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE%,fX$x&hs&Aȭ)FSv]"pWfۭ?(uAqT³?Wo>vn?֟³?Q X?eGϿۭ?+Ү~i\Y|Zf?,k_օtY`cp\[b1wxok5Ј.BP!|WMzhW_24vwAzWٿ+1JnZ[~GN JnMz2=ECoJ>\'Y=(( .tKI>q+BW00)}EziVhrŠ( ( ( ( ( ( ( ( ( ( ( ( ( AKH>}hh(FEu*OPFAMv h*9Pr0x)hUUPP0BE `0-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQER 0ih0=h ( ( + :Λ[2#Ek NjU] endstream endobj 22 0 obj <> /Font <> /XObject <>>> /Type /Page>> endobj 23 0 obj <> stream xJ0ù9i] :LټM9j$&Ei凷Ae&A9z*ͧ4w})2jUE$ w5[nkX o!^#<0vmMz*' AL1V*r:in,o+oDh&O+)5S*Rۿ"`?"H?ijM1ѨMfQ}[װJj'a9{ 3> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?-+LJKxTgwjl"XJ$8ѦNQE`E]!;*td.LpC9JlEQ$toCl X.Iɬ[_}nifHCU8 2O_jsIvoeާ=~#pBI+Wq׿l ۢ.E2;,*U8r2sO`y!JLT眮}(b"##$)CpWa֭HQ?gGu98x@TVN%ԭg?2=> -J[U$$q|IǭlXf=0Me\+j ((}8ʃހ%e XdKXh&vQӨj 5NMj%OAevqƬ7^"z)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hOsx;eG@tV~y]yY|nO@%PQKE%PQU+.nC41=+6H2 3JQXop-ŤK *{0R@"Iy$ֹgYN̾21r[嬟xc٠v-VYQZSMt&ѝax5ѿn7](Yjw*QvouШRgӣ2c쿙&c A~9u$zJ1lMYd3W60;E;Tgרс`cҾKd4K b-};+G5[q*-屑p03ך`@>,67S\fIU%UKܓ'+4𾼶|#(/һJ`&.^`pd%6y`'ݩL!ioXlNsfB##Ҍ:P=qc9V5Kȣoz/?u?:7/O`:>?u?:7/G;TeE֦ܿ9Q /Zrx~tsF_'_ΏO违jn_΍Õ`:>?u?:7/G;TeE֦ܿ9Q /Zrx~tsF_'_ΏO违jn_΍Õ`:>?u?:7/G;TeE֦ܿ9Q /Zrx~tsF_'_ΏO违jn_΍Õ`:>?u?:7/G;TeE֦ܿ9Q /Zrx~tsF_'_ΏO违jn_΍Õ`:>?u?:7/G;TeE֦ܿ9Q /ZҊ9r+/G'_ε7/FaʌO违`:ܿ󣝇*2?t}ESrx~tn_Ύv Mѹ?:9r//G'_ε7/FaʌO违`:ܿ󣝇*2?t}ESrx~tn_Ύv Mѹ?:9r//G'_ε7/FaʌO违`:ܿ󣝇*2?t}ESrx~tn_Ύv Mѹ?:9r//G'_ε7/FaʌO违`:4QÕ_`:>?uE9Q /ZQÕ_`:>?uHX(8*2?t}EK͏6? 9r7/G'_δ)haʌO违`:բv Z(aʌO违`:բv Z(aʌO违`:բv Z(aʌO违`:բv Z(aʌO违`:բv Z(aʌO违`:բv Z(aʌO违`:բv(_Rօu 6ϛa,=}nhY@h0AK@SXq@u;~ͧ\}H`0Á}Mu]f7T?,~hPQ@WJQ͵.d"{z(wBM\,aQ ( )&FcW*@ RiOJ N>o.؟Nyg^,QYf*H<}@XOSMk"X#!;jPp|2R\(sK,QK,Lb̏Ǐ$4x0>V᱓RQQX-Fn%L"h"0y>08ڲ5[,w_3;*+J71VI"񆣥QP*LD7qր;+O\k/ex-{ {x 0M[oGt%V?& VFXbܧz}3vXƧpMMIj-3L >QEQ֊(𥞗mwF~gU@kZPQ8B@vQERK+C.qVgVt=7N/u(¤1B f'=_ n.$L"\"z"A޺((x[S?ʵ4R2#ЊOmqO*"E REPTXZTXZǓRIj󙭵9&g[[@`O^Evᱯ.f[uO/sֲ|Ymq[As:On\y\B9Q0;Es֫fk\ޕ5N >A>C,h"cF;n(jǏM,m,6f B 5VFHDۑO1ǜ[2h7wV&D?fQ@kbcI &&h1 TzZP=:#>c_F=jPEPEPEPEPEPEPEPZZTZr ( ( qUZ|PFouu^dH"{W2tMH[]y62\@,7l~U@;;H2?m~|#2\o]Kkmo6L)?P+9_'7D+(!QSM#KגM1-!o4$tP-"-[&Bo:Ѕ6[תۇ*x{EEy}Xo6",fpW#Z~-jżR:Ig1&23}+V՞+[DŽN!0 +of; H3Fױi.~,jҘMxc z5Y˒8Kch!X˰'v֟}kZ8/vLPп>wTP]:i֍<1*ebYO,u3E$b5f*9ԔPY^#D+hɖHT$W ghC_dҖtm[6n23wSWH]rP]>k+W(URJ4N8k ( )oʌ7@x~T)oʌ7@x~T)oʌ7@x~T)oʌ7@x~T)oʌ7@s}oʌ7@ :zgOVῼ?*03}=Zx~TϳQtj~ P>ΞGթoʑw#=ZSoʀtj>ΞOx~Ta?*gը:z? QgOV7F}=ZSoʀtj>ΞOx~Ta?*gը:z? QgOV7F}=ZSoʀtj>ΞOx~Ta?*gը:z? QgOV7F}=ZSoʀtj>ΞOx~Ta?*gը:z? QgOV7F#TJ@#dRa?*03h:z? QgOV7F}=ZSoʀtj>ΞOx~Ta?*gը:z? QgOV7F}=ZSoʀtj>ΞOx~Ta?*gը:z? QgOV7F}=ZSoʀtj>ΞOx~Ta?*gը:z? Q%NG_z}7 QSpoʀE7 QԌ& P|֝x~T&?EO1F Pῼ?*0:n Pῼ?*0:n Pῼ?*0:n Pῼ?*0:n Pῼ?*0:n Pῼ?*0:n Pῼ?*0:(ڕ1WVePGOs u῕OEd_jg-"+ .dFvE`YqȠQLD^D)h+y+PAh((((((():pP(((((((((((((((((((((((((((((((((((((((((((((((() M>e*zk=1l<׷9-v?Q ԞO-ֵEHF!uIce W"iq>qc D.c}Wʮ[ڕƥoyMj~͸m$>ƀo\La|W5z <$N^09ڽOPv=jXCZڭ%-4s>Whؠ o\:^h)*x^t-ҝ: =T]*5Ĭv:}j~MVtH;oov?w@(5uX %8c13‘=v>K{N'kD˰sabpjҽm% LE;6v>E1*F;PQ@Q@Q@Q@Q@Q@Q@Q@5>SS uQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@VfںtAlo7  ހ4OXAM*10)Hcb?>.oc<=gwWc7Kj  ozIŨͧ2w\HEx5ϋWX0 $r=wWGxZJ:$.r=Zzf sjwZ#mVO\W]TH V-ymvKF}vZ+^Ow*퉭X?͢n0{O2m^-Tʇ*IOcEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPMO)EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP\OZ>ŧ_%vIp>c9 kȞdN#4󏅚L4;b2(KO2Bs[9~*~]vO-GEm.Ž\I7aNsҨ>o$q#o-}H~#Z }j2O$3VFxC U&@<4ڍ f2~,X w$ݻi:g97|?.\/jt)4y"0d- kEæI_Yn!`';j(Pq~Mg>x}jmGPyNv8g@'RmEu>r"VéWxTw~noXF`PVeMr<zye4FGt~[M#0[kO'4FLPC<0|C+m2q3c֠gw-L7Zo<=Fզmc q0󫷞 'l 1DK4y =({cnMkkbo{ H -TMwp zP*ơ;yAd!O$5kaɖ::@h:<"73Ko]0XgFr3EZɪAFeTdUu)JuUy`p3o#pUB~,QU ԭdA#Ex'{nL$8?:}owpY =A4jŤn#ߒ z3RBۉњD-lRI> TUOdJG-6ϲ@۸RhJTeXw6Fz>EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE#0E,$Iv"Tz(dڕ s:Ov7/4 Q{X\F #>fltȚ+ +kH,`\\,}/}/?ϥUmFevEDW N7>>T_*O0;"zM1*J)+N:}+ϥUW?>Cbݴn4Rzcy|G,}/}/?#Pc*JH7>>T_*O0;*+ϥUW?>Cʊcy|G,}/}/?#PcX_^+Q KK???쨮7>>T_*O0;*+ϥUW?>Cʊcy|G,}/}/?#PcX_^+T?nRQКOJ .v:j}J rCHpW!:((((((((((((((((( ZAvy#>T/8qz{V:1\%䄴G)p{$1$"FOփm"QKF#4Λ|̰E2%@r>c35fٚ[{xrO*Ɇ3GllJ񤑔a=P* V~ <"0QfG^g*)-,1A %ki)lRGc̟3zK;h2[¬:R**@ր0g>Bg?Տ.K01 'zgJ[xU$ ,:H?.DWC,2(Fes"wo@23}[icW#rA'9[g$HI$L 11PG˲ST!D/wt3%vߴPZ9\>4!@]z69O"b~\ xcfoTo ϡ AB(;Z Ƈw m>66c#e!~Qo #C sRPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP pHe x{v"^F%O5QE{GVFH$q]"g0\q Z6E<8+&ՑاaԌ>"-j)DJѾ7/\eZw$m?62}Pi )NpWgC(((((((((((((((((((((((((((((((((((((((((((((((( wEO% x{i,*@) N 9+\^g2(l%X.ᕳ1t_Y ݎp}뗢Bi 4LXSUuy&|'OZâPVIXKKG (T8 2?:B R~L`I(}v5bNŊHP/a2(pjrwpVҌēI0* ( ( ( ( +L'?Ҡi(tg46W6:}QFA}T@ Y|LQE# ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( v,F{ԴSM!C| >k>4v!C| >k>o,e`2r@ϰf+XRVMdg3G4*k}}chUo5;s.$Ȏ a$s*/R{h~$Yϴ?Gu# @Čgm7Qdg)c8 j99WcKv(C| OC,u jX_[V0Zx%]J?Q.ʻC| >k>iwU؇v(C| 99Wbϴ?v*j(p]~k>ϴ?]Õv!C| >k>iwU؇v(C| 99Wbϴ?Kx#m j}U@hSS u5>1QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEr>*|M-?*Yt=<=]iZpKB{>ivQZP?}I 2>yeyեi(Yn''zWKsJ+ *+k K;_['9E^z8zشY- $++z-o.23ʀ:}84}6;cl6 yczU!mIB]-}Ar%έ{pS[ b;"7l*]Uc𿌭ekƓ4qs  [ Ic'K8A.#(Z>PQ@֏Phg??hG?AEOZ>PQ@֏Phg??hG?AEOZ>PQ@֏Phg??hG?AEOZ>PQ@֏Phg??hG?AEOZ>PQ@֏;+YdOHT%$(c?:z32TʓPZoh/Wb _VOZ\8Ȭk{)`0~?JEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPMO)EPEPEPEPEPEPEPEPEPEPEP!yJoekk#O6 D^c:B0N>tdt>f dtn??:Fm3N{-ċs=@ҍiGr9F Jt[*JU-YU!f `Z|HV^DnO֏)3K߲P7}lo02FU8PE?Z@9(gh7sxu1cyO֛:77@Swo΍Gisxt)?Z#cM,D@ ?Z$e~F8f&>( ( ( ( ( ( ( ( ( ( ( ( ( (#UIƥ #*jBqrk%"T s:E52f(8 t٥ay[K#ne+ww֬TVs0aXYa֡ӭuT.-~mo܀@$4Ym-c$v€<jeu6?7}v Q^Z.m,C󀥃C\^]G0HKcn8nٮZ.Y-۬o+z ހ!m, HWQҀ(h UHJt1t0ã[%wy-?:<?͏*3`/@-?:<Xھ(ޣoQՍ(ھ+-?:'=EXھ(2,A*CUqm:7QZWQ}_>\э&6氵brHғ3WV3*ھ(]e?e>j_AFNu?֭(ھu1۵ojM[mm_AFUsVBKkxq6Hb]ɀE;j 6:+Y J%GRAr3Rm_AFKzΤ)6j ䷨[~ucj 6 KzΏ%GV6j ䷨[~ucj |M+»H˻ ]:s.X+'8sIW,|+)?`io>Fc̎"!$qRW/o&89U`&qڱF'jƴ¦wEVFEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPMO)EPEPEPEPEPEPEPEPEPEPEPGoR %xn_oCWGocKZ6U”| Ja;%#uXmXKk̎cTOֿ&Z'㦏LOMiZ>KQ6RdI#f?+4'ӣJI9ge:hDtZ/_yE`e:hDtZ/_yE`e:hDtZ/_y0vlT6)qku y@ DtֆYja'tLU*52E5QՐzzRs4+QVv/(ؿܠ7jmbr+SX*mPNBP'L2hh'L2hh'L2hh'L2hh'L2hh'L2hh')-T2o'nGHU ?5FHWIYFa]X:SKK.9ci3f;4,qV]^@ȿ*??^5{{kO{tby~4x%YcӢWS8kS9q8,n*TVupEWQEQEQEQEQEQEQEQEQEQEQEQEQE5cFw8U{ (y[JAj3]<_A5-p&i\ƹ10%v߹ӄօe)- 4ikOh)7Se%ar:k'ZBn_;a"vŌ,5sA-yf !?V=EzMS`TLu,j7Ɲl"lMzaV;H#?((y'Q\9[}>9nt<4ښU =M.G݀juҭNyAمQZV.y{ƁASK2qԆ#?p<ƌf-,oy8.6B*!Z@h)VWg8JҝM?|}+bQEQEQEQEQEQEQEQEQEQEQEQEQEQEV&M3V̹R!x#ڤ KNҭp_';o͉4>RCtg%CjgSl?|ͣn:qEE9f0F#R$$瓑ڢ1-|)Yj1_q1)'EQEQEQEQEQEQEQESS u5>QEQEQEQEQEQEQEQEQEQEQENZu2G(E締C gv S?IEPEPEP/^SK@k]dY0S^8&@:5᫨i@:sڽ̚.J|x6ښIa,nr`Gv~?t5- :J8q^$]fCIֺHJ8gc.^(BO_.Q# w< <\SyIZA`4BOh^ 5su%̢Csë, H 㠯3NqIuGԠJRc+O ?a7*X(0+џ z z?;Ehs&=UB\LJh,8ZßKrO )|0ήj]x#T :+gW ZǚRni^:((((((((((((((G[p MWڗm_EVt%31Orbjk@ɯC`(DumpͿw˜kK]RKDd|SӢi]-Ԑ]32˱RQ $zvMW&;UvI, $KEqΪ-U˥k-J_w W?(^ ֳ=knDc,GY&Ð2`{Wd-r)#2vr8(((((():pP((` AޚM[QTOQe=O«ϳ<{h__'*7\&0%@$*J ( ( ( ( (>}iN4=} :D޸*?P4 ԞsQ?z&(((/ oZ Ni#K䃻%q,1N%]}dT2 kYڧ=Ѻ|[E}b #`V5lb >FcL}[U -Q@WXCl7cfSRB"*o | rMl) (q<4(p|PsXwdQE 5=6kvykV[1ס`*⟼DԚ]R e]22͈d9cT(.~By1_rN(0Pa) (+?Fl~SC墓xvO<4w)+mPsSSPv@Q@5jHljOߕ$w@\4JH bdE @Q@Ip6>϶ߕ>ub>q<'#oʬMo6U):~P((((((Zņ+9$1)(bqoG'/1e?ξ󡢹NuEs>6s<SmP,FSyQo+9!{{i.;2@>V旚nr`fX""ENҴK縵7mv\ 0ȋ)b~ck8[}N}6H40Y:`;ej:^u _J]>iJ<`#'O#>ޗErVVB88|88(((((((j}@((kUbڌ~U3JoBp0|%:(0zjGZ:+zT έ ?2h訢@(((((hy07f}{gٴ >J>Jd~T{Q{Q{>Loʏ&O7@J>Jd~T,*r)^#;5b ({M-"Z(毭>bȬp4((]sOV6G}ьM&Bm-YEs_xkh!Ibpʲ){1QEIAEu܀2I@րTr8CW:\:5NZJ[fQRPQEQEQEQEQEQEQEQEQESOJu4QEQEQEQEQEQEWxi$UڻUh?̚Okɗߧ74 3*ْKO:,?zrھ<4ث]gQEӅQ@Q@Q@Q@Q@S9o*Xvi5%T"ݼLUK *rEPEPEPEPEPEPEPMO)EPEP\Ω!) 髛գdA+ b><ɹ 294oϭ-67UH&2h((((((7֝My)2=EEq5\J( ( ({M-"ZCOҲnn~m$-s޵re+o;[qbEJi2];Ve͓e =k_53Er [SZ HktmuԹES1 wz7P 7y7b4eY^ ZG,F<685pi/Kk Ojz:xՊ2_OOzi@((((((T⋉]HIfwWX7;mD.{}*''`3q\um}y*83v\woWS(Š((((ݍ66M$; q=we2ր2<5 )ӲF eqO rxYVlyKU] AIxBHSboVN6KbY* o )-(FGǯKxcӮ-aWef8$`]xj}* LXsV/tx/ m @v@N}qҀ<0YKXʮW dc21^X?ta;wcO5@Q@Q@Q@Q@Q@Q@Q@5>SS uQ@Q@2Xcq"E Y]b}SGDcPSj)lQE!Q@Q@Q@Q@Q@ yhP@=E&W8 ;WQ«yJ1p 4QEQEQE"ZE4dXϧNX|;z㊪^uf2Dc9ZӱNieϷSBf9Gҝ(^y7m:HY\ C&3igt&?TT)k|ͭ *to(Ij2 t{ i"U4[* [:[i6@}2*6MϽڀ>w1InFNEG sI=Lžt),zMtu&Rk6h((((&0;YP~mjIx+;[:Opt#)P쭦],QEQEQEQEQEQEQEQEQEQEQENZu5?ZoK!L`u'%S*/=9f% s@QEQEQEiiQKXzTPafPEPHr+},QZ(([u#M[EVsqV((((+[L7݌~&i+e%ykksϋ+Q'! Y+SN{uC\+v<8roqg+<JfuS_Jmht Mu)c@8E+r7 o 旡CuJw̩:~.>(xwZ|?>p^Ii!mE?՟w:>cx~Qwq5YxH--m$eOp} lZ_Z_%xFҬW}i ߆u &rmV<'{IEŧcH u|P(>i@(((((((((((((Oj/$1%̢9Ƕq<^BE _׌t:etk!4#X[,I "EI#"1mLeæJ]7#{S]GLm/6dA=b w.լ=YĦ&Lr1bpɪ&֑o$~X98Y:YA9 ̱@",NwI_ LQ@l͛Y¹ޙ9 QEQEQEQEQEQEQEQEQESS u5>QEQEQEQEQEQEQEQEQEQEQENZl,Xݞ}(d~9*?~lѿ*>~Tg?)V߶Gߕlѿ*EWd~Q S#e\}QE"ZE4QEQEQEQEQEQEQEQEQEQEQEW|FPTz5RԴMVyaH+# E'RGkʐ!o3\7=t=&{csJCk.Q(y骿j|[4TWj><4(M&`7?ZѢ B`SEQE"ZE4QEQEQEQEQEQEQEQEQEQEQEQEcqTe*Ҩ D΀*bWU8:-U7)$7OMDU)QELͪis@o~{ ";l#֯ayo=5 2ڹ?mooxzv̺lS== 'egofg2n=5wPHn Ӷ=x ;kV&9E>5q5-Nҵqk3Y/[?oJ:)SS uQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ yhP36;l| ۏFx9EPEPEP/ .[hSrޫF[hS~oQG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(S~QG(m5-p}ɿ|P=3Ě{w;sr r cxö$ҥI#1̍؃\&|B-r~OX̓ʑ 6k6;Cn}Q@Q@Q@Q@Q@5>SS uQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ yi9hz΀E7-?:2ރQMzΌSrރ-?:uܷz΀E7-?:2ރ?hn~t`q:EZt`q:#V)ߛ-?:uܷz΀E7-?:2ރQMzΌSrރ-?:uܷz΀E7-?:2ރQMzΌSrރ-?:uܷz΀E7-?:2ރQMzΌSrރ-?:uܷz΀E7-?:2ރQMzΌSrރ-?:u7Ҍ@'-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@p4^LңEAwTPQO\ΈV'.@RJW0 `[$r@98*ݭʁA87%kr֢K@r|6+_-.{jM Z^K6 J g з _MjYioek 8cP m$G7#  / WR,Qzd{i,]v]*W}5ǝZd,_B{MXAiVGm8R00r0: @Y53jvWV^;kϴC ;JU 1.yF3[jGa7Lr4eFFڹplmLh`LW4iMɓHgl?77@Obm [i%N,ٔ 23o ^9¥| Ng_ з _MiXY XȊ(?AX(((((j}@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((j}@(((((((((((((((((((((((((((((((((((((((((((((((((((sž'R ;,^aÎч5<5PKH l[\ym)T20 Vo\& 7B"GCDU@ fy :Jʬ<P)%uO,qd@'|6{8BR"PrIVAo:gZoK|ϙZdI; g,8$=h/ƚh2G :U+ jF+h%Uo)bṠej6V`l%0O %-r%cP:Wvks~8Q<z.zڎkt!Cs^r$ťԐl7 z/SFPʺ88r8 ( ( ( ( ( j}N( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (FY(n(^ٝ2}9f[0Qq@TVnגR#-FxBA#x_~?7iRY%`'M((((((((((((((((((((((((((((((((((((j}@((((((((((((((((((((((*PHUOPR@E-B;Z(uߥ +x`CqbJ(((((((((((((((((((((((((DWTgPA<:M5o(enWUӦ[DF/ kls}彅\]JB] >)ɿLPxg#ރ͂"mݲq O߯O?m2ve i_ku-YұBF>$ҫ]fӤ4R})!coßj*( ( ( ( ( ( ( ( ( j}N( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ($J1n)Q@l`- Z2Cc8r*&QQ$qs ـJگQ@BҌL,Je j>{heecׯ(((((((((pS:((((((((((((((((((((w곙eq9TY>c}:?3PWRc>Nc~ty=V|s$=A^:/oΏ1ߝKgi5[~w$֭ˤ0 mvlcr zR2$џ?ZQ4q:҇->j·v_iYǽce*zIJF?[xR`U<2ۢ~R1sCR :pF)lҨE1ߝc}:>DƱfVb{ǭTJˇndf*=1ҚM'NkF^c}:<~utG 7#JҪŧIZ=Tq}(U`Nh?Z鐭v]Pڸ2ؙEswe[[O '#3[zo| UbY&0+=0((((((((((((((((((((ݻoxԹ9Gua[ F>ab W[bBrsS$:{Q9MaR Vc kt$>M7+#̾T3-nH@Gc\)+M(`G 81Ȥwif=I94C tUXIb m^MMJ֡MidG2g)K1PJf}I#;:HNHnA[nlv;s6O٦Oix{F=7cЅ{exA>$p3u_ďeB(((((((((((((((((((((w: ljtCi4nܐ+j 6 +T EbL&U,хϙ됴.anۃD dڽ&൛(]%+ D w 2Cgz[Gu;Io-WʋkNI7eNkҨ(((((((((pS:(((((((((((((((((((([X=xI3H/A⳿ZA9?c0g9'wkChkCkK"~_ZA9??ZA9?K 3kChkCk,W 8?_/8_VNO?ƏVNO?ƻ(?>_ZA9??ZA9?K 3kChkCk,W 8oBms5'u`/]%W-Zӫ.jN,QEaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPMO)EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPLFP>lP_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@ ?:7KF&޿.zx~t`zPo_΍zQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@5>SS uQ@wZŕ1Yk4%%ܮSSV BLk68PYUc_k+%vxJlɒpN8s[ RI3IFeFn( ^?㷷`#dl zG/ALz&]Yj2Z[a۵$URʄ=qB[YcIx8a"!^H#mg4H*`sڀ-iޣ~m[xN*(7;GҙOir7m HVS$ [@s֭KQbO@ 򳽗1 h7VEOn39=~U@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@RmgKF*ڕ`pAr?Z\:پz9ukz+Y5jʪW:'v_GޓA;/OwΑ?74¹?O&=cϊUޓA;/OoI'; H??\}}G;&mtH 7s8U"qPobᙇNX~ ,*K=xѰ\`ȥcN@mf?Cw{ \׿[,zNtoO/Q=ZWzġԜ-D E REPV[QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEU-GS"G(f~+vmu՘TA=+JPS% endstream endobj 26 0 obj <> /Font <> /XObject <>>> /Type /Page>> endobj 27 0 obj <> stream xS]K1|Y0|w(A D *}UۻHw7Lvfgs{Ai]$<^d#^% hS0ӾK{[ta#fHbz$|,o! N\#[`FK]F)(~0dA%lԉDY^1IQuf*w$VWom1+mPԡƕTNLi2 d.yQ!!>ı>sC UmvARAZPkk5؃&-NKHםݮC"/- endstream endobj 30 0 obj [6 0 R /XYZ 90 752.9 0] endobj 31 0 obj [6 0 R /XYZ 111 689.6 0] endobj 32 0 obj [22 0 R /XYZ 111 769.9 0] endobj 33 0 obj [26 0 R /XYZ 111 769.9 0] endobj 8 0 obj <> endobj 9 0 obj <> stream x]ˊ1E -`=KCEdtCnڞ>ai[U;~_sy|c/~~[?绐r\/Yltx*!?K:rz_f'˷|MΉji}\YuKiTLZs*#*i&Xi{݁A}j%U;6@ʁ (iPpbɷ-C \P*ۨgzEP57< DYTA@JYؙŮ}!UD\&m%ȇJ< $2,f 1 b:diF\,(xPKPLd35;A4XK8gDSҊ.i_7pa^b:o%oNy \O^|4Ep83!.ir\<ȁwxqAKD+\(:% GmJIma? ҹp]e j,H~KF|ۺ~sx"J(C*| endstream endobj 34 0 obj <> endobj 10 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <> stream x dyw]kUoz[{t,fg̐3$9$G$.$JbډedQȖaD#D8k$J,3,,KؘS~u)˝Uswνs>r7g>Y~r(}KԋqO|C/-}s/?|9Oʇ_)'˿S?G?=_\:SqǏ>BM 3{O\zҽڇ???믿~|Wh~_ SOXwyZMpaV17v+to;(By`n>iJv^]uKӶ?es|Oq4\hBpPO??Wz8_}ޫ?J17[O??P믇yG|lz]֙M<Oq,C]珆BW:ӵq)}O;rG|<&x#5ѽ-R%lw:ZSΧX2bRx0 |08xoI{!TyZmմRsi<{G>MJ;>?O}n(|8l9u2@;]VSH~)$݋y;>|jw)I|:ͥ.n`&]Ns?s4$Mg3KsX~}!b?)|Ns豜贖>TAϥ^]OO'޷`=>OR>>b=rvز?}W~FY:-N(s?QFv(g|z%/$=6& ꘨vP_LS^sukl(3 ?qk~+ E{HRAgOŹ=z!tw+D6 s2m؏y4I}wF 6R?eOױ4.#&d_^ZGƭ42΄Njs4Mz9Oi? iޫi>9N'}Tm+iLyhqhKsi4|4=~i}wj{:;gb4O6&|%*)|>wʄCT:t-=~:~&{@2>T3YU6Hm~!H{HߟhMcRQZO,ﭦi to7͛:*NckIm3]Ouf+?-ʴ.})NkvQS8Wo1,ݵAX9'^{Q=\vAgw:]?;W'tOQqTGTc'ܷ *iԿ{>կӼ+}Zb^Ys6Y]+zjěl*Y0FOJ_)ۨ> 7]~DMcZ'OHϴ 46Zl}%Zkl!hlʂG{ɡ֘dÃN,PF/>b/囈eZ[-h:ɚmkp\_>>ɵ(Cg:NjԷLN=ׁ NY>bt+*Obt$S~*]}MQJ}n?AG]~ѥ>O?¥p,[YZ~+P_ŇS6=.!(5;[vBMP+jh^7آ_bI ~xpj홟hV,2F/=ogU6N˒x1m(" ߲KYȞgзј=NȾS2"yfcX췅1%6hg\!W^E㩞W;3. Zu =ɀ:K.ROqg]?wi:dzY$}/:3{a%L/|C餛(]O42۔+[3Nd$-elg1/۾83~{'6ؘ|(3=|eS:^G23dF2!yR?̽E #OvOBusT۶Cʎ5/U׶zYhkJI'签eo'p_"JcD=ors/e$?Wu']KfWL>G>e;9kɵhZ@uk%O,_\kcg{#X4p=?TVn[ל&~&i0UUVOeoѵre?;2Aw…!O׽4AJW@xi%@6pPhG4W[z G9ƕ,qy*ŃcszC!\V8b }JN5_S?7=1ZMoىA׺Ƃ1ߤtIg:JP_+XxQ}^ߢU o@Fۨ3@5ӿq?AWt\EU)_3}G?*W?ZSӼeWM.Z8.fGd*1@OXpՌ:h>;fɉ^cP`br&z+ݓY]t#1EGst }F5e˘{SRp۱ncy* K: /;i-}_uԓ\fA:YxՏ\4Ze5_̦byW .E{Խԥ*>LVGK?5p:CSYuym?>6]OmM|]ZVczޖM'@U~KyP>5c O˜+>)C,9S҅<}}ؼG5[~bZʼn_">2.d7)#%׊˶g0>z~,=&kMҙ_߭cK4Q㊞ҋʅ(Ϩ>毕U.Ug|ў(쾶r]xr7r>;si4r$e{~309 1&e<* vO*9 ߉(}3ߜSKֹ$Wxjg SK} 6j {'į=S$=SYbNchӴ!ѵMk| pOr+fφTki=W_:ʁ.#/p/bAޣp ]n +VN 6Qfj^CI5up2.7ʰ֙(hU1TE;ɥd#Cҧ8}%K_+3{<` x&!g Gŗ:hY; g8;~1x^g~DgӇ<傺D ?|'._-Fę=+y֜J\HOÓh~QŸPyg'_r!~^9q-ac+s1o{hFԧK<[\lB8ag } (>Zxy5ɬ;uENg(群 Ic\Mo|rI.|y(/?M>pgo7Џ|Z%tF\3^>Ƴ mZf |1D\Q?O(w v㨟r< p_NOOb?>*~~mg%^QNqP>lh iۊ{~0ܢi18~>"K^ cR[Ƙ̛i[s<>w1bTk?|#|LH3#z$+zNKE?=۫rɉ5?OK )ϽAɎb 9-?/Mٮ1%0&N`_m'ZOc>yW8::*ɼlv7IX[/zJ/]dg6kK_'alJ|Qpĸ2kP`^@{q~=k1x깊> wʳd{,?)Ӷ?s'{%³hgN~#hV[3ݣs/iq)Z7+>b% 8 9];}Gf]u37OYLk7\S1UW-Ph{2o*"Y"dK@KKP\}b~kx_xGC|.Z~C]̧Hsw\V:zN}ч$]A=-j\|z'qUv/GQot 6oa_jycj9t UU!X,m`,]2OhmGk+C̓(_Mg^<.+b3{ZbHKmw s/9fL:UK}|ZH?8<}3sÓ&|;?tVv$p%~^..,٢ /|mTO_2ogŗ!Y'Z3Y}!cN[c xP2!}?~?K1?CO%^O.ȧ~TѺ܀bFҡ9&jL-PN1ŧe›KD3)̅QW0rkS_ Q_sHX},G)Z-Q Ѻ"]8+e~ WZ2'ڤiyA(Wb2 q5C8>ss֯5(Y`^UKy(Qȗq1=|oы5X<)=FyL='q-}SMnZclU/*gM_kݐpRzW~xyOiґ~o")w-K2gR]Ӥ;R|Zy0Z8]iiOQi'Q|EOE.e1*Q.>dP}l @~ Jݣ̱0/c&Z7ϧHԆPLtHc-X1 Yٕ ox|q9oQנ~,-oַ/L#cqm#>k2`ߋ| Ж/uZ?w,޸G!}A۱a} {⍶#7#ϛs?2`ԩqtn}ZZFN v~̌O$cg7⏸ۢ9.Z):YL/O_֬A1sj-r5hoͤhE{kms^Sr?Oytr|x>{9:U .BDsj.Rg1Gdz ‹7/,_)zhx^N>p1ƽ+hzq2OMZWC7ߴп)ߥ.qзE]8 cH4`9,cE/q)5Hgmh+9T}?KSt6ԗ-c}Pcw]i!õ0c^ h\$'z4~w~s:sg>3K}lEw YTO_+(](&{Asy8jYk 5'6u~@muBr16Hhϣ POUK?G=WmkW F½έ]ՙ=&O`σo2wor?TLo:#&*ܫ:~%oKg[e oEWQ&yj޹IB{wS0Ca>In3͔my>{Ƃ֏ꚲx侫p 1E xZaX8ƚ%:3,8‘=o(Av cX0Ccʙ`^{:Ea꩎H26Ds Ol})}la k>i|X>۵1qD\1פ"K}|qY,dl'|1bLoȼ33Fe uiz3ju];"Ԩ|E c{RJ3F ϙ̻hҧ;D }.g^PνQ})G66e8g}W7i'J;'Y_D Tޕ1B_IZy>q%[k>OlJ@V[gǘԻ:7#ғ^gs|?kSڔQ3!glZq-Sf} Gq/W2)J/ӆ<#9 ?9 ;b?'0KGR9{a|,^e"9ݣNMtV?їc=~R.O1k͗5' a]?0/5O峙'ʿq/w>s*:3/&^P{>k-gcq,%%kZkmu 63gllKCG[L_@ߕއ5w͘ćޗhmmЦ &5I&hI BڒƘF[gP]950Qr䬌Rޝ"(==`xoTwԭjS,g0]?ϓc O‘$8`B:9^QAvI:.q4d6Vm~^G}S77 Է_<{w?'xV鰾EJqӟalCSc?-4f~Phu~8 3'xus/ʲŁNלq}|včteʛލ{6%/XaΒqKz ͳ̡p|ZFvK!tpm.e{Ƌ؞:E>#< c 9P~;~fo $17G W&y?Io2VMj+ByUkc_%Y"g">}#g#X:;;jV"Qי =3߼֙H>c=Nb,Գ|EtuEيZ|%ʘרYF=F{(my:氥纨ü$7Cl^~ez|@~1m5uGCQ;<.\1B 1ӻ"~ڬMSKF~B?]o쯛q'/(O}7}+G2clFi?9K?XvV{i/z u7`o3ҁy\W:yEOOks՜coƭsLV[6YeϕQM=sgg)Ve²Cf?T&\izv^DS ?h^X^>>qC>sF6WoH8-Ӹ1[S̥<[=+1ڊvVoX?yf~:aⷮ=Y?j=UqV,,L]|. E1+Z0V)G;daw9k똶Mw5%f x\Sw3C/Bqeo)pϹ(7`' XihŻꏿ%B/s+K&, =|g̝2+}|Q>%ՂcNRǪ_j7ײW+\l&_,(T@z_xl;g*܁}$)XV|ͳA>x{1GS=ڃ )6?6ygC^tz}~JKߙֽ%'z=kɐdR~xT{?O3ڇ (ҏ5ƖӜg[Z~O#'^{X[.YvIײ7ƓO~(/\{+7G9ޤ/x?&yyoG_YsSv(E{uj3.;+m|"g3#La3lnm,pʞd=wtqm[-[D;7'6M#i{&OQX\s\SW Pg#?TGP/,~Yfmӎi?gqăt cʾw{H?)ZPZF*~MS"'#nŏg>bS~'s]a#k#Y [ՇdC)#QB+GҮЯjaʤ_ ?/Shi똃t^ܫQ?|qM AΨߗЏpNF\gn [Ck=.r!j?a,K9}cӏx쓶> {.mYTp<>ܴc1^8ch3y3-(hKg=zr?JE@?6xŒK27嵟c۹wO'OeV[ExPY}:y'ߋz0Ѷq~ c9?oesrɎi.>KiU~U3?>[Eq핟߸x(֒%%M2D{.:j:gH?'Cg<^_U9agL{{ʏsI8z[W1I;SoHFOg" D3M]sY,s$/G*%Y,_#@Zơn픯Bѵέc<(OqU:[§. DbW_k _XƸs| Pܴ:$ ;?fR?*k?͟EqOq=3*h?:#@4xoơXA/ɘ:>ū›A}(>73QCHtP_1]kN wgjW/QI5Ppg|b%ʑ,Z4'g zFMe[zyU?hL;%=$ڷ h/b_7+"`#^j<}68IrEҌN^_GmN)(ͳyVc4sfʅJ>tD ֮{8ѩD{M;2/m'^f,)Zu7ciḱCe ¿x_B~uiOiT:q_KfckSOs,sX]!/ru&U1lІ[@:tɮHpIqgzSEG=ɦ ʫ+f'3Z~Z6Q9O߽ ;36gGtl8添 _ɇc׸ cOh-Wp*#X{XA7}ʶ M&K ־p״su͑1œ"I~Kc < v67gZSeƒk쮹HԖ{@Z`ݧ eN5>6iP>wkg<#^o 1n/>ߍ(w+0q=x?aWZe1er'>^Y.:+?,|pƩs~cv]s.E9lq]pG{rߚbYQH_Ų?{D6F}0?b ۩]vt9Aᧃ2 ɋԙ§rm|pugYwoGG3W,.Ҝ)/D_娫t'S{Om?kO9P8B&[!Qo$NggPex,_*~e{M o$Z54F> {:3.S,!ؘy#sg?2cuM~l?ٖWY c5i.jsMX\m$;N_{UjS~ 0g](ѧuȴ%n8o s7w\b%{ j.>t\ͩwTGuyy@6y:8>|?Ir Mf}n=|i{5hu[|cXM?8~Bo\gUƜ]nO嚳;˧*1%'pԯ\*EneM(}}SGL(Zz̎%:w1}p?=WAYێߗx6̫kϖ qeu\P&W(㼘hr旤le<*rmh9duX@4Y䳰\~c]sm+L)1_ϼmòzA(Yf^@㑾J.ii'/e^s*P2eֿ}?gwp͜߷M,ZyC_w+16RyO$.s/L{wkuN봹,uqߓ?9o#ү dJ-W=: ^>(C^zq}$cO>msY[16ζ`/'1;S=ڜږ ڗ/2ߙ+fkZܖ0Js-7>~xQz>#ʙCyTl0^_>Q1VQͱc-K;N]%^p ӇtYЎsսԹX܏WWy_k h_)/Z2L֥Z;D~-:4ݱt!-:mAƩj<NW#m=K*o'?j#ߤoHhmb;2ā'By~[8LPAy P6Kp\Ic#o-(Soϳݗ~?VN03&}; s9C?m^"{F6}>F{OIK17}x3$_aTzrQ rǵq}=m26svvM,o37evt*ӖuJwm\$o5>Kd5pceoMfQF`e{5]cΔv]<1hi'OJGsD6s~23߭~ҵϢ7_l^p 3vl~Yd˭d'3-;3c0gjgK^e_0oi)3=6W:gZdSؽnxϯ?]1Y?QEg8cEħ\㢼 gdsUYf @3[:Ma]8:X_4Gbl!*|}I͗\.(s8>smܟc6OWsy9ε2#&imh'#?y65kZl ѾrL-c`yA"];zE] Gi6-3~NRTw~{+m-Jca^WsMT~c7ymzM\o͵~Zb=Q?j>j#z;W{lyoW1-~gkֳryu%r+u756/}1w6q~8,*S}EŕֺϕwaΜq kgΆg *r*߭yoqI_o*?/7+p+^`t޸g}zC9/Ou(ͽzʨx~'^5?7ʃ)vNPR—x2Ɣ^U޵89#͡I_-n?1EnyCZ:R$qosacuᙱ:4NTxK2y~[eyXSG'ܿ^Z'|h?p^^ x޻ȞIDfy?*?Å/QWy9k'y9 ɰ>3hԇtQ;y'${䉿8Fu*7}eMj-<&1& FBmA^OY-alj[y&g޶Ϧ o4GJ@х2`,J|=K>5Po0R"_YϖKwŧAnY~{9wz+O4ħ鏊g/hc4}([n}aHHeeǧGQ*߶[$M>9GܷAǩc[2Z'5eYzLs+l:LlQNYLIK-" Fm˨xN6FK/2%lHX^sMgwZV>!q$Z9 ^bXQx;@=F.L{Kfg2$=%ѧgʕ2Ԟg]{LmA!g"r&Z.>*|vqG4>oUŻ}K/Eko[3{C8O!mԥR:ܷX}-7:6+>ډ׎ s0M)K` }ko(n;^v [o1^:90rkwY}п$䫴MQ댑D6}~-%#%C1X<߷gݑ~T^B4=Aɱ5I/ll.hq ~?[Nf'~%.~#=D`Nxpkf6}vX˘7ky3~XmsrX,?J+שwڽOO?~!O8}SW;Xg!F۠,lܶɗK=, G%Yszw蜧ʿO6MsAc*!<0^#XOcoriSt/\ ]/x8|CAx-c/C|'gG4|bxG'\''ҼϧhO{w;;?i8%Ks#]_Nx{ӽˉֺ@Y\Kxk|DMs՞C~'zG<Ƌyk׳#O'3-݋kzW/ﺄ?iλ_g]r姞|yϽx쵫wv?꫻[} |e}^+N򫻏˯Ƈ_~q50_?|ݡ*SLyzcM{P>TXs/>kWu~_GZ7f>ӜaέK簗C+Wޱ~W^ +8ERAXoEo]=ql+8vXGBO=0c/Z;Dݚz_hh~1xhobvG ~Aп9Tw7uC 4@ 4oA|hsaNlh0hhhl`hh?mX|c 4@ 4@ |+o5 thhhhoظh[ z 42l?Ña | _ |jNlh~?M8aooUؿ?~[ μ{_{8?4 @ i-Whhhhh 4@ |s¥ihho~hhhJ 4@ 4@ pnhho|c 4@ߚpo> 4@ 4@ 4@ 4@ 4@ 4A 4p 4@ 4@ 4@ 4@_p 4@ 4@ 4@4@ 4@ 4@ 4@ 4@ 4@ 4ohhhhh<hhfk@ 4@ 4@ 4@ 4@ 4@ 4p 4@ 4^7u+s?ع~]LLơd# w[.nQ|SΕ~C/]U?:gRJzۛL'~l#dlա_QwCd\W{{ϭʲ(rN.|Vy] k9[ɕzoJ襺e[vlw7-pK\v^mknϝwQݲ\8F\ 3LbW>ןXpV|p,xWz8p]^< `+ռ|P*_y|nur`@v/nZ3hm +ˀa3EXܤYWmC5&=G>oUsEY\eXHu7 ܻYqWO1>Hr>g fv[wsou\v}i#u7FK?pkn/wD.ޏ3;!'JN} Y92zu}+mpUV+{jHY}+z6>zQ;ݿ*˖~nׇw.n3+j_?9[E6;76GnW ['dyR$N AYXݏ,Tnzխ·{:l>ߙ\opb = ([7Qemk#V`<-#S/wGvͼ(Lܿ^s݃ivW۹`[_Js uNGj_(K(dE]{CWb<ν,F]g}`ŵPei+X_Nty2k[;ϷRu%ow]ZfgA}_Wn)`;?_λQ#Zy;vB{ '~#}?ϸwͫ{\;.Ή g_>DVqc TpzO.g:tywݽv$ݪry ,e}hq>'S\^d]V=Lֽ6= jëO=]_&M@IyQ\O+[KB2l7W_Oײ#꒿Ev{A6Vlw08}#r;Ϣ_*_Z9o<4G{o\8-Z4Ku]wBO={x5cn=ՠD{Y}:tvyNp"K=V@^]Vg]f—-eLPvԫeSNH<{anubQUuK]|f,؍kW3\Y~/hAW>BD9 b"8;+? !jTgR1m7jߝDTD w4 b4 ^^vߓݹխAV~|<bd~c=p][g eU\Nz֨$i>V,%Ɡc|`|4vd-;]YC׫ _ c- oߖ?Ͼ<ڪ)ԇi7Xmk7k0?+}q۝*D6=VW']gBpnfLU6n[kAN{GGuf}ͣ[[". -v@Cy]p[۝rw|>saw2ΣN'JR@JmȨۆ%szAsr?ҭwLp7 n}p_\Ջv0Wދ?凾zn.>^k[qAw#'9{252ZO%`>pT̨nzI}W7+?2XQYHۂ"^,k{۷K k9Oxݰy5K` ` 0؂S_]w۽cݥr=w~g%mxh^f=US I,:╩[|z 鯻<\ɪn]Aa;QsGB;_U_m!V0Sqg]?N78Z!o/V=>Ty|oG=M㑫~dE h*47;gYDP9p/ _h< N+xy?^=_˿c{l~~m ̾;>?ܜ|,On]hq΋@X XZClZw.g+[\F]~v҅0S%Xᔾcfpy|hG]zk֧ڷ^7ql&=0_pW7z9_"ޒz7ԉـu ٮ{l#߽s)r;루e`݉@S1݇NxX8eN={`$ᚦ'o'el?JgΏ ˗k@0HU=]t7Uԗ;g3>4(Z˛BhTlԑ[ V7.|窠{[p ٳ~.4nWAV|?wwv뮏:E ;+u:N: *-8ׇ|o+_9`тL~q/Do'Ov^=r;N>䗨Ҕ;8j O|?#bcVnVf)PqrCvG+.;xw CwK_nA ?++a}+A}ꗃֺV[Y ʤ2Ø+ڭ;ݰVd`[6PW2mM=A Xn0Q_{oXC}Or9V^[i?_=~WΔe,3 F`C;w*Z[_(&/F3 :=v{Zp9VAU!z[ue, vvB[^v'Bs^ʵij)[S[pO;pG]gy=V:Í)0ti:r&) TWr)Σ[R 0F('P(#b)ꔏ) 0l{rtȭ'[c&^sl jZ\9+Ø?Rh KnuP/ZZ6z2;ho-]ϑ{7Ō{S(IQ= 3Lteʓ+4nb?֩c2^; -pu Nvί껋NpVubr̬vjOyKSo>TAí`{*WlyK>D~ʪ̷wzz=<\O~&hg:p[kqFĬ]h/x7<Ekxcr 3bgB;mzz];,Da~+_.S_Ut jJŒ6Z2gG_vKoWq҉&4N?heunsu%bW:NU'3}V{pml {$ڞ0Fxᷦ;"A+~}^=rO;>^>3duO-푘>Lse !/XMcYg ax)jc@VrLSBqˇAwٟuYS_NVsnP'ams)z(0^ guC/=}2^u,,gAF8J891Y"ɇfp Hoo:Q_3WtܴE0D#dzVAVߺ+,/F!XYvSzgݧwWwƵ`yTw=Ky3<ۋWnz㮧N>/on1vzc>8n]Ix°QW{'b[O{?ZӍSیֺE!6u2sdt~= yw_ CK˾ AL/KuVϵ:Lӹ5"V/R !\\{  *fmϻݕVQ寷C˷^;Ȇ4K qݣ+;L~}rk{˃5F<~9}W.ww\rܥHI$%(H[)R$YrTWq|+Z#uP4c )W -Z#E?: i zCC}.rggwg7yi]ؿk]*x/JY^,O/>, (F`8J&ŅcYEZ\ftl*VMHH4B la岦MhOEĠYBO>N]&)@)/cQ \?ikDT]MX#>oW;G !rVhS<v -H1Yy(g~-yzae1B" bG6…>b}9 6i$H>@ų'mu'P2*o0kyTN/4;ުS|yx+k+G.^.ID'z.lq6 gg@9֣w365,Uk KsPo5M@œZ)/CvZhr!}*j K 8R<~ >lG]:ƈE\JeqXXǔZ]eRRH[V1,e?rlq0fe䘇? R+=i5J's |IX.jcc+'S47v>9l)LŧA31A:XLq^4Zr]?ò)(#cǁVc דv}jl|hS0)_jq^̳u tQn{?\Q .i@: T1@KMjqn+gRc&__`6Kgf⓭b~lOON'au)+VQz!,l"~3z?:ᣑZ.^`dalD6QOCWw琔5{zrב0j"_GWۑ1=pHDc1Vfa|Ҫ=HnoCwERZ gV2{!wjR+ Y 䞣XHZ lFQ |ldSX9v)a baԘD63OL4ZsqٜJU$n&[UijM{;t.<w <ͫ_={Uw[ڽv;w,7R)a '/^ri-#zbX<:0}?Agi D]#[֚%ožēt*/6X05&V9ť~^By.IE\m!pM#˪KR 1yn'|2r j ?Oühby@/)FYf\`aJ>RMX1p,^7 XM_; I˜ +<)\}ojҲޘ#!trI=ORI1N-iHZM:7~#`u|9g? HyeC,%Xxa[u&yo+\vwVMU# z"94 Ԑ0\~Yh9ܙX*;?PVI)m$hV VRDW$W'\oLrPi:"}fCaGn-)H6-,6 j+ޕKB]sIEB4Hxbҝ{g!!U)dw#8\miNt:qdPL1)ִ0<*{ cƳWHYvLJGuX8-h8hzorA~t#Z>Q!# tnF U%*\+4@G>k͛v8l2SGozؼ^ক&K Ì%, }kfp/p F<YXt|+ofs)ͯ'xg矯Nn{q/ 8[i 7K*x݊x'%_XE'$ujg}&Enxs9h#B h~yu/ߜ?7n H4eΨ_z62G`&H@L=V3fM:ԭGp4[E–K09we6b(-z9lfbIZ6I7g tR>wSoUW1 ]2n{CZ3&0.c6XGlcU>Gx\s6-_L+Ͱx&XRH&(GzSf?׵U2OK-fŭϽrgwCG>9kF9PϡߏS|`P+` >NЕ:,x%sLJ:U&,EܭrNT A*"z'ivr ƙXfqSf<Mb6Fv EXtڹ7P*k-E;}2 }1 S|_/Uԃfj~2ٕ6!9oVW㘙-J^EV.|1mfTka}>7᷌{o4}K3)Xn$dLo/~H?]d`L92v[šD/9xE|eC+6w4ډR>Mm)08&ƫiilz 9{D}6"55ߙ2 FnEfaL\a-'u1|1Ava!nxBsd ֌@EGa0R5wYEř퐷])ѾBi- bEXJV &q-.빀<BZOXx qъ.vC1442PB"NLV 1 m#Rn%,G{1;1´/D5Ïoq$oGBŽl,*_GJ37}Yϼڛe8LG(jhUQڌڈgQ2\MC 2A/t8Q{#r^H1d-c MzN!lN 6rlX|i dD*@5Ĩ Z|$,Y ".7!'[~ݠZcti`FWwIBb{u, ËZ9gYD=̝ $O*ZF|g}i$Ngɇ3 $\~F]bfT};NYzgp{ÂMfȺ:!rHO@p'9{ &Ab[Dv GW`7}8 1j;YEx$^~,y^d1mFv<,ӛqvDwQ^1L|& AA8jfY_4|uƖkڙ1oq=TTqYϿbuykR`u|1'T$^=ja>J2:]796&ߔHa* "xȲ)v#2539ܟa`:O~n4ԃEQ\ib-qH#\rac+Qc+5'u)}ڙ-ie☫dg-ja 9Yh~W2xnˊ1Sw,JޔHy}A/Lm,*V __oïDeמy W[P2Z![N4BR/pۜR#(FRXɊ˷-״}`|$'ǡTͻ\«s+7[hPB*`xA0h'avTmno$Fxt"༃H&q5[ʱߟ3WoP~GdViDxN"' =P)eQ|}{F6%)%!K`wHk~x+*c `Y#讘Qln\im|^f'߼qz.jO*97txx`ˋ 8P7~>R `5~&[V(XBIwGع$(_&`Y AUǺ}8fˎv0ċmrmўI=\, A6"b I*8b4VyHxGjwK.-irI"Xα؋;|ޅu&\yo%ՋW=v|sҍޘﭼ啯^{oBŷwK~+Oo|mcuR~&Гaу nm7Kgosߗgv`~4ف/EzvcփP~6 MkuSF g\Y6FCGy65~H\ _Srdt؊yo&SBz 73{zv(717lw'4&7zIr,)>k5k3s+n>x*,<_?aY)8mKCsE,4(vS ݚmhO$yi2AIcd41{^ K9{wןҿ˻[W_~yfX~o}W{G܈hBSQw,YȢܥSs oñu~8DF,^/R΢0+F*O`'ѵ cˉe,sA!mi' Bշ2vh!_+8gIXƽ+9"A!-a@P4b?S XH@Bx}+٪y.R8g ݱ83!AnV> endobj 15 0 obj <> stream x]j y9nwBl[ȡh0:IFŸ}l ?fv;gu FLşFp5Ӆʯg*$;7zh{K+[4`u3~pF-͋ jF`ELn۴os uaAhop JcTnªZhs:/8ɆQXem :DD@tOt,$yJ)SeV9[f18J[FȪ4 endstream endobj 16 0 obj <> endobj 17 0 obj <> endobj 18 0 obj <> stream x `E==3!!@.C[0H,B䈈ȍ %*"+(DTDDDDDT AP@eDEEQ61ﯺ; [߾uW҈GIPI7NcؚD×O6mBt!BcƏF|hbGum# a`ԨE=aZDo0n3}+v!gFNi[~ -7zkꕯ6 d3dی>oCŌ(^szѮyRѦ|7!,{j*~ͱVu负'Ҭ>ǽO|svo!zQ}FCє1=iFrHXi%ګSfx&n6zcOcE?S8pp gѢ9} p.ZZlc4%@ iX2EFs#y~p7Py̖F=5 n{|/P . r!PG!r$ yQߡ 9[hgȗl?m`w9˾Éqn%olM)fu(}=ǥ@YrݺGlRG 8 C}֧T6m2uʯW݀ \?vQ\ 튶pPn *lEQCA8 ^4@Ҡ 9m5dMUn;KE0h f\VN/TrUHG\~G~8ܟuIԥ|uՏ֡SQNUא @VTmHy"j*0\R5k=iPi cvu"}iw<ĵQĘU&D6T]9cθWc/D:㻒13p{' BP}mkE(Zzݞ{4Joj@;6;PKN;x$8%ȭK칬%&Gvzy1gimE~ (S>-Y2z>b23yv6a~DʏA4[_6Ro{' oAxI/ Fj7ox_0T~ߡl(@yvF}Cs>db<=4~AY v߇5M Fg=sg]H~wt;z<%qxr|v}GG8/|)ꏾ_n.)=|I#,GďvzjL@uTd>kh1OhvW*|X6ԧ~udlW5#ھ\iq}p-׏ m}_ ;ƘHmt{[.yRi *>H啷#OpSnX_{{A>}xJ:7Q#сnF@7c]QhhZ n5sN+V_ 6ߧe=NCͳ+RE |;j IX;/*P0Wzx 7B{A+ʃ{wHhW# 7pTXjYHc+`n ,z J  Iu%3Z{D$*ĺapϝk8@e<2[)NX%Z:19u5,p`s>1P1bn }}QJOSs.Fyjsè%ƞJOCfjHN`Hk[Ws>[7,/Q􎺟byJc `O^뻨k9g75M}w TsVƶc"j5H]f~%_o$IzWE?Kޑ/K~bxFBe?f#MP [5 (3t W4CLE]@=e4^BN jS%>7(Ew VґL ?i\O^M2Ixo!}o_b~t?@·qC\k6`̙g@8+>GS[k+S`c-fY~H6$.;]{k##9(3_ HY\sHg%9k۠|+UFwg'}7v[y7El h9{x2='+ݓ3נN P2[^Oj]!] Xa*~m)z[/;aSx=|js+W{k+i}*ڋ vA|j)x@xxO4յ:gh\vW|P1jTW%TB- +Q'l|D %T tq&v-%hW¯lD|Am-yí-@[Ec=MRm|B=[{TǶo5ugߜK<ָ1Pyjiu ?>)cnὒLb'K)G2 ~vyXNi16 #:?4 l^Ͼ wlB{p*Ag H[${vX clpclϊs}O]g{䏨Ja0Y:WA kϺzoB̩_8P#J8 NS29^yWt 2x419ől_J&P";9-r]6X2E 1ǯ*h_\$CҸ sC[NpyU~? l }aBOk {+Ȯ,m}5.Nl\'4T>[`< 6c5zP.#wgGԞ%܈Gks19=gت|{caiZds%Q>Q/lj 3HvƓMYG nuSv>߈3Oy6ޡ~(p]mx4=~M{(N߅36"X7)6#Qs~ N<N5v8qmG0Z!|p.qC:򨾟2$wI6[ҘȘry&xډp;nWǵ }*&#i0kF}|Y aW}viMال_ |m)Au<$jQA-F@G%XW5 ظv&`!({)~:-$G&zkE*u~ec۲!8 M?4/?ٰg1玂<0ї^_m0h&#o,f~-6 wNU^G`ԳTNV$&m{G{_}$ڡ AZ$mBΆRq,];Y^P1ާitBpX^Ow5A/B/95nk5V\mw\똯sh7)fvhOs =<9>e؇9x&#!@E<;lM`]F1NO㺺y*mm籟T(S q"M+ SFbCl r¹m'\a tֱ`4ߋ7IQzaCڠ:ДYN12q?:gkZEHxm]"<׺8=!n|mN!O |$[Ls22=Z+(]P-?3dd2!pL #3keq% Wg(oK)=;Y|;F,ڛA\'5(Uvk!z,:Յl%Sٟ2`#S[y v+D0JwQxFNaGQ6g [^tn>=[.X e{G(ly51[a雷Ћyd[Nyhߡ^ECz~Ejށ7Pؿ)W};c?"O6^뱿cvu>.8/{C盋^|GщOX<[u<c߱D5s}d|N{ E7vP}cPb7ǁ+?E ogCT|vxog)(sSLȸeQx6`}_PrA_zP{7z7 F}=h Ky{ 4{ڃU̩o$\9Me<=uhHz?̸߼gT{Owh{.h/r<ʲ Ֆ|> ۣb{ǜ53i9Ҍywˇ{-f.u1gQ̧#JM᳝/2& ܯA|9yƃpKQC`,KTq' i G3h`"{=ZS>u*3|nrZ@t vu# KgǝeI>Y݄tUOZ{es%[N>iipqAŇK`seI>/xCcgȓOm쯒{fVZVrd7dMO}Fa_?uh~Χ~B/֟9ޘ>GZvu_^hW稗ޅv|yrׅ œ|dZ\A`}>v_?Zn oKkT̕?QG2搟7UȲz[ې,$7O4kU:^9^\k| }s s?i2pQ#cwqp/Kw a'*m46;húDD"?3({Qz ^ m=IwOQ{wv]{H>P{Qg͒EgyYGAգ5rulf`zy^D뻮7T~UjտTjEߝf8{/9!x"wy?c}dkz.ou/7n(#Hy+ |[ʒZu18pfx)+%R`+HrKP /۬Vzf̵M 9MB 0!Dv c@/Gs\Fj`0Xc=W~Λ< sd˧,Է~{Y3%{@bZ} f}'w,ߎ13XO}Ck>5g2r6KyE,Q-F,qm$U!y 32$gY:koհ sp`3Ob:w1:'BgDuO9RkuO5Nz&x_&Os#\{ݭ6G7n"4ҫ~M~ cÍU5CK_]D墩_ +e_"K@ZsW_fsW YKd3v%a,dq(Loo)P|' 7kX K|e/-qP,^ŋOW'Os'G'hxP9+TEñP8x BL"_/b?ߟ.>ZZA~TS|A`_%AWIbŻlZ❽;;bovxo=K&ް.K|Y(^.IVx-Qh&^ˏ%e~؞WAn(*WثfW+,/ZbKx!Al*G<M s\qxjg,O[b%򋵖xrM|kĚL Tb5.Y,xPh z u]oЮ F~k-,R`kiN"i ^%܇|M?d򐧾s yusZM̓O//jG"Et !i+Hm:_dggʸ\H33óibf1GJ97X 5: #jeԔ&g7ғcbkET5=2X .7~YCboŦ_}oџ)@OrsMDhv&r>ݤh=3V@7kiVŮY6C5O 3_ݫg׮=z^wp^ޑ#ӏ>uQޡCyL^v3jƤ3fL4cǯ駯-:`D}O>y4yɓ~~t6> endobj 1 0 obj <> /Outlines 5 0 R /Pages 2 0 R /Type /Catalog>> endobj 4 0 obj <> endobj 21 0 obj <> endobj 25 0 obj <> endobj 29 0 obj <> endobj 20 0 obj <> endobj 5 0 obj <> endobj xref 0 35 0000000000 65535 f 0000183068 00000 n 0000182999 00000 n 0000000016 00000 n 0000183155 00000 n 0000183698 00000 n 0000049384 00000 n 0000049576 00000 n 0000135034 00000 n 0000135178 00000 n 0000135940 00000 n 0000136127 00000 n 0000136446 00000 n 0000000282 00000 n 0000169071 00000 n 0000169219 00000 n 0000169561 00000 n 0000169779 00000 n 0000170102 00000 n 0000000354 00000 n 0000183579 00000 n 0000183235 00000 n 0000095219 00000 n 0000095413 00000 n 0000050182 00000 n 0000183346 00000 n 0000134257 00000 n 0000134451 00000 n 0000095831 00000 n 0000183471 00000 n 0000134869 00000 n 0000134909 00000 n 0000134950 00000 n 0000134992 00000 n 0000135867 00000 n trailer<]>> startxref 183753 %%EOF peony-extensions/peony-intelligent-data-management-service/data/智能空间使用手册.pdf0000664000175000017500000415524115156143275035407 0ustar fengfeng%PDF-1.7 %³ 3 0 obj <> endobj 13 0 obj <> endobj 6 0 obj <> /Font <>>> /Type /Page>> endobj 7 0 obj <> stream x[[#7 ~/?Щ$7X sI }@@i ۇzɌ-;3tc<$I~>ߌs?z|״׍z_h@9V81|սL~⎬÷Bu!| kn Yk /y8"߽B\|v 9=}Ǘ2t.%j{EpvrqZWXe*KL|T-kw˪F]RO*Ujes7DWڕC"bo{#(|(vs/+[FŃ&5ӘT&ݧbqLJ Iׂc%1FhT N:t&ˍW{xUY*SˈfiNe+7x]otC@*d c!1D7WCVD=sK_㒂HC -| ^I-p '!SMZIhL (VPiToZʄHN0;'ۊЋE=JW3y;w't5;P2.&v ʐ=O(Fe%e?|$XApނ9-̇( x˰s2Y+#XSc ΂q82Dy9&ۃ,+ܝ%FAABJf-=R925A#OL;W\c sM z/&8e9يNNQ9/ù$o3)LDp7)g,BRJ Np8 2D.|{Q4KCUP38WhazPF``7 L bYS.N}܊q~IO"+:^ᥰlF֪df;mP+޾VdRvMVZ*AR 6k8 9$FFXyC.WjL l%R3ءjZoc47iv6/\N@YUqb:N@cMlRmٓ =! zcS{ljp䐇,ʟ5Fv.mMv4nƽF{7su#'4U jneXs\5\>p(+OW%6*햴6"XxɶX|EY>kvWWYcH"#'OUޣxn (ڲ]U-<XJoA5 "Af8CM!.UEPq &թ4N#RBPmYd}\dNcy1D YHFʲ<WSbɀ+r5wV,' cL_YPC+qXpqh]G1E<`t08B@&ieq:XzЄk\qSi㫀0"~lv(#K@6y`d_>'nߔ۸Յgrljw$Q3`<فC1G. o#Ł-BOP,}` s#`n݊&?Q?؞&ɋ]8iwW N /)4ς`L}WzGr4RQnZu\f_2BSx/c+Oѩܩ}e&yW:<3g*8':,8vyk_ ޠi<]b+!Xin7nµber|],:ͼifa7$ܫ(-2+z VFwb/X]QQHIw3'*> !ӊ +6cacj\Za)v,W'p\G~~oՌb]zcJ^?wHuPH{,G l(fs{GoGe@îɹ={g x_Zu%hDibkB\D ѨR%\,Ž+'9遹%  Js$'ߨ9{< 㿗e$҇D],׃Fh<~yB_6ƾۼ{}g ~{]}k^XUdH;o 0 endstream endobj 34 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| ,-maRiY#p qg'==M%%~5IPġc RV)ɑ qEϬQ?EEVEϬQ?EadW ~aX?ثQvEϬQOEadW ~aX?ثQvEϬQOEadW ~aX?ثQvEϬQOEadW ~aX?ثQvE *xX$)d\s\ ;>L^%pI7*/VAkDRuA#"]>X@=lJ* !jR1JS z 0=-AKEQ)hz 0=-LAF)0=8S0=&hSAFAKE&)0=:nRLB`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z #ʇ55^+%Ůi)cnc^uoA%@p}hbVg<&B_>\mL|퓟ڷsKie]I޾c2Jm=H=^))*@W Q]5r"!2*۞90 ۏzZ_P6w[[Ay+m1]YetRAgh5:Υ<|:}#èyҢ~x /]o=rۖ`WÖ8SzW ̳ii.sf̲#(P6<P3b7-;yu:qp20MXu59nmՔJ%CC 6c YVܗͪODح[=. SxSIn5zz|ռ]^4QEQEQEQEQEu$YQ 1-i4jJ;F'_濸3%壁NZ4p[8g'>ë]M62)+i LB;H=wAܝ2+{C q$lU'Q;ng]v@Ě諛4{_ %8!yc5,@OA\ܾ1цg kgv&[<8ϥtB[$/ 𲞤DA߻?f.m."X:*GP{S8sPâ+ JZ((J(QL@i)M%((b ( JZ((QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE Īv6O`NiyG.#ː_#~@~@;֔eHeUY#aԧ+O.(ڀyq`t})!J(lԢ y:]Tk.>j?;eTamY2Š( ( ( LR@ E) (QLBQKE%PQERR@ E-LQZ(1E-J)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)h?J)_-"((((((((@=UstI",=I'd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}Ozf,HC)5WQmCsr[nB;sN1rv[v[ xe[?,[Z}2F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0F?~?[?,GէQ0Fkr8Z:^HМW3*5c'o5袊((($*b$G壴{ivg0xmSWZ+gB?ggB?gW~+>o\|KB>ʽd[EvzvoXwjqޱA^libiUvj(cp((((((((((((}>M _>GҖŠ?ntحoli3w{/`3@(H?Ȫq\b-QEQMwf @?r+JHcb\z( (zLry'fFp=\훖S-QE ųΦn@FMoIYK%85 w9&@1H>hEBCUtM2 8%Ԍ4l'<Ebxm8 1ٲ|z.WRx ;ASU'<*V?*YY9*qNWZɥOxn9L:1ncQm%58#fs uW}eO^Ң6$">p <,k?lܚ mL z}sPoLyHy^BB)` 2p;3\Iua8mѩ$6罔G5';r|{[6rOc7wX7%_7 $9$=oIgx,uK+G;/YkkzNs͌4FzLc6SzdUcGY#xZ݀e=͍j*y.i׳I-E̠n= Hrmxd̛]rF9~M7F3Fb ރ׉hI4vߙol]T)Y=ckg]DM7Fi#T4$o#6zMIJ*% Gn?h$HTè+ɼ2u];XI%R5?zo9 OK۝ KM3RexPLUm^G'zzm}zUKs?Q^1mGtKk}3Z I.;)eSx^@Q@Q@Q@Q@Q@6׉naa3D%N7P}FAqkGiƚ{#}ʽ0CrHW>G@ 4D򮂰?+jEk}ToElQX~/SY$K,<Rdr Wbٻٿhۗ8/w15xB;o @d4e Ey#20^w\$xR Y^!^ݮȽפ?S~8Ŀcy>h'o}";6JtŸ;=O zj^vdįOrI=.WW9z@JͿݟ:(>(ng^_wkW'0̧rphKƔ;fZ۠I4QHdmp=OfxVT2 nPsGlF7Ik>[qumIT1?7 rz8^yⳂ_nZF6qʌq@,B"F)v,x{}yk'!gO>)o3io+=+`IrxLujPXDH-c1ڀ PzV")w|MUtHR!`'AS}0OFA=޷k1;m6DH9c+R?4)vy$ݎKIdt}hO>SBc-"2u7\*U%RCm=T9Z()$2J'ҟEQEcYx;]Ju@Q@Q@Cw:2y2z;ho-edT)"7FR0EcBfZimC(n~ lxn<PvͣA5Mj$2C#%f<ɹ[ޝsnC"  Ώߥt(mLcӹ 3 |ў 7Qmu;n !kRErqGJȸrqKdyml+.0HG* U!O=7dڙ2\m3 l$ј 3|dg<QGҀ2m|5moԗwF0mG''ԷZFCFaOw&%@S h{ΏߥekzTװZdѨvbT.E~$ulV Ng&n_J˘^e9pqrT+FᓃJ<P14\Epd.DT)K+ IۀdU<#y5H~gm>Ht|mӯGJjZE:\|t(Ď<{ڦ_ ji]jm$إ0ł3\$_Gҏ:?~A o<-a*"fR{{ިQGҀxh &x8=FjZΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$GJΏߥt(J*?:?~y{ (QGҀ$G@({O'o;o?,Vռf8o&u[>T-Z |VfTޚ1&sLiqjF$ \D@xddАAH5y[VSׁ ZY=@=A] Kv?tx+24_}ױ[/|(Bt 82z[-֭$Q6cXx{O&M=A'Gk#}b FK$_}tz,iS'ۥvmc!,^I\Bo䢑GoM=A'G&A>GތЮtm[u$rȲ-,;A$w&x^[}3[(J~^aؑޤ24}V?(:ѿE`m??6? MUa?ξoXx{O&M=A'Gk#}bV&AoC ZXF !dh24}V?(:ѿE`m??6? MUa?ξi^r]o1a;Gl >jH^+C8'8WNA9 !dh24}V?(:ѿXPi?6=LvH}C[%H"hr 3&w#OJR(UZ[]HTqPwסEW!ijo١`fxxhYTJykB?Է}VKGg؛پϷsyVIifuVi#Դ IEg/j/kkݯ٧]ZfϦAּFNk{^) :Q_A<;+&4-_R:1A lQ@mo-PEP%}kq|Z|h \]<-اF%)fO9wcٮIt4$6\ nءs¯QE2O>Q'Z)1GҖ>Qz _֪䐹mQX?oӷDQ 牵EboӷDQNG$& :OӯDQ 牵EbANG$?Թ$6_H?"'ZIQ'Z)3KH: Z97jueaFUVk'MVxͳN5v'!5I 4F[MuPJ 0HTywYTgDW!hMvV8r4Ed ՞HVHIh.V)LeֵQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE-̎*;qZu@}<FG\iiS@bhTaRGl)`IAϤ_:`G$쑈F2@ :_ѣΗz?iM3L̗Y ؤTTK=4yGdϯX !#fs VsE3Hʎ6%q(o:_ѠM(?Q@bzY>*$4F9E#!I7W3E_μc-aJ;_̎X귀~@noo5AoI+*SS_izs fJ\H䢟-GsUkĿۺo!ƏcYU{*ʾ}CwX?Vk>=?_p{IfA[wX?8 tI\UQ5=ylT;k : .Si_5*s]ؗwX?Vk>eOW/i>?+}5$#mYSb8RAz8YQѭoiW`H?5X BƭDT+N+d}E9S!}>Y"ƎRQ%CNԚY-._B2g!<, O,K3 EykwWjzL?6enF=9>nP֢4fB"iDYL$ty;z+uQ ($w6x٘j/O=3U5ZRb_Y0$]' ̃mށ^ܽ夐&]V@89UU* W!-[Q׆P"il% s{aMj&QE1W?.N2I?kQEQEW;㛑hSKug41-w h8UMCKbH$m99R}C9&fmTPC>=rZ^<#0d22)nFy1gq #cI5%c/[]5{ ~#Ƚo_K=u?asc?/C=Zڲ W:ƺE7;[VcpUr2Oe'qWU)˙;iu>~U٫公Y(;vec2;p@=K{]4wOqhۅKt;k2<1\=%ks_f7/jnEs!#;CcA V%KI-!(q5QW<<׽0cqK'71Sh ;~ z[MմOKi3 ,dp@<_N+6׉u :8$|T4U7ukzE=VԮfkqegoV00idROZ N'[8~HQ G*3~_Q\M?U/Y_qhcMDHI6ǷB Grzu+JpOOVENMu*$_O?F5oQ1~Ky:WtF$M" \T,2c$X n9ǧ\'ZkM%cNx!ۣ?1Yqzc&dae9zNŠ(EPEPES((&)hJ(QERb(3?@ z :GM~F= uݧz7m?ѿOQM~F= u= 7iOo(ض=aF?z7G2y?3Ge~fm?ѿOiQ OOo(~@~/4}_'j= 6ߧPaF?z7G2y?3RhcpA#giOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€E7iOo(SvߧQF?€T%ɍVߧQF?€(_'h Oz7m?ѿO ?a>/5{iOo(e~f^~F= 2gZF?z7F"(ꞳíiYJWUCWvߧQF?ª2pwBTGq!.{fiuM~F= =pgP2s͏MuM~F= ?=<2s͏MuM~F= ?=<2s͏MuM~F= ?=<2s͏MuM~F= ?=<2s͏MI}D̢"c,^F?z7mᬺb8tm %ڹݧz7m?ѿO9ڒJI>ECt^8Y;QBNsLPҮ$7&%봞OGc+^ɁHfcv=Yr}jjy^5ΧхyrO&0԰ֳmw!Khf6󼱑&䍜n2.rM^ 7hɌV}66Y2w5x^JK-D8RA=OSEwGwg浵.a8*hr~\88bN q>%GYow$p*]iQHUT`'|[XOiKorљY%Ay nJR؇QEQE0 ( ( ( (Q@Q@&)h)tG4RȎcmp}#~L4N{ Vh%B`~\p5#H&s*XB<,4ci;UIG,s&W\NFAWxyy[Rk{;ż"t*R?xdO A$cnK^-H 4*('3MITep `CЊ J H5IٳV'33\݌61Ztϵm8/s[rU9@w((((Yp?":d-d.%g\ b۽=F~"yK A8, o ncp?Z[ص _j{yZNL@X Pc5-$hLbMG(h6z4k"d=*I. fYAW`9cRNyOn7:[[4=[ )h8;~I +Z'쑯mi/cĄ\/A*[4D`:M9+K,s.[#+'F'rTl<@/O-֫{;xI$Ljqe.1n z:Ȋ*rꡢE$ RHbWSHPQEQEQEql8PI:YoR%(BdH0E.esO=6b|w:, ER۞suZ:YͧKJ۫[Š(AF())h(QEQEb((Z1@5>:y< uojؒib2_F@Xq>m%A%P7\7\`q@VlT)-6W9ޏ#9i&l4G3I x` k@ y[Mu4{U]pGO΋o &nӖ''#e,G+9|(oh!qۅWeK؜tQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?is3gdNVr\mdr88J(_9H3بe]¬/b5=JWIs*M!!;qtP9rATgŵ.)NqZ{:Zrkbq9w3>Q@d,4J+9lEyAFm,8QP tqt&SB0؁H# sEQEQEQEQEG(&HH}SMO%\>Yg⦢9Gd3y\oaċ%vOL]JnF{I.,oį myR ’p8'u(.MjZ:nE3)$~cksajOYi g)ݛ#|nsy9wPa+6*N?;/i9̈9< _~GGXҽgKHy()$((((;ZP$IE^ZHWu0Yn gs8:{wMShqCy͜Z4E%RW B]-yMsί]>fPIz`T@_@= 隕sG#:"F rrs{8W[/n d %P U$gۊq#(//-).XmBH@:-T{ޖr9nQX>1ҭ[PzosnȉO8jfo=Z6|߲H'eW'@@4W3 } A.r\.Ջi:q"~ҪcOn 8l6rG4`hfCF{ G`/E'jڠ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ׿GE4SBg.: ()q]'8R@֟7:$Z^Yy@*l$] PzOb2H&2<[=ElZܪ+"b$%Q(@)X.a;_ _J[Ȩ|VW>q o-RKb rFAr 8@=@8bKi(Ēcsc<? N7s-645v~UόKl張[15쭹ĄecOY1k,pI3a2ȩG}^D&%9cD޻I`2=r=8+̒MlTHVFܘ P0V75^8IR&Hb r6q7s\nj`Դ]i;Dme}Bm<WGTx3J]DKLGL"#q }gG׎{|%oiv&|msrlBCoIf ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((({ ^CE4&sQ]'8QEG=[e&}@4qG,f2p3ܚMԴmV+"*GE9߭gxH-:FE/$IBHtz|{oq,2D.:p8*J5uK&>ͳv7mRq+Եv9.Ue$Fĝڕxm$DGԆL!c,A]+c>=Fʎl.90)Ju:/ꗚl/fmoClP6,\lg=-W'-ouq9s,*\͌? jzoDV0YvOd-:dbAҵ j)4-yĬ! )[;( (((1IZ(( QEQEQEQE())y΢z+/QMkA`GsXE>m?ՍLE:nz4GnŠủ, 1}WBC)H#U:Щ;[VJtVf+]Mm8]8 +Y\xʖJ5wo<\6T[h+˿)!'Q N? ?¸/?ܿ+˿)!'Vjwսvg OaWK;Ԝiݺu<-jilE{'Z@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jEZ@P*jE-4TShtt91F(汦iҬW7uBG SZ_Zj:#ru6Lڌ6fQ) P? \ˣ[[[G7--I}4*1 r==&#gTRTw'U]ˑ{eeaΙW1m$?uwqj#NTp۷ݎlM4IEC?VP}庒/Z .Nlno?铊c/.5aL8*^s)޵ шϘr{cH2R# Q_<:^S5EH4q]ƞԮ Ebu,اНQEY!F(bJ)hJ)qE%.( ( ((P_PI*zOA\gQxTeF9v4;t%?KGYE3 c5curQNgѲC| t^ cB0Y,-6G႕^;JhV-Y T-ugEUOJi_t#5r|I&n-wLT㑎qcsc943O|$tAZDbxg1ZfhV?+ΰU;wg^𭷞VP(hʳ@DuF ]Kn#uTXwϤ%̓xĥBQ/a֪ia_l*n%btmBprogoO!tOkuW)|aV&b9o#KĄA#ۭr:kX٦#̌)rsrzևmetp-h' '+!x@Oa)S}n?C|GV^k穷=E>:`ۂ6AjyY,MoO0m߼q zz]i$#$ S:UmTզ?HJhܧ9Gyv,a~֥ ) m(((#-=t($hSK|mPbpj!(u10{nP@\+ I{4=Oiڈت&#*ZDַ9R/o"H W>z%?xjzo(s]Eq/وIAk]jxJk&6^00 z΀; +úh]d'!L67s؊m}hۿWͷg_w9㯵_w">Z٫!c }\,m59[Y]>)]rܱoPԬ 0+u*lzS/4cq}ue@ ٯQ<wBą Qg+{GI*d1@FA#@#մUBc 9d OqO幊w SOY [ðj%ϩ@諵GKknYi\MݷHdz,BCC3APr1W:eܳe#-DVݲ>'8 2T((((((((((((((((((((((((((((((((((((((((((((((((((((((((SDC)39(y4REcauWs]A,)FOM鷑_ZK=6CE`wjek E%ԞT*z֮ReNtkk,qME"$V:ޟERwm .qG 3ϽtVr {irG-㣷#\UG_@,Anƕ Tºo~GדV^zj>,aiὝiY?&qcyJ>!]'+&\Ҽoj]MF{ɮZxBmuBmt+FomMy鶊_08lcצ;tO>u kI!uQd@mg|*FwNG&gV2jO9DYd8 mhe#*6e+#)L 3Ovn]g"T?"׌WؗC1__ #mIČ.qGZ΍m& 4:4۶;tGmg|*ih:e[\[l7no1N2v>хUFVMt}ΜbisVR[sP$QDU Ut*Gk{][9zC2V-tb.M;9ǷxgFeӭY$v4[EcgMHIo }[ӵWo9.ك gc:cp=0ռ\My+NȑLY~a9$Tl!Y1grf&I߂x'[PDž.u[`{[{G[6R36tz}-/{m[Ku%7< l1޶h :mķVVӬG1bV➖Vm QQDq*z((((Ddu 0T=+:ú&p.,t}>pAl f -Oe%iMk#ox Q9\`3I ;NcXcvduv4T4.أPrn7HVcޣ7\TPY4GA]f?k@.k\Vw,vZ4Prh4vf4/nn1>}cb*( LS dKaj-%\bV1 hDp(b28GnOEVO{{;xfdĪ d"Ҵ->1q>Ty뻎EU}2YKgR4JZ<t•[׳kYJd63VhH"Y%Hd#*\ߎ*J(Hin*0Î9>hc6hM90/Gq~5vwagyi+nU0PzI{+yeT'? EW{ )/R kVs.62(k 6x c%s>Q@}ȆUP*;c:S+^IbqGP%T acllKr>\c5*ۥ,-Ċ Υ(wmK@>oƪxvRנo4!Ԗt`lֶ(U T-PU+UT;MpdU(>Aѵ9k&U]#Hޭ-y-_1B >*j( iQDDPtT?F-~ox2E. 1AVh hZE^[V01,n䞤_((((((((((((((((((((((((((((((((((((((((((((((((((((((((?!O?9Ukվ2Gp2l wȫCsRԮ&M>` J^]zub3Q@mdwӾ6p]04;t5?rFq^^/RNX3˰4kQ皻;i'DݢF8/;}镃e  IT7M}bӤc$6PK n\:(Ǟtvo^Mm88!I_+up ]&yU/`&2Ã8c\}k:"5#[J͸*5aw$u(\\_¶yYB*I`la\*Eu,q$9Sx5)}Ո^ӟΧsjxwZmmsuݹ|’9W q[Zbbf-Lfm?o,J[6k W*ܥ.VWk\ISqrWk]EsG<OAngΦX xxro$4)Ϳx/?1Q_j~mQXV3ޣ[ӄv[~fcszչ(./u; #@TUh^sn꫌Vo%zXyYX`y$h dp94EeZs IF#v*q QxO#ÿ́tlQEQEQEQEQEQEV,T;dX P?EURi`$*A b[K!o$NOЎ(j*jr=WPlKQ;9R&c$.,H€-QUoVɮl G\qw&ILQXRXVڀ.QUf{4+gٞUTlV CQUc%kﭞ3uJ.:峊 nIh\ed+b:V}ߵc5 o49XhH[lF~bϿ״m.uPլ-&eܱ\lFq  fdp-ۮ @([)%pZ0wp3 j630[;F%S;`FGl@Q@Udl$Or@~tf{g-`,POAqKi{i[+na=$@OEPEPEPEOP%-Bd$#\̱>ۈ\OO+lح>D26sqNmy(''ES}7VG}7PXmY봜SƣbaaynbBj푏@<Ҁ,YyzvڭHJ\#:#@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ԏƊ/ԏs`p(sE-m>,][·Bh7da*Jz']i>wGGXalʐN A Z}3UǔVGކL{jQR_I\ߘ rOGNi\Z=>K!iPN+OAK;"gbFޜs]5孅\^\E+`~&m ynܼs<8CwI6$j.KmB k{o*KYQ'rqFuAk)#C qy1YNX&~%ӯAuڒ|%וR0OWfOB{p2 p8^}+ǤKEYQKE%PQKE%PQKE%S)qEQEaE.(%uj}{ :Ns'n5bb_ D"2yi-83})t=@mMx鶊_08lcצ;tmWO3\0c̯uɜ31}s$o=)Pcp udaW+ʤ[X(vx<~=XӢgFw6{jwu2dr7Lu&8\kץQnzi^3%@ֱ]LG8Sw{/28D6dB> $1=k9&2:0TE/}kcº-<Rti|HqjN{究IN(N=N<;{ t\Fhҷڣyʍϕp}WuH帾ҭ弎h+֏̌wP[h9<ס v %@:Mh֥r9:+ϼ8? x)Ko/|;| D1sӑ똾mWS~çۥα^!<(>CN=;z ]4Eɹ}8b8U;/ }m,斐?\r 4.uiV>U[ƢSI 91\Zm 9667$ uFC3N+m<1[C$rIRf{LY>-*x+F|\^39B:l@gč<4YX}A &#mH1#rpjEm<:lQ;$,펵7kZt$#rI8m-Y^wf4ckNμ.I߂IZyKaecCicsq&W@6dtc?Qc[VѡUNK:y&X9UU~@ ~:; Zͭkoi%ٵ01;|5gkڅpMl-e:Ό6cր9 &PּQ_ZUq,Wrm~`ssP/ncFKHF0lRN wuk,/V%XcĮS9"-aY!ʉ0 '(='(((+)V "WjicOc lL[9EC+ # JͲ懦܋NZ0x6/?cȋK JVG1 G~IFtmJLClQeب[ =x/vMi/e%ivMi#ox vrW'=~AvBLyfcߌ;@2::/qiqimc5uՀ =1ӌntD^@lQT-!>9N6zkEڳ_@@F#4餺 &[tdߞ5^64zl$@*1X@G&i%ėB$xiHٱft۝:!.Ѵ셟"7M<PGvT<t8U86<+o ͶVG"Gп`[d xV"TQB/u[>[L`u.J1^Ϩ%k$pMTP+hR܆c iaf :[{;xfdĪ d"tO\Onz^w~lWIi X<"Rӎ? Vl^oٮag1)@Z7'jkPhkm8ZU'9l0g5I/V[[InE#bod`~n{owagFArەf8:fl.gy쭥RIG#€8qI_%QRK4jLYIۘ1eAu Ų7([+~ 9줽K׳kVsQaf\D֐Xc,19x.A)u@pB,rW=AU.) ۽򽺖B7ϖsr@?a!y0hңlt703Ē (eaA@4( $v [-TM'FU;p*n6^B(c O}:^{Xڛ,c |Ju%ķ(G :z2:II5MAMk%1 (#'bP~#y^vZL!$39|3u]"RqA.F'Q_>oƪ_xrR+Ć(md4ЇRYуdlNh ZiWҋo.T {Џ_˶~ÿomߏ+۳گ*PP0)hw66׺uk Dm[nX<誵ޟe*-흽ʡܢhŸQq^.3S+o bB(w<=q]6'C<($vUk躤6X]ʪ^$`Aǽ[[;d6[Bb|  }@PV [S:RvUڣW!࿶Zhwȉ#M+[ɸ6mϧC VGPơ4PtvMvd]1A@aj.&K(u-R"LbMNpeA^Yiz׶M7NK4ۢ',rkB ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (+^QEЎpt9“PmƁ]zImI5i͍`SwRyS<7ז?#0+CaU-`a *W #Ns &3Pƒ(!udt4zY)О[YS<A`TSA=׵kq/c%ت $Q,r%mkH[^wdW^GzZEAI?.ᰳC vpeA!z竇RW;]rѕ2?ZEAI? Lԡ{'1ʛ'Ky導7IickkO-K,C ʑ0xu+M8-/u(^J%$:(2J+ο?"NJ5CNˌyV/%:$ 3r&'ڔ0F𐨊Y:SQ05e;rl%zֻ\{Jx!VK7>20:xcs*԰9I)Ogݙjk:n%|ݤU'vQT]/VזZerpn8o^9P6Za=k\Vo]Yڽ# #8湴ԭeR < ¥gH~5eMbAA.Tw=dB5pve09 Gozv:^,9թUn3~mVnfb{j z 9u+I h$_xmʜ.w1ː+~I~<1Z2OǵrUS"ソᯗR[~vb,A7 m 0'5ac/<iդv1a0N8-4dcW#kon1z~e%]2FJ<ȃFጌQ`xjtkw%ҡn^W}|בi/ulZi d=} 1 ?,`szYM{{CPg<)Fr%E; NTﭮVo*Ƞ yjt=vPI~ץ/VK~Cq4Ux5HTɬDH%uݑnUPޘ;q54q_ˌ;Np ǵGeuuqmow @=7z֭?u4&}\`Re~aV߀mFio%/]D"wg'袊(((((*ʖpN,R(};e!*F=1^so5cIm:>Ҁ;uy.!"JTp1Ogx[S#hqײdŦ[9 c'@{U56'7b-BfPgK9l+d@ť(۝܏L)I4P DGG 82<}_ ;m#'Hۏ+ΑQH;LPMT [y_ێ|@ɿ[ղk.o#o\S&,m;0FJdmą%nm/4ZHG*۟k@xpsڵ!|A"R(Rͯ#{TU(1-TO =eaGZo\~-B,\$Wz/xrK6+ZYC&G ؅Cƅa n:K]_5c5ڒ;ir=ܒmwBeX P0ָIbolVR]RNƑpFQ71Ҩxu^{佳[Cwv7(KOɆ#L`5[Iw,w dG ffp-Ӯ ԊB#7 1#[bA,7iGZͽ^mE,ҖU(Nϗq@Tu;k -k1L)><,מ7K1Xq)u.>flgD= eԟcYM+Dm"ye ̋62yҹ=3D]K k;Fg[[B9)~"4x9խXg?"Ii ԚՂ[XE+\(W+s2* Rbid1$2m.y鎹,մ0k$A #'cjZǕAZvkk.NgF2Pek\_U@eǢ=&c1,D16x 2^$Z@v<*u߼F6<3JQ}au< RX/<PAe&>% q )1FF,s$ba3p{+g \_i)i:x[w7Z^FT*,cxQ@ݷ4׵Vr$&% rq^aIfȗ;eѮm(-1-לWEPEPP]_ZXZi}{xٓXԕVE$PP6rQ@擥ȑܫqp2*HV02X=s^k/ih5eXsulݐ8 ֻ-omLWZ܃oƀ,AwOm4sB)$lX{daaynbDjF=zV7YBXۓ\ms&9mҹżnn;}o7wgmYMt;gR:bו tkgzF[d1e}NpeA^@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?O?Й:QJ: +s(9]RU<+I v]PO0,&MIq;c4Đ}2yrzTPy C%12g|n::ƪfYQ"wV!?66 92θ\hbŽIހڼLZAr,է7zІ%P+|nֵ vnBzAM}ZM$Yjq[xǬ{s”YcW/hQ IB+̥KaPGb}>A.;[v1kvL}POPI+&v4Ix5b~"K|L*yfp ϰ).t3!GAG xZpY:j|qqi|#M[fȠJg?ifŭlE:/{KQ̎;A@`(1?[ǖ6jgd(CQpx⹁۩iV!kFj:sZ[BG9c[Y?_!&|W~mt'N-6o8$H20KF sJ4*=(gk[Y?G"TX99x{h^Jd  `$gIJ~gmrĄHv-ϟEOAEMːq'ֽlni[}qI׌{K*mKWޛN3d)/"1p ']2U+O#1bG1,nϽeh^ 8%2L pAl f -Oe%iMk#ox Q9\`3I ;NcXcvduv4T4.أPrn7HVcޣ7\TPY4GA]f?k@.k\Vw,vZ4Prh4vf4/nn1>}cb*( LS dKaj-%\bV1 hDp(b28GnOEVO{{;xfdĪ d"Ҵ->1q>Ty뻎EU}2YKgR4JZ<t•[׳kYJd63Vhn/O4 I\F&`ʀ8_ڢ1aj-e*N=OUGA+"[$>hoc ,GEc),V,/&LbňqB_Gl£Z+rmy}kb~3jnA.;n(| (բnɶ-zJ3[4PLѥӍKmoGh ӟ@+X0,PAJ(  io~fvxķL(r3z`PEPEPU,|Aqc5b>eu?|{g8qMʱsp̱b28*ԨF0K(Tl$g/[HZjEW+n29ם[DsYI_޸[ҡ0zP݁+s& 9Uctv@G6`qKom.X%\c wpq\o5,ڂ2y&2A2-@HK*Wi;=ö vPlff)h$J)hP(Q@Q@Q(RъJ)h)t^AMyr c8lj` {[ Ke *"f>.VG29Q6}wToiv݌aouk10!Ǡ$杨^$Qak:n%|ݤU'vQV7i>$/-u+ Up$\1z槙jQ^-x\tV(l<lSǰKuI[= P9vǠ5˦k,I f.IIFi[UNIIFY/%$ ?-?!'W16k73~1lw=HK4R/ ~]km~y}ckެ-?!'Ww۹+k38bWrpm#} ^io,ui!e]Xd+ɧuڬkuu bMruÚZiPR$gAPXj ib2~Ximx8󣢪Xj~ M_[^DeCu*O<έEE54qˌ;Np DZ+[{& OrhGCր,EQEQEQEQEQEQEV,T;dX P?EURi`$*A b[K!o$NOЎ(j*jr=WPlKQ;9R&c$.,H€-QUoVɮl G\qw&ILQXRXVڀ.QUf{4+gٞUTlV CQUc%kﭞ3uJ.:峊 nIh\ed+b:V}ߵc5 o49XhH[lF~bϿ״m.uPլ-&eܱ\lFq  fdp-ۮ @(_j|5 y),lp)>Ʋ[z6iuwK =ldʟҀ6hy2bYDc` NhVtz݌Xm0qY?>V%I:..9 ^|E]޽R&ˑ!1,vÓxέQEQEQU,^ tv 4'zEg>"G6nr-FXzdU82;KhTV]ۥŴ eaG 幊w F=x<JEgEj3!*EpGPTVQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVR?ޢ#)3t80 ( 9,MZ"@KmpjX^K0BF ؀pHϹj7WvBfiIi~F::KO{y>Y'1)Uo.VLI#;sjo,rBMFzF8O YJVG7 )hx *TFNAZ]lZ;1y`6UϩiyȎG|dm+wvo/VWʣL@yvsz]OMzq b6];!434ag;rpqthkFҙs .7`W8bAZWAfuQF* (Z((-b(((1F(QK((Z(SS?W9>ֵj?S ^A>Qǁ~ mv0;K2=K ּϬꉯ-q,̱O x5$?E̗P<R'>¹5~SU^ ^ ִ=u9#9k»Gu?lw6$_bf4IUxsFk)F2(#9ǯғ?wF5MDR3_DLMt[<0\z68iqk{ۈb*%E`ra- b3xjC3 ,d۞Nu@7zIņ0'χA4גܗn" ;~Uݍ>,J,:y<Ո-ൌo q!frPYII$QEQEQEQEQEQE#e*#Wj B\8,t`6_1muT }Kc>##eadY^ۑqcֳ@ TF4УivJ"{dHک(Ԏ]?:Whm, a@gٮɴM%줲m.ɭ$mBJ;O H46I3,{ghO@Ye@";V"n<4"zFQ`o!7C[q.hQ 5 g9R`7HVsޫw6zr]&4ZD+nlF߹ձ֗{^89oY턈QF#Hm$[ėo){63UL]sZZZ1²6:s}VIէJ{JFBmAۛ!Y>B|W\c6Qz l7tݷjXXJ#h@8%gbо0I,NF Wc85kR]dMc) a n95ծ-XlBYϪ XZ#c*;u==h-,55,/ih1te#q?xc*m _^[L`->xbL7 עAXKqogo 8Y dZNB]7@;#x\MB${f'Ynu`qfHXV?k*?'gn͊)4>YKgR4*Z;hrQdf((+%&i,VE%1B1B.#WSU,_Aq yż/gK}dQv:,a7:Vˍ(-o WZw! 0=?J}:+;y9$Y2).t t,+ 6zP74BX5/햷Z"k+Jn'E n;~i1zmnw2B>(gXXM` cր<]C^M=P2ZEnۨě<(ʂ3޽bmniErORX օQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVR?ޢHzhL(t1 (i)k^FH` jYl(!v0I r qww3;mșfw.0RX}*DžtM}Re=1Vt[[;)o8"*AGsBHuk.&-M-3> Ƙē<,€q?7yjκ3 2nrwcP qJ(#3fbPpX*h"4I$dUr8>4x,,ѰABx8ZV_BQTHQEQE((((((Z((-5>:7W9^Xgr :(Q=l(:?粯^]ag5. !+h>B.MK{]dže1kgGSKwΧ^GP8 2Fk:aou0!Ǡ?$֟[j1^Y$A#B=).VThF4=GSb̍`NcK\[(u ;SK.3b6),;aX?+1P?5~{f}n~ןۖ+K\G"ZTo%:$ ?)!'W/&%8#䣿?"wIt+kk2nܹbG# N? ?»]k6Jr{w9nCܯ⵭u۩EUVEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(ןGTSE4#* {D2,{I3VJl4O2Ȇk{yYv+c%<wR8bP4;M*G40د4q=z~y*Qֺj㼰C{X#idk7tqAC]MR%E-%bZ(((PK(((((7Ty61Qԣh6}ÝUGa.IJ~{>UYwͿد+F";z*R~"/- Up$\1zך:֗綶Ҏ$wG3}cWh.nNѥU0Lrq)C\䲶=JN(fǠ5ͦk,I f.Fu }<3N$LY pyxyo$y#¹[|v^kk)/'4ևQD7 dbFp_+S]l4{. Kpz漎O?bNhO3$gAPx`_IeQc }l/?[j{9 aJ4C,)C6FL185`H:1,ŧb7U<—J%HEU#hP96Af+ #@zmyڦwNAl⦂xnIXed+b: }f l$ gR Wcx5kÒ\ ^58Hx*4- Iqր:X5"m[Ԑ#Hw18";-k]-RBP,}N=e5|Ke!!5^!R&fY)Ttpv 1Tҋ.W&; 1H?}v:)5i([aI61`99)zű*Hn"l㞞כh ηNy3'žx \C[?xT(IM#Iۿ:2UycŒu}A޽r&ˑ!1,vÓxJO4ՍD.sh!An8Fo> ( ( F`H3ާcv̚Ŧ") b^qczؿ4.DP,[WZDH̎걁X!{O Ae-+-0֒Ǝeqk{jbn/p ;~4f {iI#`؎ D5 # 3 s'W1<ұ |*Ōܚ<kX5m-Otqހ;}oI{;mRk$41#:#~]C[]=P2$EnY( s*zJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((dCDCM1J:Q[ E-J++]jV -$$qoSsZPI[/Pa#\c~,Omʤf%}]*-=8ŷd#MH h͑qҕǡ$(dzUVcωd+H[=&@$,9 ݁+o}ivPLx@s8N%\Caݹ3L!#mi׌G+J/NyQȮ9nDċu--n8c].aQ<71JJF 2 .l{Dҥ &.Rd_ݜ._ )蚍01ԩ2Ċ Jw F)h (Q@QHaEPES((($=A'o O1<#XϫIIJM2ga$^ X߈4sʡ^E.s+kK Qqk/…"րGVNdl_"xsFk)F2(#9ǯҙZG-LJ1kG'Nè.Ro|#PP8 2Fjdy%+C7:Ϳ 0#Fqg8`ib(|qՈ`s}"c⥉$d;fx|lu8IM[6$=6̌=3шҍ) sq>F9?">?ƹ;%?8ck{9/^=ҁA ~*KL\'Ii̧#>jS)fXm (;[GYZ-4H8 n̓9B#>p7cj٢* +NFXZs78yෆ2CHYjcxI$((((((GEC+ # Jγ\ O["0*(hSIfeZfrW'=t#LDNӭ,Ęx=靠g?]) */`< K(T-!>jf{żkhM.՘7 =VM:$Do){63P6KZ:}խK ]h8#;֍M"mK@nیgϵ_XXJ#h@8(zz5ZFh+W B4J#Q۩S@n%Y$q*H4ȴ:8O\dOnzƮQ@_Lxg8m:VFs ՚([ 鷓$}y,x涨 X|+Zl. JSCu@~S:i|QiJȥd? 5@xjK8Am2 I8ᘱb8Pỵ[9֧ܣo_cآ0o<#߻ڛKN `ہ 08J<5h[櫲mj3;`q/4itaD6{4-e9fF3v 0K8kR‡Zei}ߙ]1-.J2ã5EQEQEU{ ;/v\ym<>= X+O]OvMrI!$dR\Օ>9eXIsV ^Fhs-+cBw}K_6 yKH9 `y՚(kxזU7LK4ۢ',sW袀 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (7Q7SBf8E((L(ŌWL͹-?Ŏv=j!eA g_)h]YcFkpGykm;w/6>o:__{,mdG c,ǷjM\(Hd|ϫ2>5,j:\VJeU6(ŰgdX Ii(-Dgbnκz6cmsO/U ɦO#%C|V9VH'r[8ST2^֭qq[f#"v=g& /}g/۶9wq^(QKXo-丏Ētr(.gQ𞧩[\VSyOfΥ<}3QJI upSO*Hȓ%s]uj2u+;X'W }lEؾogWd;ZQhݤ8A,<za?yM| ⱡETTMRCDěA'qKq OKv (IPgAh57PIc$A B*Z(d+-4;SPEf RQ@ E%RQ1h4S?S+B+4)._dwlgV84_kdA7|[ 7WDR֥5 _S*&Ϯ.۱y8#-f5Af;oӵ]V+9DJ2?#ҹ}VmgW6b䯝8m'Ėv:erpn8o^0~4]6ZKk Ǘ@yø n? ?Ÿ#Rӵcu:G '.8tԭeR < ¥gH~5X')(ߣv?5+c)(K7c\G%$ &maFo-ӗR fQ2E˭q|m/Ym~y՝/%$ ;w=msr}ۛg @{ X0n-`O!pk|= >V"<N$, py{4.UnAS,T!ɮW}sxsT:V < A2d=} Am?tLFS0 m-ҟ^ttUK SOaikȕ3̲(n%I̶N#,jXл=A$ÏŚcKs\C(FRkN x + dcF*Q@h~@n$m21SlsENmWN{hrJXюx<=t56WFzN((*ex "(b*:żKD$IU %ݵZ\Eq }"p~q@QUP"[d ZX2ґ5+'whgF_]~jkzMweܰ=B8l-e;0FJemłǎJ~r6٥[>6g$FJLdnϥ:a-^}lӬQq-T\CuOo4sB+$lX{ր$-kkxuAŽEmwBeX P0 V}isai3.#b3H8?Xk4K'nw,@\EJT䵎fg \p=0.LYccI5+KF'XPȱhܣc'TEcEi ܚޞӖJ yFj R6bid1$2m.y鎹Y޽aHo*HB8Q'ր3$CU zs@d'Ļn!';ŀ 9)*Hww6qO@hk-:"0>\ f#sǦuh((w}i`P[Ui ={+>]=GUwn.2#"<V02X=s@.-h\e$+b85lL0L/-SH_]1zP+>]//^U V+gR:b(((((((((((((((((((((((((((((((((((((((((((((((((((((((((^QLFEQEEÏ0>9}|Vo3fv8lpF@{ėEwZʳ2IsȰ؆'W-goEΘ,}Kr$n;w]5,'Pq88g ?ks)Xix~I4\~V>`z-M3RudL#>Xe]q=t>_6L &c^1Khs{n>叻wo;- 5Yc[2/bNs]֝c|ѵݝFrXʟl*Y&98tޠaЌ#֋ cͿjE>0tyXַiV22+1vF7pF\-tڮM4-$0˒pV4+ H'*Tz^Z5!r=N:w57: ϴ؁g`.+CoFN0kJӥXZbJI$3OOk 6X +n1:?J,;Qng/>x*9c\cjj#+qq6:.FL89 rGueŹ_:;$a0 y$10q@*eIRTdHpN{X.Ě _W+)om,D r<9G4]~5I_VYK-W GLh;dmvenFDbP09>簳fWPу0 q}Ox+HGiv r`Tt;r]ę!ݣ:]WMwC}SKXuV\G&x f _s#>v |lUJ(KೊEr\[)9#qN{UhlCiַȆDY۸@2(ʷ\u>n:k)ʬ+}@">>K!Dlzd hRQE1-5>:y420:x= b3pifUQǩa[:׃uQ5%y6;߈B\38W7&W*jKk:֗綶Ҏ$wG3}cWh.bDSZ 1|Êohe=H4Eg8RxfI|FuԷ_<Ț݉uǐc 9F"9>Ѝo{q3]EYb$ B q޽77:巚0#Fqg8Pib(|qՈ`s}|v.:mkEn~ucµJN:O~'xmʜ.w1ː+~I~<1Z2Oǵ\FmIL{m%zg9Re}G s35WJR~k9+ՄWkt_5g19[&i0F ݁*cך]Kh]ơ#O}hѥdApF(gK-r1м&6$@,Hϩ^&F2%lbBF$c;d}p=+0]=o&B%"5y4 CBe짔7u(Dȧ`F+"ZjV7zN=TW #8\ NI.f.$}=UOE>OP[uNӏNF}>0L6M`"d(b1I61֛qhC#yg֨o-cx7ppxomymdLWqKy$;ZM؎6q{=kdqjVK^Gh-Ғ~TSu" ]Auk6 .d%Q1Y .BcJҢlv2<4vvPVzV 6[a e{b*^#)@BzףGoqksvɾEe49 Gַl44觎̳O%o ŏPKi7uo[Kmg7gO~=枉(*n!3dj,$/cl``[ Fel UV%8 8hʌ(ZZ(((`X`Њm\AZkG= 8-0lt]Ddu 0T=+6Úr.,t]:pj f9~ "-.[@1)YOl {U%ѵ+2'W Ebl3(0L56M56Q9\`i>  1}~38,$VwYRǔfQH;-Vo$6Wv+wN1޻em$y]FPrl5;X=^5jc}u8PKӦ[H#mm~zx0sڶ#ӯ|C-=,֖0I|C6!P6?1$r&ҴkW9\bV1 XDpРb28GnqE^%#-.$n;y' cPڽ|r^@K+rɌ% W)H4-_G?ԁLI`>1s>TyhduZkɨZDl9MΠ6uYj\B-eQzA^&<3cl@FKGN8)[M{{+fAĦ@=c4ju팖V%žou+VrR\|HWV%߅tapVO25pJ8_䴽 u/ tkK =m~Х0Nw7_s==*(<']6`ukkV'nۋ(g:Iz]Le*p=Ue%GAy+"[O$>hoc ,~4ԳW#KTx^I @GnOǎc+KTu-+J6fm2$1I,FřɜpXt(O ZYTkSnQ 8˩wYRk{u3EyRHnu߼F61IQJaw ?"JK v|j;\ͪ8i?. 0@# Ti5&ۼ; 1+?|?s[8:Oa9{ [0șT8=¬xJ!4}#N anf0ʅE F3-]ܾeңN[D6ӤG,oh0ᄎ`qg Hv;ȗf.l(-9Sż|rz:]gߙ15.J2ã5@Q@Q@r$dm55}2Eh&1F(ZE¶qczjՅH.<ܞtaQ8)oj;}NE5ѣ@q pAs^ŭ@_Cqn7dROX\Eqqgo4М$+2bFE%ΙayXN|%a&Wp#vJ!FW 0;|ƹCDMeqi\MݷH{m~}OҴ&7M[hV0ԅ4kD[v䁎vW7iw  p(%bء x=ɤ=!X:R GqX>#ԥzødn'xweo XpY#!*|DqqiSۦ.lڝ-yWi3=q|7Ė-&C1s f m)O P|?; `ҨiqA[&aX9s}it9ݎ gu#di#M)HYnqId)1 9U4kۿSBEw8G^F8QqeQ@yPI+#@P72u2 c3(PzQtejIˍIEYc2m)f%Sԯ|#e(tl#5Em>1QX#XG;>?"ע^M|j#q??ƽ ?޿Dȩ5v{ 6L.AX5jtpu)Vw`2j&jv-QUhHTUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh^QQM?dJ)hKt2E˦K8{,k$n+) n{kHbpWSLW;rr͎PY/q2M%q1#kQqwtTVCss"ȇ\x 61#qt?u}[ErxQH-[X71mG$N3ݜ(Y3USjK55ɰR0NOZ.5i+O]n5Ln$++ a{WGSj*[ǰT"6O=rƋJ+ռc{꺼+sx+9C ȭ!)Mgē݅pFmWwaYG0q{Eŕ#\1;| O\ڦ}^hJ]>dCmP09ӊu{,nפ[eb7 mۉ"]EcuA&}1am^=`}:K^W'eSa2rhXb(#Frx}r^"JQOqw2K$4m$df!@}*b/.DVGkےs`Ỵދ,ԯnH`608؄ xXngC y&E!X+uHv TeP;Hzjr_y]}<}*2<;P4Fi(7Ty,e{Wb]Ny 'nAZztwY'I,5ڋWLOZxNe8ҕ3*q,*$v%ىn"EU]eVbRC/"7h-o4f'*YS[ 3Ko̷[*Eog#`gEF *F5N)+rDˈ`ǭr:"wz59.n"io w QYzډc$6:\L҉A,hpp6=6{-"hvSH0Wr 3ޖŚND͍>qN +xO)N _iڑo=2[`>N#ҤcT&y"dAGh±}N1Z-i$s,N2኱6zZfm C!{DlUV#^a,>mx BPQ؍#' 9+<9Ϧiss u<$*UIy'{N=MkEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEɿҊJ+C0)CRQ۴]ɸE=sW4XBT`)Gg=qh/ vFafaC y.0\ö&Upsح~zy,2;iqaZ$ K*n;BgdD_܍Mx!6 $|:ꨢs =&]MծŨ ) FI9\q֠52-:K+. HݘE'Rx"!6kisope#❡E=JYZhdHET' Q޺(\|9yiimKv "T?.?zKmH_$3F[H(X0\cFMv4Qa`ҵ>inl[ԍrRu@Ё8:u\xGBVxLvGjC"K?6'5QE24Ax}=p^$=I.@EbYt8RO%[oʝH#oGZ5Pݚy*( ;nUSBG.1&o;^B3z#]]%8OKu֣<5"<;H3I`ty$C(6Ŵp\* lz|˂?*lm5/mBI \_M2l@׃/AKq)r-2d`9iX)S,#Uy`lE3Mo@5&,n-7unw2W^mOǐ`ha HoOB+b|?g_OyM4k5ԓX$XS-3j+ N$bKI:RTz@("oYM,ZuY.F261بltA7`E}*MB4/7of98(Uג-O9C 8;Tm-.캷{y .>({JwCpZ9LWvð1kiji 44M1]ǩ;@:U(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((EE1#(צ I :|֌{6e,`e.mhԟ1,w#VhK-4r AwBo =+!d?)[uq$%'9^YcFkpGyk6ܝF{Z7|//@闣Qҭ/ǟ W+亳קK}/OAyջFjt}5t.%p2*nbU 32pZm)i}\~Nd)c#'Ww\G9Y [=V;}nTTI7Z]u?8Oʷ:if.p7nV/MW3XdqpHao;~uOzb3߭ܒ4MSh}@"g2%A~0N96Vqi+ bMs*`9O X$%|.33ڋ8m|CjY;i6!"Kt<`V<>xwTP+{oy$qUT3=kյ)w{h mf&`n20qެϢYo۪0L(0h Ŵextt3H] $H'7Y}ڣ@HbU ^7\iE=!#o9Fbַi$3kAp>93CLG5K+˱ ˶r{zZky,n;u/]8!^AQxJKi-;aFm[ { \<b*p|2vQhgjIq!2˻JIaqj. ގ/}^>>$3y *H(ڬ6i%鹓rbdb6yiⶶ+m<$wP٫w" dcW{sx7+Egu VX|랝{_ XYv)-%л;H ?1#q%ݜ^BO/$!CBǿ^h ݑ!1{ҾouۻѪf4 &G$ywzPCmյ1)RA3dd@ fh kIpDy2yy69}A _P{+dKd7m[Ieݥҵ;LWͷ(_)=bKhpV6?q $Fiѯ~i\}>q+~EPIN?3tmn(L9ݜ_j?TT5(Y!BRMsE>/UO7_k}^J.yaF$p57o1]BRH ^0$Su> ]EZH%m?Ҟ/-Q=%A`IJ|JcEyL"^МOt;h~֭k[es*s;OopѬM߿#AYP2BAOED0%[4щVt0 #-QEQEQEQEQEQEQEQU +Ǖ-o-xN)C>ƀ,UaԬ.-丆X"$I"JGPH8-n+[S#Oy2 bmG~X<0 DG 82< TUv[ղk.79G>ak,] 5S+mTV,/'gU=0Ձ"4BPc#ppxǮ}(UX; l;gLeRl⦂{yY#`؎%ohwamXx[p #*xV-%+k'* bр<XK`5k Iw,w)dAA_٥Y= v`2=B$P(ckh"y e\7:=hj*Z 7yѦTu<EPEY5)5 4#@n$ESܯP9Ye)f$2 !GsOFu %IM帎9D.UH :EPEEM41 *+Pv 5-QEQP\ZF$,4z =su/3md)(=OҤXD4gsԓҀEEms\ZVƣbaaynbBj푏@<Ҁ,YyzvڭHJ\#:#@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?ԏHzb0J)J\ՐRQ@YC,7HZ"A~aǙf>xǾ+b7<ٝ34m6\#1cOgM $2}>xgՙRj/"Hfdϑam @O1^.Ɖdx`[ ?xq8(zE@זg s86d[ӎymC@>i,ָ}n;>9DGkẃ,G|b#]OO{7KfCLp*x!`v:\u͊x z,kwcl}v{ևfNJέq,2ynGL`^[1,|x<:T\C9E,rdp}޼{/ ^iy=ǘ!>4˛TbУ"gʏ e.:y5*+߱IY y :|qVdhaxY#8 yƵ 06KHGijS}ZL4!x:b>Ye 'f8+ǡj6V%役n>~i)ycmss}h9_P@J._˒{xV1 '$p0 jMt:tȂk9[wE Aϯ :J)3@(Q "}{ u5>IG/qO# )M\5, %s0UFwn8r_>o2RvB[)9"YWp }EQA_@fiLSPjM) oSy{|swN-.~;scȓvm9ٝ;c+&.!y[K>oʹ.!r 36@-{urxGvPI<93y^܍ N9_}ȫIvuxKX_:H$e9v# *~jW7Z=\ެ2*Dg ghڙA;Ѝ7m1Ӄ/\obO?Zƛqoij-bCap}hbۨ(ZDN#p$cr=EZykYKV>,gbB]>ɥkK;{vHbP8o->$Ɋ4Xcğ`(-u`O<-sh\,f } ӟItY2R2/hzmȸtY K#zhQc?숴yn ĥds=$wTjGFԮ _+6[k}̠H3ׂd&RY6d֒6ۡGl%qsޝ^$ngd$Ǚh=3 'ì hXKgޫqfKQFF=#W_0LPMX _ێ|8zIK{E vBYpyichxְ]9oU tk=B9.NKn "hڕIM#Uoj؎KNoD($ tri\It-K|=&m.Mӭn-QRaYAqހ9>Yr+cqjquwj%Hɽ@aCpqߥSo!Bm͐,e`@!Pp>R+Oh(}=m @nیgϵh,QB%E Jo]տ[h_O$ R _+._ZX&G ؅Cƌ0ĐʷJӖŬVZ6K@!_,qYZ,P-aCB1ZM{`w4k2䟼1CjS{e/د-o&0P<1\H~k Ӭmn%iIJRM2-'M\tHD!QCkKԽ{;vAg1)uJoMiN1I1A@tJէkA, ,cd$rd<}fZs8Y$~#^rXl;F {}6;f3ĥ#܊qP[xS6V 19.15ŨM"R{f[ yl.ў2W{e]ӈ#ж3Q 7 W,Z,lmĖI&9~u Nx:W]Ul,TȆ0`tb ( -6io?h`~`G13,JV7*7 88sFhG,HeO29be8*q)vIŦrE#C02[KĖz]A,wyHIn[tb@g9pzdҐ˦ik% y^! s9 ?4mʤ :ՠA ǣ"i"iVS#!qy?0(ͽVww*:#~՜X]Kk ;% < (sC6ZVcf91®P{:RcE9U%pȨm4M'Oϲ쭦j)(T}{  K{t+Ua  I~]6s]>aBֈ< o]Tq+/L!7L?*4_6N [r'nO&^xl#=b5Af; mR+9D2#Gi,4習"?hR?fxY4N8m.-˥k0KW(vd {VuEZ.ŅǓ6~ޮd ҁ'=F[ǵz )4Z}7E=vF#R\4r8,`hOcYjvMoy.oǕ-23lkW2m.e[_%$#LVΣzG4Zo *Y{~`0G'u9imh./4wˎ16_/cnW=* e?cXe!nA$Wkicob{ҼcOPI) T2x[RY^K3@Zj7 qTM]bZuociO*ۉy@FSR@a#7veUz<#XZI%)۳;VmgW6b䯝8KLnV.V2p ?^jZ_J:Ma]ʶ5;FSV 18 rKڭKs_+=;W7S{Prg<6*A'XTƴ>,X8c1ʜ`c\vhF"T[!H8_5u'Re:*^&oͪ߸ [m]R.i$dCyZ Se!r1ӓo/'0Q@IXޙJ*p]5T]o[:#mFA,|;e:2f,2F ⓙ弒aLc mm 8yOѬ𞋦\_ZiGh128l½| Nv:T1-ﯚ:?ζ͋M:}<̑B|u~ۚy&#!]G6s@k3IUoo}7u(Dȧl\PSܦSǪa UD|.[ {aj}JYY$"V. /1] c/(b1\杯w&\jY5Į>mʪz&ݢY3+q`7 =N8,-؁4Q'9ZPէΦϫ,}jL,12z h׍- N s`q@]Q@Q@Q@Q@Q@Q@WyR #E@`0H+ua }qZYi3m{U@Զ:P}cqo%7A!IT)vװ,O }"p~q\m=zS?옴y'$dOjQ:>w;ClQeب[ L࿵vg-Eu es郟55)&uhgF_]Ҹ?ࡢc-wZiqy46Gqɖ [ o+zct;qo7kzMwe 2 zdڥqu&ILQX㝭ߵq=ͥr\3kWI[s}/~{VwZd:}5dG[{pQF#HC6aohs_[Gl3ʡ=0Ձ"4BPc#p`xǮkiVڬOs ]( t8ToH!B"%l*EvqV2ٵwj:ʥ:峊 ẁ'9bq6 =\-^k%[h^`X%KpJ]EկIrf%{K(dH!xж?1$rZ`4汷lfRC[p #TާXH[lFi,Z jKX82ѮR2F㷒~:Uaphr܆c i0C3@ici3.#b3H Ua,uܰvz\DvF_$kbWs#m#Y:ͨRʢ5I۸(Ҫma%s7q0 y5rǚ}唖^Qf+<.͌Pºk)Sh7ZO,cѹFOiSJ/\.ǁ JK#֬էn!&;ŀ T]R“ĩ#Azz^oeh7::VdCL2&~selSK%4'O cnf@ʅU`o 1wz`B.DIJcN9ҵ+,V7yWl5͠F#|rz((* KWM @Oz2k hRx9A(b\t=CTwn.2FE]i#2;Kk`<-uԬxZK8 nt:ŭB_C=| 0{.Ih\e$+b85,0/-SH\HDzx<J+!^W 0;rkwt[`Ds-W8q?7mp{mz&K)pGPTUaumw,2YCHlf7l#=(((((((((((((((((((((((((((((((((((((((((((((((((((((((((EE0JZhKЁsIL@u=B]4Ep`2YEæK;>9qױ"Kr=Z˴9xiGeeN+K0O\ &qOBF^9b&);Gjٹ״KRv [`0OzjVRMF\#CKy)fn;KRYRK(@PF;_g7[ӤuOpm&PH+drzU`{ۂj6ֱeي/@PXۇڥ.sa]kV[-#7 [r.ihG@ӭ"[Y^&YXe2A[@\Qi"ܩ{fٲ0v|Os"E62j7 cbu$goFF5{VU l4 4RQ@Ӭ;p2O@I j}umK`߿ Ϩ#Wy~bfȤ#H c֪iVwڜgF]#pppBǑvҐΒQ@mA'o >yb}_RMul.%i6; ':j?E̗PU )pa]7SZ\.fB3YN(:?"t&fG@Ú3YOp4)A=~?)oF?ƾCG,L#&3eN7蝎ZmqI%1d` 鞌@iHU{*0>P8\Q>?ƏEȩ5/s[rм&6$@,Hϩ^*I2%X =+[Y?]k5oop%Mۗ ,Ooz41+dҷT)㊓%MuDU՝Ė6gSQR_XE,bA:NN>e9TWҟ^G$Lc2ĎcmYA2='zм9iApKvdגy瓁{VPiZp7B%Xt9sO6єBcPrI'ԔPEPEPEPEPEPEP:,X`zVutM:\X}$=F@iQ@[EҞK6,F6s9Kf$vif$L8HiV{iy]Fi,<sS5-[Bnv1Gn i֏q%Ȃ$xiHٱ*]2NmUV("YAq޴h 4h͚i j_0-vc<}"TQBQ@KɬZ6K@!]H,V(bЈ!Qdp OOZekq,v/#U@ɦEi Z}b"}sw5re<6,hx+i/zgnj0lf@bx_Mi&̸Lg8pw5Eb]2զ[aqU"{q֫K? JMVE+ H|YǓVRYg lY_JL ŋL ݭեmF>V~y6ݮf4]wP *QEo5]m[QCfh |1Kǧ&(ѷ9>k,V579aY͜ZP>-KjorQdf(((XYkmijZ}>xhcH#"MxI,JMwp#vJET*7NG;mXR ӿ썪Zd]A c֬@ д[ƼҬabY=I`3EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEɿ0Q@xf3nh;4בcBTrK^/f!Ӡ2hLLoKΌP!v VH.hQxPOˌvqԚ>KG s{Ќ *+z =B/$UҪRL|V֚,-kVo+~y6yր/Q@9*!skQ4fPHb7$댂3hZ( ( ( ( ( ( ( (څʖ'lC@hV\C{m,$%VT#$ Tvqiq-déMEVMB['m1icp6w#?JDԬd"yc }vqP*_٭5rd ⣟Ux.)+ K9*P*ڞof[El3ʪbpj!(u18*zM{ڦwNE\qSAq = eaGZϷ״{լfBFekjo㨠j ҳ4zH)ZK&Y[{7Լf ?Cn<kqog.48 WvIm݁9zsv%mm =j4+ǩ#$LZ_>H##eadY^ۑqcֳ@ TF4УivJ"{dHک(Ԏ]?:Whm, a@gٮɴM%줲m.ɭ$mBJ;O H46I3,{ghO@Ye@";V"n<4"zFQ`o!7C[q.hQ 5 g9R`7HVsޫw6zr]&4ZD+nlF߹ձ֗{^89oY턈QF#Hm$[ėo){63UL]sZZZ1²6:s}VIէJ{JFBmAۛ!Y>B|W\c6Qz l7tݷjXXJ#h@8%gbо0I,NF Wc85kR]dMc) a n95ծ-XlBYϪ XZ#c*;u==h-,55,/ih1te#q?xc*m _^[L`->xbL7 עAXKqogo 8Y dZNB]7@;#x\MB${f'Ynu`qfHXV?k*?'gn͊)4>YKgR4*Z}zvvjbR=c",+ c%>c>92&OpKY.YNhI4%U4y*;*8pI(8G 9,6/iYvm1N6vs[gJG)A =#_mQJcA#&s]Xۉ-9$Ms> 5+3Al@BӌtYة[;H-ձ a*QEx-tp2LWOT K-6ϗMqgw#NٵmV! (AVn.N=X+k7IibMd;d /<;>;@Ҥ"oQ b#P OD5&@t>*8쫵GCl5Y\G-W8q?7m(lqߟN#HB$hUT PgXXM` cր<]C^M=P2ZEnۨě<(ʂ3޽bmniErORX օQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVR?ޢ#(Fi)sZ3Mf4;4f3@(٣44f4f\sE;4fFi u.ifP1sKnisH.h&h4Rf;:j}{ uA'o"ocpet:TA?⫰Ե6u$&ϓzX:/qq9f©߁VI![ nD݉DݢF8/;}2TU1.J~8|.椯o_ԷEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(V[#*)9px5444@њvh4Rff1sFi(1RRIE!4Rh3I\sE&h I)4j >OPI)Ͷj#\kmn Zз<@k2ճD!$ N>g^{{KI ?T'W5-Քy,+ux>jo4ګtE:uito90ΥKMRQ/M-CI5ڼÖWn-%i*cL N9|inupl2A u q~tz孭K5E#8>r5}ޒW&xaC`I8 nڴVSmg4*19FfbCsNA7lj4[F†yq1Ӛ~kc/4/;=mwVDTKKg$.rv>P3on+!ŵԚtikd''U( ch%Cryӟ4E ɥܪ$jlKaUhvg zW'u6Wf,b avFrWw9' oLFt={kKxH^c;RFGJn';Bqon6@N=TD͍Mmp@m˰k/E?/bXhƱ6{0 ^%ӥIҬy8_1F'ү5#L:`J4Fg22!T(c {r+CKKi-ܡUXHw1}Ez]Cq!%ECu?(Jװ$fcgpiQww9'#v_yFk+Yre.9ާwH5ya[yY ib,p? FB`D͹ K6XT9Ԭekm3uJ ug4uOo,r+$lX{ָ[45_Kdн%pK:% ^ͨJ֖PɫC6!PlbH[iWcov@G\9Oogw$]4V9>C2XԗT7Sqe\<єdo$tW9/lVݹ +af;wH'X5 Vf]FgA_٥Y t`2=B"i%H#uPF GQֳod'W"fuQq Dj qPTIuo-̶S,JnujZN{'Ωd&O14*ιܣ0r;`՚4hm>uFC3,`rW^ǁ@5,$Ż;~ݞ}(+V-f "mqmZ]#0ZU'a==|Maj7Kmd%ȵDv XyL#owY5 )/K4 uSܯP9r!ԣ}F/Oj)faIK ?{s%q1y;e .Q"VX4lIqRFLJČ Bx*3Y,RJn 06$ uxNtVyq^A,7ݐiKU}fUp<ԨZ^FiŴ42g?{p gE6EU lwMհFxrqM sFBBʊ$d;pAjZ$k&Wk- υu GN1Һ(*k4uq X(i\($@XˤИˁ>jdN(v{;Ye8+9 //4cq}ue@ ⹯ >'`KfuHbL D2v V іX̱J`PҦF2 2VuTwӵ [Ōe)>I)ʏ¹/}EMgOPI(|65q:ZD,BUݸM4h51v@Heu;! / I~׽ⰱ:;Caq2(niLSPjM) oR^玠\=p[#5 / I~zNa;6ɮʘ j[35iʹxR8W@p4+ǩ#$GE| +EӴ8fM34q.x 8=SA.w瞟*ҬQ@&m/㼕 K[x(K? U4iLkQrαy1LUC>U=+Z̏EuëIwu4r R@Py(IoM+Ut}GLdFY AU(+kk{;t( a#@jUl-2J eNG̤Wq<%X6<+m2cw涍Mk <vf;[X-%!@[1Y}coc.I Щ1くJEUL mCħM~:}VvGtm,AQY+ >UYۅILKK8l^MY((GEC+ # Jγ\ O["0*(hSIfeZfrW'=t#LDNӭ,Ęx=靠g?]) */`< K(T-!>jf{żkhM.՘7 =VM:$Do){63P6KZ:}խK ]h8#;֍M"mK@nیgϵ_XXJ#h@8(zz5ZFh+W B4J#Q۩S@n%Y$q*H4ȴ:8O\dOnzƮQ@_Lxg8m:VFs ՚(IR$Y% ʠ `d㊒}::[G#ʌ#w099=NOD>pͧZr6L |ߍ]+YƱZArەf8ޒm:xgYaU#Ĭ#¬@KԽ{H՜ ͢&pŦCĤ aϠPwؽ hSgуM1N6qBr)@=@8MEW[ 5kմ]ӈ= c4 1hւlC卍r9$j^JZnOjEQEU-CG5o/GNfqn/=>;A؁}gjdp=YGm Pܠ`=a4(""(UU:*#jcW/mu1i JxF<%)V;(N<)Ci5ETӵ]?Wk6bH$2;ΛgXjw),n#E?q?ϡ*zM{ڦwNE\qSAq = eaGZϷ״{լfBFeki3æZGk ҙ8qPG[XZOu4Q,q;*y=(ƚO[E)R;;Y7ǰEf\rP -lnӣK[>p P@JusiwicKq$*XZkM{Qr$^LfY%P;^kI-KE{:dhg%wp#x VD2I5{k(xJd29VeSu##]x]O2E|Uʖ EMOh^iiwV|l38Q&V}qk4 w "EF`v+6199'\ޣwkzjw0h0@3Iڠ]m%B4Vb>T*82Mr"oh-P[t- ;;`opսRPu[Թ2(\#5ZV06v9%HBNDU`HϽWnO5T*c`pwxը-gga'䚖 ( ( ( ( (` B+͵qi_ V:D0l/:>ӟItY2R2/hzmȸtY K#zhQc?숴yn ĥds=$wTjGFԮ _+6[k}̠H3ׂd&RY6d֒6ۡGl%qsޝ^$ngd$Ǚh=3 'ì hXKgޫqfKQFF=#W_0LPMX _ێ|8zIK{E vBYpyichxְ]9oU tk=B9.NKn "hڕIM#Uoj؎KNoD($ tri\It-K|=&m.Mӭn-QRaYAqހ9>Yr+cqjquwj%Hɽ@aCpqߥSo!Bm͐,e`@!Pp>R+Oh(}=m @nیgϵh,QB%E Jo]տ[h_O$ R _+._ZX&G ؅Cƌ0ĐʷJӖŬVZ6K@!_,qYZ,P-aCB1ZM{`w4k2䟼1CjS{e/د-o&0P<1\H~k Ӭmn%iIJRM2-'M\tHD!QCkKԽ{;vAg1)uJoMiN1I1A@tJէkA, ,cd$rd<}fZs8Y$~#^rXl;F {}6;f3ĥ#܊qP[xS6V 19.15ŨM"R{f[ yl.ў2W{e]ӈ#ж3Q 7 W,Z,lmĖI&9~u Nx:W]Ul,TȆ0`tb ( 5:D4.A"Vt- *1L/l7~I$Stמ-:RC+m Kcue=G$K> A?^Zn;x/v+NZn/]L3FTk o ťRn2[{IvA 2H$}1N{s_$}Z~a[Ɲy* HC`dw%cwykkrf9?ϸ\myo/wɞ5f+x~PX.x9RN;F%a>vY 3 NQY䓐y M!цm𡼯5s~\g;3t柤kG B0G"[iմz?i.ǰG, ݏ; Hm1mgeu&Z @ n Pܞp4Ƈm;D,iw*=I1[PYunšJdG\6!|O_iD'[c) u]0+ծ AGo4Z[_عT4-[4щKeQ;MB]ԎhwwIh]$`g'/; /#ּ*kF*orxhu o M/򣸍`N8QՁ<Ƨ۩s,>`ٰq2J{(f((((((#-=t($hSK|mPbpj!(u108o^I'r&wby5SQ-ZkfrS9M=W9o/ [[ Bv8$dz试>hv 鶱C4f1T/Gh#%ԂYw0U\UzU(mwicKq`Gj;IMj.Y/&3=ԳyiJv;Gʽ=kQ@豮ui.cxB!V*X( %5}I$ic#=TP;}+NζaUln-,VRrjJ=LGZ# qѺujV@0LdP S}Z(((((((Ddu 0T=+:ú&p.,t}>pAl f -Oe%iMk#ox Q9\`3I ;NcXcvduv4T4.أPrn7HVcޣ7\TPY4GA]f?k@.k\Vw,vZ4Prh4vf4/nn1>}cb*( LS dKaj-%\bV1 hDp(b28GnOEVO{{;xfdĪ d"Ҵ->1q>Ty뻎EU}2YKgR4JZ<t•[׳kYJd63VhH"Y%Hd#*\ߎ*J(Hin*0Î9>hc6hM90/Gq~5vwagyi+nU0PzI{+yeT'? EW{ )/R kVs.62(k 6x c%s>Q@Kb5MCF {}6ǵ8[5W#!((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((4Q{ SGҖ3JZQE-QE Z))hhKIK@–ZZJ)RR 撊Z)(4f4Rf IE%=yMOARP( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (+^CEGH?^N>QE-Q@Š( (E%- JZZ))hJ) Z)(\QJZJ)(i(GS?S*JEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPk#hH{h?t})iJZ1h-%-QE (QE Z))ifZ\sK@ E%@QI@ IE&hRP!i(z=5>:yQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVR?ޢ#)GҖ~R֦aEPIK@ E%-QE ZJZ((\IE J(f4%4QIE 3IEf(="yMOARP( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (+^QEy_)kS (QE-Q@ E%-QE 3ERQ@ E&h J(h% 3Fh4(3@%PS?S*JEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPk#)o?ԏ3GJZAGҖ2QERQ@ E&ih-%RfIEJ(h%3EbIE.h%(QE%QI@MOAN(uQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?ԏHzCIC(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((yEGR-Mik%܂8$V<zVfEO?^/y?]Ǣ?O?^1譏/y?a9ע,z+c _ 3<z?tfFi3[_sEYV<z?t;34f?O?^1譏/y?`9ץt1y??\,z+c^yz.;V<??\,cfף^jJפץpI[_JM,2q `SS?S) uQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?ԏHzC9ME\O.2êj8$Ý<qҩJ("if#ygv4x>o߲G&nW 6T'$c {5dcLgֲ:M$>mk'Oc_͸y#L{9E\Z6AGY]20ʲ=ExUե-~X\n?75^Me*ֶJ4-vd>$'#9\Bf02T 73RW:5֣[/5N ڥ6Nw%B>S<נPEPEPEPEPEPEPEPEPEPEPTMbXhoѶiׯN˹X!w gjiX3ק@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@ y/קQ@OȀQ~^QHg )(v0*YXdt Գx_$֨d)3yֳ|jZdW+"d?u:IYxr] K8MrI;[MQW󬲡+n>sI6xH|3yI ?e[P+'sNNxi&_ j-bpX#.Gk@G 7zmm.ȣm]$VLJk=)5 x#$ *d| lv4 LI Kp8=]Wrk((((((((((d66$}w(;Xt#]_As!Wk/Zmp;s@mQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ ~VG5I4"[p3p.?Vr3Hm]?Y7Z`*vF6jd=+4.bEP2B(̋[ϵϵP0}PjQ@ jyڊ(ayڊ(aO0 <Q@ނ1P7oAEy(QEcz <Q@ 淠oAEy([QEkz <Q@ނ5P淠oAEy([QEkz <Q@ނ5P淠oAEy([QEkz <Q@ނ5P淠oAEy([QEkz <Q@ނ5P淠oAEy([QE/ނ1P7oAEy(QEcz <Q@ނ1P7oAEy(_0Qj(>yڊ(ayڊ(ayڊ(}} ڍڊ(}} ڍڊ(}} ڍڊ(}} ڍڊ(}} ڍڊ(}wj(yy7j7j(yy7j׵[68\'8l5Ms54]Rh endstream endobj 32 0 obj <> /Font <> /XObject <>>> /Type /Page>> endobj 33 0 obj <> stream xYۊ7}?sں% eL6a7/*i.,vkJ*)+^Єn}+~ԕ*8m4ȜwGէ?FEP!lHR@uc3r8C!Naaa:GգB0"U C=m.sMm:B-#-Qa2]s=|g793a]{#ȃ bS #^^cfI XJ6[Ք^*/բ"¡hujoV0ڋ@jCіݛ Mx d+?nty {l!`c3'iU|”#Dޑ-ZOhE[87o{VL8 ry=GOfwM[$+ { [U0+QjTiS]5d{iИ}p(uuu6-?Zɕ^,ٞ%K%v9ܽ5'և+ ul #㙐8`!#tK^RKWgRMϜl/d& ]piQM"T@RQuS)-X/p(XB2=uw_ȲrF9p`6%bˍǥRJBsXu0/Yȴ9c?W,ݛA3j~4d̩߄ M=C<ج⠸T [sFsש>lAdvOgB[<#51|Y#~:IYpuk" 럻ϣv%;T9/HS@Sx8= bq~ 7,&q-y M!FsƅA+ŰϨU5+ #듅e d#yrݵ xd:rwrJ_x^_B^nb!㘫ֆ͋,XשYI-V?r(&ԓDv G?X& endstream endobj 38 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?I/ jͨKYÓ 8^x:#7{O 2 IV?Xx1y+Mq;zϨ?ҜqnB ^F[9%CyPRF[xۓC=). <s/\z01N-.%bہDRNpkc#6!6 ّS~Py#gn."B>lҽb0pƩ\/ޣ(2W["Zo2 `t-zP<4*eUbi r9^E_fGyt e.bE2qsfK^yUSX_k??*D^EZ}Z%<C=թ(<=A@UQՠyHMoV eSɑAyU2 ze&lk *k ?*=w^EZ}^'<?Uz}jb,S]yT)<:1G֦Wk??*E5^1GfWk??*E5^E?>?Uk??*N>0O,O]{_DR>Uo^EY}^''<?Uz}fax[@T'=Uz}fax[@cO]yWQGfW??*=wWQGfW??*DO*J>0O-O]{O]{*(<=wcO]{*(<=wG"zR>?*DUz}fax[@T'=UTQyo"z=wWQGfW??*D^EY}^''=U??*(<=wG"zR>??Uz}fax[@T'=UTQyo"zQ@UTQyo"zQ@cԨ3DG"z^E/ϰ}^''<ODO*NY`O-O]{?Uz}fax[@S=wWQGfW??*=wSR>?*E5Rϰ}^''<_D^E/>U'"zS>?U/"z^EY`O,O]yQ@WGfW??*D^EY}^''<UTQyg"zO]yUTQyo"z=wWQGg>U'"zS>?U/"zWQO3D=wcԨ3WoG"zWQGfWoU??*(<=wcO]{*(yo"zI@SS>U??*J>0O,O]yT'<:(<=wcO]{*(<=wG"zS>U??*)}fax[@Q@UTS<=wK@WQGfWoG"zWQGfW???Uz}fax[@S=w^EY}^''<O]yUTQyg"zG"S>U'"S>?Uk??*N>0O,S]yT'<0O,S]yT)<0O,S]yT)<,Q>0O-S]yT)<*(<MwQ@cԨ3E5G"R>Uk??*J>0O-S]yTk??*F>0O-S\yT)<Ԩ3E5G"O*J>0O-S\yQ@cԨY}^')<S\yW>0O.S\yQ@SQ>˿T)<h3E5G"Q>˿?T)<h3E5Uk??(<MsQ@SQ>˿?T)<(<MsQ@W▏>/4s5[wn?8>s^/^t̖s0#yr˲= 4푊Jè.*F  vh??z6B#Uɖ;Kqo\|ǖ>=N{A]^~ _1ׯzRnW-N#Pڙ988xSH ]BC>WCT_V{]wFVaEU((J)hQKLPE ((QEQE1 \QERPbQ(1E-R@ Q1F(J)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)h&(PbR@ E-R@ E-(b(1E-R@ E.(%b((^_,C[?knv;tQʌݼ)\v7(b (wRMاYS1xhfPWQE(Ilm5]%7)50K< _TzA͞im:[$00 2)ƕc~( y[}r$lF7?0AM&]j1IE'k&A.ٱz֊RAC(4c,lhb,UcFv2fTej΢ w7aR{ 5YHy?ZϧXNIiecQEP(aEb ( ( ( PQEQE(PE!(Z((Z((.(&(;bbLQZ(1E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-R@ E-bLQZ(1F)hSNN&((.(&)1N&(R@ E-bLQKE%PQF((( v\-v>Jw}w~.!dVQ*BvW RbbZ((Z(6A,"fR\{TRiщ//9Jhģ&FʎկE+!J)h!*͌Wrnd3v$wnTPfߥImE~'6hyϏ={/>=?V>3*}?ubz m'ҽ7 'GWNǘbz^'GW,yQ'U bzyUz(ǘbz^'GW,yQ'U bzyUz(ǘT-ѡߵN:}'`zi"0OOEiMhɾtQ{4s7_&x5Gad_}Ɠ"x5QGad_}Ɨ&x5Gad}ƏOEч"9}]E{F/>GEkjÑEhȾuQXr#Ⱦ_񮢊=f_"x4d_}ƺ(=9}GEkj٣Ⱦ/Ekjd^}ƏOEՇG1{]={VMhɾtQX{4s7_&x5Gad}Ə/OEՇG1{<?o>]={VMio>]5{VMhɽtQX{4s?7_&x5QGadyƏ/MEՇG3{]={VMǗhɾ?tQX{4s7_&x5Gad}ƏOEՇG1}]={VMhȾtQX{4s_ȾuQX{4r_"x5QGad_}ƏQEՇG-}]M٣ǿ_񮦊=٣ǿ_񮦊=٣Ǿ'=kk h>G=kk h>G=kk h>G=kk h>G=kk h>G=kk h>G=kk h>G=kk h>G=kk h>G=kk h>G=kk h>^c[_@&6qxl$޺0c LK#my? l'ҽR (N((((r n# gZ((c*RlzmKT#Ă}+b#'h'j6]>" ^X@;Fԓ#4$$Ecrf^`Q_<~5 fAaY]>plOѲO id׫}j֊HiPcճ+yx"IVOѲO[-ZKm0>X*0  ݴ`efTU$~;$$cIYI[Sm,jUEmq9O./VwO%2y6 0[=y olzlz㵏}3j֞Me!m<=HOˈಝ ƙΣh(Gdd{ivi4) τY媳= <ϡ#][ڶٚLzg8'hGddxT7GN\Ua= oη6΋" #(U`>POѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEGOѲOԔP{$$IEG2*Jo-RP2JCbJGn+[Aks}v3CjJг1 ɠ o5QJx5 $ʞ0kz ="(k{~xJR&ԯ!ʨ1$y Q˙sxnC*.FAfPUF<_ߥk{K鶆Hf ÔA#*Inn><)_l.rGp3Ҁ5G+ohUik{L xi)QM@8>{-nP}ii((Kzf;w$ᣈO!YV8`+rH҉G[ c$*@̶i#ڭ ]?=hVQ6c&%c~#7׀{9u%N3-xJ4kck {YwI ,T}܏m͏L15IƣkH۳`R` <Qk}qV3t+ ( *Z̈́Ls1K:yl!8b6H#p6p^D۾Y"pǦQ>~5VULj).+Am"q"Ƭ́RO'4Ed%4k#%S$[ҦZVL#k#yS€4(GY s! 1mFFvcS֟jڧ~\yDNYvQ_ր.X='WL`x͑UUAdjkiVBβs`zlҀ5h3 #FTpHcceu 2A"(((y⶷y8bBp@'@XH8Y-3Zy-o^Iu`sb/o.?4fO#ey?c?_ᔵyut4x/ )_j{Wus×"M.-,f2NUx>Q99=tQ@E#NyQˎ!_jZ(?#(( mwj(aJ;+=9? Mpl `Z((^?ֱ]\ v7c*r GŸJ-5+coyM!GBPG2bjV}9##6{Vb%pJv\I,݉B:J؅ PHP>€8鉪Mm= kE%b-P=0FsԞo hV'R~J|r>zKG%I幌)c\0؊e=6姆MAؼ.TQx`FI若idKaj-!]T[kAo pă jT{Ҁ<.)6Ƣs$Vڀ";F'|ZĚrciv&sѨy! cX`Wmu{y֖b-;Ȅ*嘄v%bF܎kJ{q!r-0ucszr^D/CiEڨ#󋷻U}Zyc':.b*J7>p<52\HgY 9<皧Y-^.@ Wj$X bH9;H,,ⴶG*4tQEQEQEQEQEk&F`cnxӶ1kE PXH՘e?RI4GCxanzv99h#(F4C#or>Ÿ@bV)ȿՊ}QEq.7u=^Wbi}_ٳ67y6q5CwjZ}7پ+;{9]3V漎rD Eu,K$`  D.59'u[V76f6F?zPpmJ0kW~Y<_8Zk߳jmBcc7\nOSVFao/#$IIͦ_ٞ :)~(MNF/^m}.6_åP7w~u<3͐38Dt+O (8n\\,1Z-OVr [n쑽>UNu K;hgY%KX' +]M88I{%Z[ܪI`H;HqQiz>=i/#J,O'(xeP֯Lܪ}x?ym]bGO==_PȷbK\le\m* 9hÚxג}V='P\Y[63gp~bsZ6si^}I1o"g9PVE=:K;Iʓom;FG]wwSK].=8D6yF>`r~$$%䲗MD0ywQ1㚏6V4^ kgN6`){|k767uĒA$o,PO9Zt6^rחhq Sl hΑ[?iZs@`dpbp1|Su ØK)G"Y<#A$Waq]6QKo~U 8tOV0hhѼV4ܹ$z\[I%uh4̐FfRrÆgQjAV=*<߼%RyI=:ڴ@Q@ u,  1s'%?~^sZ(P  `EbD{-Լ7GDt{o7ajŋ(m8~bO5pW Z )eXGk-n"Z~ԸcX>NGȪ8L{^&ft2[ۤlW쳜d@x4'[{^1xRq 9RwvN9+~,o^I{5xŏ?7N|WQ#ey? k'ҽgC 0+ ( ( ( ( ( ( (I x~IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTP{__IEGOQOTPxy椢HbWUEʂOךINRSɭ8Tqv=oI'Nkhi32՟ٿ BzO!?Ƽf?i3Y*=oI'Nkh0՟ٿ BzO!?Ƽf?i3Y*=oI'Nkh0՟ۭнS(cp~U5y&ʱ8ucVwLpofsvױ׎|VCzJ 7fЖסWF#0 2 E{4-D\B8"[[Cgmb8UZ+n˨6bd_^$1Pry?vvЙ!׬rq6{ =-{l̓d7ylYī_RzU_o.pRvΟhP(Hw'WMMicGDwUg8@N g׀ir98+/4ء 8.kwKxw# =I2e /P" C..IGHjBvzp3ӥGeKO0hpOp7g%`zFz(d9; _IxgXb73€G=溘XHaQB }Ұ\(b (-%b1E0 ( |-w:5ź 㟘Cں )5qܯiciaxR}p;*zZ)bDg߮z6)_Q߻;.zv\aq & pN:rEn{P7?zs?W( I,ծm_K0ZQ$lHi~&asbe%H@r9ol[F%~yNG~eg'Iqe.#"gv([<ɠE|uVKq#(ɀ4Ü_dn!\]^K9446I{*($\ bx?[o4Z8F/89yh\Cɬ2Z2Ƃ\7m\#_GukRs1is-iB"y2m]Up,~& cs'[oqw@Q@- o*z((GMoF%6V ̀4Wp\#Hþ)/SYբle2o^⸜yS;HeF@WXƷkiy"%bOһ}VZV(fLFc@u( MynkZi{Kwc5ޕη$*$S~qڨ:WxDt,A7#LYH΃'%9<nEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQET7֦n?ՏQ7|q7;7k5n5}*ѭm6@з #+3^u>+a2 RKvs{PAjwZwGJLA"1K!fǞ2{t9N4RT(|E::jvR[cd܀ٌ+{H_/fhDvUT9Ҁ7P2-WaM{ϳ@%TFB[XwhЮ_0hű,/9S E}|;~^>nJ/%._2o DHibwL-$QyʻP.=@85zmx);[F.2,1)8U+mb-9,6)$#ۗziAW;_kK-o(~)`Gx-S4q JuzMn[t۴r\cwl5 :($.w:oS??oSٹr^Ɵ:6>wOl)m3@4 Q=*Ɲ-ٷID@FBXڍ-`_n2>VS皧hA=HOp3רiʥIA@I&+(.u3 5rXbbЃqY]̒GJ;u{yVd(B[M{is[5%F"]ĐF=9kw}#D04r\c1̗/_?̏cOnF!;Ѕ{-x։!;Ѕ{-x#z9W/P#Uy'Uj4޼NW^-z+!ٿ%AGaC (6)KC`~B\zE}e yQδo>Hc!d f6s: =~gzu\RA,nWq5xE nb>4.y \»O@c2 m#Y#[LA~$_luѺ9pϨ&w̎#I6 BH9⹋tG+ByopFOخH,s^cOa.rH^I]\=2zɓ%Y ݬ|MŒɇ'-T5NƪHK{+)9{o&kL+!T8ҠC a[qeJeLJ47ɴ{i@ɰdB1D梵ҵ;ɣtR\5~SszĊu{緶6yAogB =KsZCgklivx !N SC5\ޖ+-sLs\xyc@ey$wضu=Z-2·n 3LZv`ȸƳu;, {N7/)k!6QE1Q@QL((QE%PQKEh1[3oW=OQPXQEQEQE5^2#Ztj>Ξ@?gOVZf=ZPj*ը:zV=ZP[S*Z((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((?ՏKH@#dP*}=Z+QV~ΞGըg}=Z+QV~ΞGըZQ(jS xc8>? vwe#drU9:ȻPsY*kbVE rϮ*Λ0I8ɓp+^L!6y',@s~n0ֹ+EӓcuݕЂ,QFz6YULd}8S'CNyb:.佝x;LN=8>XH<`}>Xm޵5QG4a3Z |.}N;{檯t1[rcc1R^qT`LmJ}NxbHI@1`z^3|AIv7Y!F#"t Az҇&H\ Ju-Y"QF(AEPES(((QF)h(&((KDgm53n?Cbu"^gy=~U-SVt۫&;hY0[j TZ<_h<_i!]C)VGc۶IIyYtJ3@?gygy2Oe/$p ѼckluKWMm/8 @;PkGkX>1ҭ[PzosnȉO8kCOYtRI ;I[<_h<_jJ(?<֏<֤#<_h<_jJ(?<֏<֤#<_h<_jJ(?<֏<֤#<_h<_jJ(?<֏<֤#<_h<_jJ(?<֏<֤#<_h<_jJd.d78('JO<֏<֫OZ[^mxF#HI>CL񆃫}Qr}a$Ș1[R{ ]#\F>ÚeCP0\@1VPÏ\?kGkX*_o+V2KslqԺ׉)6tvʡW%H'=4xxt"ඉ3J6-C0# ՠZ>Z55(Z>Z55)ȝS'#h>Z>Z{:0QN+>=nMb},KRsXyS€./}/OR촥d!g/݀} yzӴVUWKm(.ߘ?ZxxWV6/{1-#pF~}պ55-5/nm-YJ!#qEq^U55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(Z>Z55(ZJődO ګž)zՃ)ϊK^-zp[>3 QEŴ7Qs&893!ihU v'$;RE<HϦ?5mUGnަR|֒}6MNqP\iVWWQ\ib$8#, H2q֬(wTg3n+%KUnnD3ga@Sۿi6-ondmb<gYDwaC̟^tXx^x;!>V읻o,-n,C22[p8fl[[dKۇ! p #Ӈ4ś!d6Hܰw18u` FE->tNbKTxA`5B?wSiW 4M 0v!dtAju(j9\TXIsv w${;KԣE-zG׵;YV)c;./ט~z$fV hѼN'}| H,j+>#T}>͡w;$J2@`HȮQ( &)h(QEQEQEih1[o4_VC\>#hl[_>HtmMyiӈxp|) Gr7ֻYVeg?vzs>Ք4{ ?PCŬ(MnTBd(kGwu!#SbPϻ,Ks55I5 4IsHyۻ昞MOC{;bee<rYZn7;?!ZZA!v2N)t^gNfvlZoB,#$WcivΛ-ѐC!R'(+#+:/ipjwfPeV^VFaя#ހ<ʸrs-f&8lp"t 3]_#m \|>̬uӶ Ie{7b*L\jL`Ҭ!,II& $@?\(((((((((;WmLZ"3lk+ncT5M*I<^;h_?r9VTVZd(c48ʨJ^/a.#6<2y+C ˘H.t#W ӦW{m *1FB o'qZ_]g8 )"|1JY]ڛvo"0I,H; @>X'ZԴ^͢Nѩeg~،q[Ri?)ad,V"ab*r3t#: 6ʶ1JZ %ZlUx-mUud }("-r?x`ꗚt}mkj0oKԴW_yD!b]ִh(((Y?AZU'3d~@Jg,5&_Z5+{l0$á[SI[h:UkvDP85 ,&K41r=oo 1p =n_c@ hɪ뉨 HgE+E : ;ts ϲifnR0(K;c%`:q0ip[j3#IĒ#X~5 pm`8RH.z1PAxII{ Pޛ[C3oq &_}M͢6$CzLj|^a >\WhћN-%̱$ݸ$4M["ܚrϾ(ڮ^iGj}CxD[{XcwI赅OMZͩ$vmJv@arnEPEPEPQ7}G5i _##(>Sk[H.؂b0Hpkˬt_:NNeCL2&~seb~OlSkYfo)9}]*=4%M!Qo+7Yr>&Х'䱗L40ywQ1㞱;O:ִ5r0`+ÓܟkEι[($6MXI?ZLK"i7_Orz6OaYjVՙvPm0@}+`[vVzA.ca!UlRa[^gڴb+W=YO&ؙbW.YMrw&L/^Aqcv4%%#e>A2\**(((((((((((((((((((((((((((((((((((((((((((((((((((((((+ž)z5lWBz K^-wx'C׻K]o(|LRXڼDp8~s JJ9+agi?+,Iw YU``3 0ܷ<1H3&vPJ=+JTh`VQZa0WAOj˔Ә%.mu =:*s$G`_+<ӎSy<QEK;t:ǩw3.˖l e8JEP ҰRT!(&((ZLREQEQ@?+mշ&\km +"=|*<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<*cO_ҏ1=JEXO_ҫ@<(P1=J<3N=zybDО9_('Cr׻K]p^ }PQX^dX \ӓQ s(wc;wcnq9WzIh B#,۸mkdwFzTyG<6Aw1@V5 >Pqzjه\ӧ w9+b0H 5v.-[k$2yte`C7ϯUM\NQKE1 E-EPEPEPEPEPE-bh1[OtoVC\>#j{[[ U8ZăCԦ85M@]i֬' |x}* :毼Q1DT5R졛8:Et,Mh,0Dn!cuvehټ$`89@Xtr^[[UHhoc$ !԰r3qY6Ldqd/36"H!)gu Cྼ<'W:}PJ,㺱ˉa1#3(ܨfQ ʰnN㹚FYg ud@z:MCwXOE$Ur.Ў3zV5" 70<;s_^ZygqY ׺!I#8P~b [iU:i!嵽SGiŸٌc*plPt?j $,D!,/%$=A01>6^jj6[Xΰc*6cINZ\7,-(b:)r z~'ִ+3Mԭg2i~b ;O :> ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( /4^=zy"DО9_(+C׻K]p^ {ƇQEsKS#Klr#UgEe yQκiR<9b# rzc '`Fk/X.#K i<6BブI:Y:MWm7-m-4.fyܜK1=[kK[KgM*+}7'$pOog̋; +7òI7tevygcĠ'*dQL((EPEPLR@ E-Q@Q@R\kFgm5S7è™F&3yk _1gӓO#B+eշ=Oc\nmlUB($F{O^yxlu\ꒅ+Sʨ­vOlVf#Pe.0?\C<0<#8"$ hsp"bkB\Ͷ{0zQIZW!p-^5휱JۄB<~2K+[VgDPT h\3fa/o[~h"Z:Y[˷( 0nsM$ȇRksԢ3o`||ժڣqV^ϨY+Y v8'RoAg!U)- dU,%YM-W*8餮^ 4 I\|1rڋrqX!Gy3"А2q.21}],~EN/tڊ/YkKdVJ)2o`=4_ĂOu(&DvI@WiWk㕐gtgx Ϊ.,0@5cTΗǦL.`\?*epRIQ_JhOA񽞭{cfZ{ufXW9ףguc]2 $ʁ.9Foٔdg館 kROۥᜈ\CFu@U{+}Byox`}H(Q@Q@Q@U=CPωZ]]<#q'I:(|Ub[Cus%ϙyglm<zsIu{8R ˋ&/s 98'q';ݢuhGkV  ǑOZݾpqb[2zrI`P1\=Kk}#jվ]Z]Y%,tN3A +x(ٯteIe1(dn,pO,@#=H2 }N+9/bIf#xIB󃃌Z֢MzJ!x.n%1̖).ysr{U|W[@Y"I3u'7(cVFrA=c")ww`R:'V (&/Ḹ ,fY)n0q'%J٢iڍ`lYHqFV*Gb +'Ot[隅]\{Y]8jh3ZSx>skq 7 6v<]%ܡpDҰ^(ǿ=.Lm" 7s#8. :2I2. MEPESS`tB跑mz*bY3*Tt&+-Ni%FHՁ#=uo)P:0FAJ((((k?S +P03F! F>O0jwwsR,ry# sz4=sćBYdFԮm@=H`;WnXyM&Qs8_.ӺcKe|,>OZC 3Lk[wHFxbsb/fN{IaqR[g,v;gKlк.ݛR"\WSSҺ,wx GpC`}xH<Fޡ=| Ԡ}N[&_Q)OH3&—Yݵpʷتͅ {dNC *]8N)RzCЛv@gݺ;5s䠀3ʂuiccvz5FO&N#;I QFU@,}OCcamڋkHCw;cO@sY-v?crFO(tN+z[- KMt+H^Q1Hyr/v86m˄;ɷq?3f9>5f2|5E8{RwO%ww{ֵPEPEPX&ntm3̳q< 7 :+jt؆>[w6ZZZs4$C78U廲ዝ?Q qoB{k9gXbKDJ>tPxCnuK9[c!5|`6T 01Y]kv~"Nm-,/f_1@!#BQ@bIfIC*:ȊUHM}]>km?SKk m|HgT@ί?j euwƟe[tVEr6+z5iMtSk+ٹ܃o9RF5P1}m(@Cd*kX ;Fsk6l"YjrOviGڣ,2F tP!=UP٤*ye<@H6{5&sC6X.-Yܔ D J(𝤖aP:{]IXMwwP g6 nk{IUQFK(4ާlgֳYxZlm>CW.4q4WRͮt}]C31]ţJۥťEV)dE:2Ӡ:;85] G,??+G+h8ӵ(bJ=~4Moq#nf'9]ؐ`>}GK^Dvd rFyczeh~#U"$lN@!Ps;ThS^-֞[B[{t" n*O1uPkZDsºuӘcC،7#!$*-_F/, vcT##ϼj(ZP㷽KmXwĮvƎK6ũ-:t,/o1)c\1'j2F67kǫN^an]>]8]3O^ssR[i?.&Ř՚ _"{|>+Qq5=P(d%^uax-ű^2p.$Hu.?v?,F[ c  `ֺ(Dm'^-¼gj~F*?jzeF[tir>\gky`2|Bg8]eQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW|J/O k%z׃)͊GBZ\75/{Va1E-nW{tB4*;O\,?5CJaGkmϟ3z}+UiU;#Q(l+)Sg)5Huti7Й$ #nsׂzt,k7r~}KĒJX#[\;Reǡ5ZnFzVgѵӠ\ܦnln0f8$dV<Ӕ VE-D%h~԰-3F3{gm/30ǧdR7yˏPŇ=UEDMg,yDFxrT1$;*+4;Qq67K#xU~H.qwާ_66t3HnRr`}J1MCH `t9O@< 4dLռFٛ ` o'0y =U2@lFB(L/Yk^_-H̐ T猎|Lon5 bY-wb0xib RbJ)hQKE%PQKEQF(3l???Voz6T/]7SdI]FL\ƹ&تPI* $lD%:*Z{пh_OƹI1-Lj y%>k/*_$zp8{F$+ ]:&<}OPt/>VݜzVr9i.<,xiU\u+ǝiuv QQ7De\}Z?Lψ_@<oʏA#?*X/ɘ}w/_GO=v ٷ}+Jook2Y[mϺe !*qV^Q]ghQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW|I/O >$zׂ)͊OBZhԿuCbb̈́%VhO&T;. ^: _A#RAЫhP̌Z1+4mfi8lVl"k"O0FA'p9ݜ4WFt{MFevee? 0RuYl[Vso()-2ON=ڕVgAE;IZOqf YՊZ(( E-R@ E-R@ E-RLPEe#>k?Voz6^yxlu^^o#ē3^fdIz [c4(IuMӅZV_՚%u#R2:J!m5 $udi=Nq^[J=r*W^5휱JۄB<~2K+[VgDPT h\3oOka,6ZhG m9H\ SҏZ0JmixhY[˷( 0nsM$ȇRksԢ3o`|` 6&;Pb$&_˟cS0;F4_7Rpp# F[3~u\z[zsgK,,Vrc zu 7ibq~S2}* &8WƱIlNו#~T?՟.'71BvnsHpY]NwNvJpQuexy5({ἶ=_z־qK9mEčr]n uʍ8h^ChH8LAm X2I x̬qa(9<-6ú l>u!C ʳ~`⾟Q,11^1֖4+!RSŽG4};ROa]CPl%Ȥs1 t|og^ٵ֞YV+62{u+5"]}XLB2svKNQe#pVw2^Y0Dg/M2O*x @qW!5h]N˅UU~l.p?0h|5Ki?n[r#r&=Vqvk-ϒ< G* N1ֲ|?k1ӼץGq[w6ZZZs4$C78U廲ዝ?Q qoB{k9gXbKDJ>#E Ӥ@1%# qOԵ}.ż3I#rM"+ѕ >8i7Gz^"9axu9]>1@U5=F 'K.yљhpvvYVWBG扭$mtlG+ ֢մohiz=:k(XLAH9lrqUSnmz,[LxՙI*JQTo|Qud4BZFM#$j`k*@ qq%e"u2҃dqڥBRnrۡmDpqRx(x⸁*FH?I^}iΫ sNa=[pJ[b08z:}WT?YuR"k`>*(Ѩ35k =B[W['e-aڋw:v,΋ӓ$IΌƬ!sğ=i{ݮ;zQgɹwvwLe>{39ϗvǥ^Ȟ_>y<GǀvgMO@~!v-VagB3݌}ڟM`ﭣ),XےFAsbOYMVx\<[!MT ^1R蚠okgjre="Y@F=hO\!ЖY%ѵ+h;n"*='UۭV+8^xfSy2@f3YȟRñkŸWR6u~pG=2m^L"K|0ێGMq8lno"FS @cNszZ}i@u $pzX]b_}[mk+|w 9A[x5FkX&"Q}v3w((((((((((((((((((((((((((((((((((((((((((((((((((((((((GתM^^S#Fh?&Nl_ʞԿs\7NvЖV( ÂFq sغ=sz_EuQNybL4*K4'eP\30*^jȞVUcɀry<}kz7RNO"QxRIಝ* 88QqnZy[4YBaRyt"ƌgZZ1LBQF(QE0 (QF)q@ E.(%PQZ((({Ip1Ztok Ca3`}oNE b#֏ ΩOѡl6ʄk*ܮocj)KsӼA9Ûm;<Gs-+q"HA_lLQKE1 E-R@ E-R@ E-R@ F)h1F)q@ E.(QKE0/i_+YշNMb[} aSshl:5@^i!yKBU N[֯_vR^^K[ǍqsԊC ^v 3 0KKTiJWקSk+V1jsXy_j:,Hp 9Q{xalϖNb@'Y7W_X-q-ǟn,FT+zT^K]kcf&E)2:uZMյ6WɅ(GpKN3uׂdɈ73R͑ݤ}k)N[s~qVs8}6h-l'Hع$s^b+9%vURق7c Cw⿫\`M^+OUm6!Byqm rTgĀ3㞢z^xvTmƵjJihu )ƢZ'=5/ɤ_[^۬ͶS?vҲ  _.&y-lw'm)\ `j󤢹=/څݨ3kRkodĔiW@51hļ=ԃCpa402"$JG8n kxx^u bOcFG"(((((*gČ-.FڑGy$Ir>*{KYo `+ӭ ,@$ęsЎx@V~EX t‘@UqMV+\i\La͖3,r“ålU4FSKr6,8#+e#~ -ItBN[ct.V=g͜sӵt4VnC<gU ;[FqW'[ky'u5,V4.E}IEr~7"kSʷ3p+'qzX񥞛ge*wLe0q#ۑH9k]j6ֶ%u W7|?񚱦ih7{yg8saא\`BG\Oz>.B),GZƶMDpR(ai"ne@=9pW2|e3^dw W>@,GJ+R7LQXn}sLխ/ X-xBs^}{q&H4H< vb!yqjϠKܗWNY-4g%XSߧwƲxok7# !CGN\g~-/muw1\BIpˑ+m%V$O%#q"W یNqq&S pYG x R5cnZ1THRQF( \QE0 Q(((\QJ)h%/i_+UշVMb_[} c=aAR1ktH%6aXΡ݀2vFަ+V2m>67l8<G?P+xb\OY9U[2?sTn穄NnǢ[YL餍OfF uP>J2m>s9ۻ9k < ฤW2Foa_qbkے}קa#?ݶ P&&?/ j1ppq{Ε4W*D#u$!ȯ>x)W4|&H6["@16q22Ojs,b9 C=9]C 3eG$cC_N?-7)SuadԺ_NX$L+-"KhH4h$I pz+jO <>$y J,OAfSnؐl. 06TIfؠn%I5t1ZOW/ (N)H4oM1seeȶ^M[01Vz-/T/'_"32L vB';eP c'_j~y5MĒ2 s$~v  q=sw~~gxd "# ԯ$Xd('* mB8XIn#O@+i_f{-:Kv6!$x8d0Rvex'kq4WENrj[-RB+CE*@)Q@Q@Q@Q@Q@Q@bYѴ2Vư@' 'h'(bT^ogEkqk9k7IH d`#VÚo.tD%žu c-S9+dW@}=Yu,fHAo-[xTl}Pp0HƖeuXTI8EPpvǦ]"dP$ 3Ee$ܒI4ju./;{K#TDDɝh=Ա̶n/{ɨi|\) n}F$;\xU7Z:Tvm$w4pC")U#h >z5tM/m7 A!rSqRpF$WcEs:4I%WٕmyYG`HzsQګqCe7MfrݼIף@+34?ha 9`0cɬ?ۥIeCi<ۣ}1j $W@lziWs]Bf yI yTc{+_`-grP( ;sӐ2k(OvZz=@"9u%a6 1@6'5[S^]H#.{S Ah/ʹ{wfd]$m 2p#܄5E֟,qƖh.hY;[;|9=s>E v,T ' D'j(Q84?m٢eG&@|)xmV${]nXNs*x+H*8( *n>[x~J5=b[TD:ȩ/YdlǒqsZ墸ӧwE?ŗ ,Hmace>y14Er:֍2̻ iqZ?&r~bI S|7yWGn@ppzN3]HdXH)`$4X2a_,Qk{p.,}4"1^jLG #dI8w&.T O qh#nrc2}N>)(!4&i iRr+KǂuT8~#lӥKkCu藚}&?5Rp00k6MKXeMb@`H5cV]?jE{G^lEݟ%[}>խ嶊FgUR; [4ƣ5ZE`X6 >л~;O&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG&hG4.hLњh4QIK@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@yg_cTКN'ײM]X?د+joC9BZ*.YXrn4%NA#i,R&q*б3O5 zbwNaX0RUR1j#4Z-B{++RZ#EI'*1~-7*-mZ Q @쫬h!IdE !EyxZ=>mV۱\C_M.{5ma7DeG qW ?@[O W89ޮ*i?!WKsT:34:K guzQ_j~QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ@!4@4i Վ3@pޭh?&r߶ ю5vZ}ee甿“/<mEd甿ŒVR߶ 2_ZPN_yK~()kQ@9/o/<mEd甿ŒVR߶ 2_ZPN_yK~()kQ@9/o/<mEd甿ŒVR߶ 2_ZPN_yK~()kQ@9/o/<mEd甿ŒVR߶ 2_ZPN_yK~()kQ@9/o/<mEd甿ŒVR߶ 2_ZPN_yK~()kQ@9/o)j@{y~( 0eMF:Ԥ 0! A Ӂ(>H H : >JZ((((((((((((((((((((((((((((((((((+˾"{'5dBj ?ݿ%ڸ D2n%ε;C: ԥc1[ڭē/l 8ߧz]2ilbWDwaLV^Jn~lNA$^g.v \]ͩN4k㜸{h ig#9hsoʭD3ߓR*PP0I;k!v[j??Vog6 줼ʷ$':&R"9gVzaM\">lbK-1lmⱝBe썽Ma94K FiݓM5Hy'?ƥϹpiAɫiʟ~tЬ[,eF{H<9)|7i[1n-Fќwk"KH(R0 %R@ ¶44g0q~K!`d*varIka09Toڗd//lB]{ eiYXcCWI6;KK6售0yϵW4?[EqYy-vW@*rGK{;/dKȸ̰S/9985_ Pg֤8[Q)n:k cיi^ڋqT`Ni-uTBlլ$[b?v<=3P#Mo RY pH$THbHPUQ: ( ( ( ( ( j# Kv$I RE\_k7:6Y]^]J8Px.|͖ ;d$ciԀ3Kz^\Y5{aqA;8 Vҧ+{=N+[YYNfDc 3ܷv^|1s!.-Omg,lIh\'Ҁ:_v(At$qaAa=io"\qi3qnjX>CnuK9[c!5|`6T 01Y]kv~"Nm-,/f_ XMdmo&ZX'[ 9Vd-g[ vǽr:{ N,"iVԆDRF@,}Rk[i^]o<kC8>H S^ҥH^ Lm3%{F˞G{yHibL?f9G<4I%WٕmyYG`HzsQګqCe7MfrݼIojhH',pP.@@GSU ee7ae#-0To\hqCwPmph{Maf-BK-B#NI6Tep@`";;Qܰ)0YHAd.K}R]3PӖ k +gTtgJ4@o,GI|沤7_hsZfżyk;@V(@C<gU ;[FqW'[ky'u5,V4.E}rS 1Ox˩+ Pnf9OAws ooqD:o"$f=.ᛔV;N&K=6Uͺa`?5F+#3Ԑs\yW7Oqt._]̋mXn{>;ƿcHy.8 3kgo'yŭuZh%_hpxr0:Z*u87I$"Up8V##X4?m٢eG&@|)xmV${]nXNs*x+H5}u{ms\$rH.Z2 Q5 4V;y6S[ s4o FKzR%,N1x\WtՕh2x6 9\ 4Z;<V3J'N$p P|i厣kVKsa0$IVZ]hzM|˰6n(ßؒB$g kǑ[2= ($$:3|A_JGksr<ơn$|0ku3A<-$ >xf83޼2NW,5yl.q@!m"W wW齀g+R?}$ztɭْvqR@#Qd_mb58mfQV]9rrO^QEpvzkG=̓iRt;Fmћ?0&Bq9{jq5ޡ c}lגI %A=+vH|J S[\67dV"v;;V~6_ͨ?e5XEtMSV ӁA S uQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWB?_ +>!{/Ճ)͊mI5 Qk]=kbkdz"1z]t$0&g<A~c <D\B8#V-m`5GjV Z+c RyIiWp 0ߓDZPp.|^Ԧdh.c),{ħ"zotilmSο.nhLP=y5U!0)((((@QKL.( ( ( ZJZ7VCYg|m55M[KtɴLݰA@)Mr>fXyVinv6xlUՍb;X.0Κ86ʧ|R?45@];S)?2%lUTOJ+kA"K:FʒdzdWbj!A9Woʼ\_{۹IceM~[KRݮF1 Q?Ӭd{+vMJFk?oʼecW6\;AK-`6@qήhCiDFmXdS]G Ki߲AqT|g8trn:]u_]1HCKԪ DP28"xDyNT8TWٟXbI^UI0€[2{⡽#;Ŋd0@5f((((((((()CR4,U$rI'F$;\((((( A@0D6q=I4C6pD&IڊdNIEpE sHsP7SjJ(L{v:g֏*/ȧh5$ȱRFHhx㑑#;O"-\hسmP2OR}qʌ8'Thlij64jM9?1U\Y3i^Ee=B#y]ׂ+e=dt,1[Uw.J(b U/}7dѴNq>s;vVI#F" &rHMjK+ ROdzb)&0yw@8)ҕIUo5+ 8!|<WCwX,FlLM܀ss:ԣlFݭE:ʧn60>QqCzZujKwurȫFXbC"l#!qמoQW*Mn$"7#$9?=ԍ(0w}18k{FE)7 p=+ǠAs9[i1`Zķ4Oaie lV3[8$J)qF((b1K% QEQEQZ(((-b[} fi+MշS;FP:-H^wqPUp]V{d8"dw_CWACG boL)MRu7E2U|GiZVWڤG"NTc괞*iA[GsӘ goCkVz3KyKq۶ " ÃG"Rmنh!IdE !EyvumkMax(A\A$ӌ⼾]u๸Y,'b LTdniZSVߡ\UN4mM+>{[ F6.I=3jפkI]T%v`磅%x>B*W[E}ͺf#Ѕa_[Bo6 x稥޺מ'ݧancyCFqpZZ'w6JqnK2iv!.mTŏݲ,smn1WzKɫǤKk @%rW<ھ<(OKvwj Ԛ k|*1%2mP9u{(/NA8iP򶝜Kuuu}ifG99,} M[viԄ#[W\w2$#e]\jr*My8,a$5 eHX5_2jIەWu]?TK.$$00bbE<ʹ0s-|)iג_ZjIuߘ*E|FηDrj~=g_#Ofztvw70LT slԮMZg,2<'[NiM]MXYS=;4io?DhOGNws[hȟ чEy\??C҇Ey\??A=ws[hӿGD?+??Ə2()WioNdPS(7ӿG\??Q^o??Ə;4dOiCOHNMmj,Y9`:O(m$(_TO??VoQqeD kf+3ՃAR1ktH%6aXΡ݀2vFަ+V2m>67l8<G?P+xb\OY9U[2?sTn秄NnǢ[YL餍OfF uP>J2m>s9ۻ9k < ฤW2Foa_qbkے}קa#?ݶ P&&?/ j1ppq{Ε4W*D#u$!ȯ>x)W4|&H6["@16q22Ojs,b9 C=9]C 3eG$cC_N?-7)SuadԺ_NX$L+-"KhH4h$I pz+jO <>$y J,OAfSnؐl. 06TIfؠn%I5t1ZOW/ (N)H4oM1seeȶ^M[01Vz-/T/'_"32L vB';eP c'_j~y5MĒ2 s$~v  q=sw~~gxd "# ԯ$Xd('* mB8XIn#O@+i_f{-:Kv6!$x8d0Rvex'kq4WENrj[-RB+CE*@)Q@Q@Q@Q@Q@Q@bYѴ2Vư@' 'h'(bT^ogEkqk9k7IH d`#VÚo.tD%žu c-S9+dW@}=Yu,fHAo-[xTl}Pp0HƖeuXTI8EPpvǦ]"dP$ 3Ee$ܒI4ju./;{K#TDDɝh=Ա̶n/{ɨi|\) n}F$;\xU7Z:Tvm$w4pC")U#h >z5tM/m7 A!rSqRpF$WcEs:4I%WٕmyYG`HzsQګqCe7MfrݼIף@+34?ha 9`0cɬ?ۥIeCi<ۣ}1j $W@lziWs]Bf yI yTc{+_`-grP( ;sӐ2k(OvZz=@"9u%a6 1@6'5[S^]H#.{S Ah/ʹ{wfd]$m 2p#܄5E֟,qƖh.hY;[;|9=s>E v,T ' D'j(Q84?m٢eG&@|)xmV${]nXNs*x+H*8( *n>[x~J5=b[TD:ȩ/YdlǒqsZbӦwE?w ,Hmbce>L?@uCm7L.¤\Vɸr9$.FqQsU_&<]9$!F!$$f I+HRFHi(dgEb*H=(b-K}B>W2a_,k նNCH׺E_&##$bMuqnDśj=I,QHۜc2}N#TS5B^CVtoU?_(v(he!󬮠8 >[;__%_=[x*x W#qisimubPeY6|wI#j+js^G e1c15ZڝX=@b,lB~b>M;}8%y͖5p Ӵ&q,d CVp jY|KEkڥM݂G<6+67M?7W{Z̾sXbJQKP5f"XwS_;F~apJF #y"\_Ec)ۄSII2Lڐ)2@#ƷUys Ӷ< 87[Bx kn&5aS YY+-! BةpA*Nx渝 (+UgӧC_ϧʰKf(ś+"jr%ńDZ*eߦ ?/7Qabk2|nٗs~(ϱV>7?M{XwAثEZ̿Geߦ =;sUf_ύo2|n{9*V/7Q>7?k~}UMf_ύoúg>Z*f>7?¦5 1k,`i'ZWr@M$EyZת![4XETG@~k>Rc Kw\ 9 4a?CSF}^KM$/;sU*pIzL '#tO{D>ټW?/c=sOկ-粰%8tYrR[x¬+β!A9WoʺfHPrW׍աOڵm%5?RVnvG T QWExZ?Lϛ_@<oʥoo ~*E}N3}!Bt878C3J8w_nGEaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPIKI@ Hii 0 <"jv+IV_U?_(v(sFʊ+ =cj^m-a(cnQ1~ESN, o䯗q?k-~)̐+ ERG'&ӭ$bR22 r:PvG%^K 9eY- y&E#`A)='jm.4; zz%q%.0f >nwқ>cusŕY"Vd2*QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEǚAdGZytLta*tRZjAQH(M@ KH)h((((((((((((((((((((((((((((((((5?_ ҫͼy#WN_ \BZk -v#2QXu]Hivpmg *C *Q2G;_oʏ3}-w8յ3."z 2&:y/?ڿh^Vqǥ&4iiZ-^ZlWd2J1V{z`[_զ4Ԇ x{ B?\Η) %s'?ܤ6hZIFp{C֎5,Iy=LDS4mc辽p$d®Rp9'%=ƦEϚENI };CijQ5Q tqK3MIgZWcIN[)"6s 9)r,Jxįa}ϼZe  |Ǩ5DŽMM>x#r+߼F+o$ӮeZm/QѭV27a}8担VXEA)ԈsԁN$J)hPQE ((((PE7VCYo|m5ܵe%Ux' =HY<]4:Gh: j/gDYo#fo(wdmk ɧdz8Z4OwOEf*iA[GsӘ goCkVz3KyKq۶ " ÃzWc-ﬦ tFѧUٳ#:(SzxߙG@wy`Oݜ4ܺv֫#/ni^L/E"8˂X($:qWˮ7 %LApɜl6#^a#?ݶ P&&?/ j1ppqyu4|Q^?k|{[ F6.I=3jפkI]T%v`6["@16q22Ojs,b9 C=9^5,5'_e>}ϖJM_wNTsg9c*3mb@QKuc=[ h>^{"^OEferyP9>]]ڃ?&H ILp%qs]`9ƼM^&\AI?;O 8AS&2>Z(57wd6gXc6f.]Fh ,1a$qfqZyw"V g1_7٣δ[DR da_h'AeY &#|>h qkRzMݍ2\};-)Lg zW!q=އr 6{f pw>8 խLAKfHAX#@.3C@9C{]Y]Es˅L?Zۘr岓eaA^Y7NKme,A AjsA$\E6sS꺔ZF5XNHg^} [P]˫b5mIE/1ԓqJlinJIѬ"p!O\WIuɴs+8#%T$Z5i<3u,rJ!yX. nq]-QEQEOP?FWO#mHI<@X&ntm3̳q< 7 :(V/ik57W2\-GvɐHg4^+ kqr0Ø㈂wq#-6!OVzʹVt$!@8f 02;Ukn9bOB\[PY8ؒ)E9cOu4Qa#+ Ib‚'z-nKD1o-g=9$}}+:3A$sMd C-k*6>l8$`cKPE,*$"[8Y _;c.̿@Msͣ*O6 )9Ar1֭[-ťՒ[ηH9$zum:Y"E"yRɴl܏NjX[k7Q d P]y.p7w>PLQ%^/cP&ݨXX FzVeVs^ĒGrb&9>TkSڧmO4:|ҭé TXK%4Px&qMI|q@oJ7fKxq<99=>+ӭ ,@$ęsЎxUui PK+4*ۦ+< \УѭVLoYX&y 09XբѬܐOqXH ]*ԎI 4n.&0fF [9\aIaҨ[i/"/.3CY sNќ<ͺ[Z:G7mc6(/ Ewvo%aRa؂?J]fe-W+@3Wf9ڨ6iaw5+wi Yl7 eI>n粵͖ yw%ðQ'=9&;-3ZSx>skq 7 6v<\NN#,jXh]$ 'i%cB#RVo]i͂s]-徟e5Ԃ8!BPuO*DHz]7(w=McƖzm ߛu2*%~j$WnFg +of]꿿I,CL6}w!~=u,>\q ?:g/wNO\CZQ/KG `>u8 7=*RUK po͘IE6pFGoƱt/h1 h!ME<$MЁSCV4Inݢէ.T61WskNH\d} 0k6imvmu?+}+t$Unm!(oUQW"eI1kN[MO4<#8 <ƀ='V5rGzgb*iUR $P|g厣kVKsa0$IVZ\ѡ]yaRm.+G9FĒi#8xsU_&<]Hd{IA QHI8e=I' |@_JGh [6t KԎ DH۳mᛧ0aߦ{יŦ[֗}-e<¾X;7MYqgl&;^|G䌒}4յB3T/@Z7~s?UY*֍\EP%8"yeuHfca[o!&Ln,} "N$W:0?G+}T*PRf-SqJ^J^ROŇ%O J^J^ROŇ%O J^J^ͩ^!|{7wیs;9{ =/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo::~=/XrTo*ɴ~V:g 1Ўƭ=Eʗyt.h?4ԋVi_q785wt:(k<k":ֿ?F# )S@:R jA@RQKH)h((((((((((((((((((((((((((((((((7׺M^^qBj-!y?K]q%¯[v{1(B s;Oq>Awot$ ɴpz3ϥbj>hb4'du ==k3 LH§5k}&yidC 1yW?g-FmZ-K(IE6 0V˩VRXt9-C V0FD UG0Kϧ'jxJPlH$V]ےhEoPFE;G4fFe2=8$~4ƴ{kxFՔ,<׹ME5cDwuEVs `n}jkywF1zҬQ@ ӥRJ)h(QEQEQE (@QEfZ\??Vo7>[inR*j]MofqO Zk+25ʳKsVQp3gB.m/Zp,ч g(tQNU< ฤW2Foa_G4/ K@}?)(dUJ&zT\x_ X:6T##"Vq`Ȑ(rG U*OdkUϖL#*oߪ]"v8Ɔ& 4!fʎH Pc$~[[n/R7] G U,q_qk)lH6 usDuO*$}3lP7$?@[O W89ޫq2乌jE]u]NUQ"U )O##wHʠ8ObJhI(4Q#"G+?i:t q.jhR9¨\W(K SHk# BҊ(8 8 c%T A7v1=I( ֗S.ۉBN=*PEPEPEPEPEPLBđfb $;I>EƊ7$hșdz@Q@Q@Q@Q@!Ɩ8!8$@YO!˂$2NP'p*J((KD.ۜSRQ@ ce>L?RRPbVcA#: ?/6Zݕޯ4!iqq~uKx[Xnp dZ.x.cҾ3eYϜE 뚞ƛjQۥm[DHT.U4 ~fSxzhH4Էڶy92T %>"͇xf bdTfnk [Xդv,f+= d׮+[X{JAm=5f9fAMF4rG}&W7-ܷHmGv#0qYW⽻Ԭ qnZ9X$*ª8AC@x:\x 3[Y'.&.AmUtGLK B@h"ӯemA-b61EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPXPk~?+jEk}ToElgk_#Y־i]5YJjր$ZTkR xPZAK@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@yǎ`O_ |t?~WFw -ugB\BZLGgG(ZTG<s$L#?b +XXaU{Z+'IQ֥=sv$F*G>4[O"E UBSƟ4pr}<K [Zq:]Xpf:*ө#=Ut.w=,tյH̨P2I8PE67IY"uxVS K)G "W0|"j(9QE (( (Q@QHaEPEPEP0)wLp1Zo4oj!QE!W(걼ZKhKin6`\'°4TW+7l5Y/⺳&;yq+IG#:UDĺ=Ԯ'W$(œ*ɷj!=1{vV%ߊ,8[NRKr9hԎ©{VŞ]{{pTf6qȠe+$>HaQ-บ72T2[ pqԋ_強HU.$E3&Ḓq dPxTҞ[+ ${7 I Cڇ`䴱(y.$mꍕAQ\1DɴgfK=yqTo#z(qYb^ր;Z+/ Y=ƕm%#W22A2$uVĚzu>3?QE1. z0GT5it.i[r189⵵9o4S'HRc\G@v88de\xw[;'~v94>]}3Mnmۿ˕62y!AY*4boGQw"2#\e O^[?ʜg* 3 @݌gSU5Vslm"@M/' Aƛ1=?\F" RLL$`󝜨$5Y5iۯ&3\cGG*2$q6k-|9s?44i7,s% pr>8Hbxi>@H; yڕrׄ$k "; k0BjZW֤Os @6"fN9SR ( ((3I@ E (aEPE&h1sE%-QFh-b[} e+Qշ=DwvZKkrJQXhz :Ճ@$zOt4RWxX|O]@Indd9 y9n!H;smltɠ/H1ē5PҵTR>b0$=EgWM>b$DCiaw |*8'u-&1e(0XA# U9|?oy}>H|/Wy#>lQK "L0$%p,ǃ$du I.,-R_ϴyKm݌ڀ,!洂&;- ^DTLds,7 > BЈ6w&ѴGN~^+clrIY:nphԬdӧԡg}+Zldsm*IO艨MwmqMz d*'a$|WagqZAfHy1su=;Պnofg{}Ctͨ`j$[n(ٓ_]>Kծm$x<7@B0/ݯN0fYheU*Tn pH~BKwܚUŏ.fB鎘ldP( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( JZJJCKHhjm@B3T/@Z7~s?UY*֍\EPEs;%mr(dfYH ʙ~_^['t.1 ̦E88o_xi'6V$;HzxQHukkb7Q*Hc:+eaH@UG^x^ɪ1$D^{Ig}m[}<[MɄE3*$\ |?Z'M.ͼ:u7w+ċ#9f!Y" -- X F)6y({h́"SP }3֦8eK*ɥ0E5#pҚ(?p|3.sPSEs8ݤ˷=7U1Zlͩk[ UB[#P}}Fs\|^(iꯨKbҶ yi99\ xF}a`Kfk3RH~fd`d^5o[mkxD 0 5zZnbKX EefBa0r9tTW!:ӤK[s:1HFwPd.:t9dãM'tA5dy|:@E"R d; ןCr.#mG. mh(6=b7qRzա.&Y-"Cd:W Ef)緒E~;J2?N ((Pf)QE ((QE .i-@qVo7?h8{cz֫ocCM7:ZEEHzօ ח$ qP<:++?߱>ˌ}ML"9i*IGU~YEWQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@%-%%!4QHj6ڡz U?_*kFgܢ(ZdG*A84ht{Kѭb7Ɏ;3ZP[sƒ&[S(p00 z9W^ү ўs]JJGVފXrˊԢ2m3Z4MCɻO16*HyF}m]v㧵h@RxkHi;ܩ8d,RK7B:Cs vc|\oNN rĂrA& =ܭk\DˇaP:G>VDr-aD$lNkv/`.ctt\2F{ejBOm'Q9#k2ZCiENe#\8;Cz [u؋9RFמyZڢ14VEgk++Fv, ڦt;1LB76rvgh'''洨 C:6rK$C@fPoYs[ΰQ,H!aXr0EtP=煴]B[ KɉUi*3Lڙ~͋FUvAH'Ң3tMҧyl+Q Q`*ͽmIq;(@pO*ǥY3WAuGt+3sg=:sҙ&Hv$,Lj@4JiZWc#0Ĝ &EQEQEQEQEϭX$|_>QZѫ*3U!3N5 -$2r$p2si-V?^sXKʥ}$xu~-(TɞgCcah t ζiBy*A],)sBzs_]?Qwld`v(>[vkH!_*0ʬWWfQ*{lB\ҬݐQEuytDuǚAdG@RZ*u Ԃ)E REPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP\/x/j~??Urex9T\5ͽVڊ˓l9O [h6=8 7(,1WUZTIJ TVaXUF4le G_}O?r<1KtDn7FA'p4YX[дV;vyFfdIU+Z_jm$,$[< #;=jM QTRPc%Q2gkrۜFqPzܺA.qr'u$ $cSZ/i^Eu,nג8,Ppz].GM.n?6䟭fٰ-DS#hf\rN}9##5TTXgrefڱ{vq1ı!cB}꽟̿m0y;vc<-%PE4PZJ(RQH4P(J(0J(sN<껿ZojXɷē@SL W+~< ~q֕eJMCN^q"٤Z#w`j&"!+)ʺOq^Ox2kou)ff *O\T+#Zlwb(wF#蠞ՏA{2#W Eq7h[ܾvlO=(beh}6_',43Uks۳#è$g=1֍׌-m<@Fngky E3dBlV$^Uk70*Ojvx]&?цI+b|WXJG&he[MΞL8 v:=xӦgk}e^[] q䵄(/2gnzUnt7V4M!"I5 &!싽;7F|sd_m|NeDHE$[Jp6ys^BѯBG{C}pd)4&] Ǩ;Iµ qxK-nBnYT6pr0;UKKխd$TI倇ѹBx=2k .鴈e+q b 9 .K'=1,4t}^\YyLϖYdy`y@EyekY^\5{0Yo(®pHN gO{ytau(< *(,R `cҖ ( ( ( ( ( j# Kv$I RE\_k7:6Y]^]J8Px.|͖ ;d$ciԀ3Kz^\Y5{aqA;8 Vҧ+{=N+[YYNfDc 3ܷv^|1s!.-Omg,lIh\'Ҁ:_v(At$qaAa=io"\qi3qnjX>CnuK9[c!5|`6T 01Y]kv~"Nm-,/f_ XMdmo&ZX'[ 9Vd-g[ vǽr:{ N,"iVԆDRF@,}Rk[i^]o<kC8>H S^ҥH^ Lm3%{F˞G{yHibL?f9G<4I%WٕmyYG`HzsQګqCe7MfrݼIojhH',pP.@@GSU ee7ae#-0To\hqCwPmph{Maf-BK-B#NI6Tep@`";;Qܰ)0YHAd.K}R]3PӖ k +gTtgJ4@o,GI|沤7_hsZfżyk;@V(@C<gU ;[FqV.P8"iX/Rdߊ'i%cB#RVo]i͂s[o78FyUTQġ |cà󍾣$Q@Fxgs{_&X6fE~ܾ}A:ާlgֳYxZlm>CW.4q4WRͮt}]C31@ţJۥťEV)dE:2Ӡ:;85] G,??+G(jw:afG2pa\%EzUБk{p18ăqm>^w #KH9nq,uX%6 ׫%/2%I^J7Bk;Pnw)pNNf,<8k6@ qq%e"u|iA28QjzEߎŻ[UIqq O|k${ 链3-N k:5s$39`9~BztYj;M$ܗV?h7$}wbF8h';}"]Jȷpz5=:1@{]_JB8N($ߊHYA "#Z2'ũQ|$[ΐ #n9$qՓNVR p(h1UdZ7~s?PQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ytDuǚAdG@RZ*u Ԃ)E REPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP\8W{\yupT!%0sU (8+߁Gl ٻu\>r(}p34-}0 q- =wo4G}ׯQQv[mgrW~`3mډ?.QĈ%@XtaޙavEAw^Ԟj,eu^Y3Qt(ƛwhCE9bN~ UR(I-tH`hϜ"L;4Ucb2xhGq G 8Ur3.ݻhϮ)PEPEPEPEPEPEPEPEPEPEPLBđfb $;I>EƊ7$hșdz@Q@Q@Q@Q@Q@Q@Q@%-%%!4QHj6ڡz U?_*kFgܢ(((((((((+LQnI4oή_/K=4(tG/M4:WVo,(8:/K=4(tG/OPI.u EfG2p=[GtL/ZNr@55Ħ(UK={fK=4(tG/(GtUdn#FeZ6WDHs}<FEQ2cOGtLK=5nvsA5f{EhQEǚAdGZytDte*uZjAQH(R Q@ )i-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQESݯ5W'8ꠃ/4Q܆F0 :XZ-W1M|y$裟9=r_ 5>!sI3FkC33XdӭmTU,"PAkiLRͺ43IY;gf5M:ݮ ѕFP̦) 䎆." j&w.NpNq1@͝kQkit"I]UYVg5 VVMb?/!IcaL}GzY5w7w"TK*Nv3'z&mqM?R+#]lP hj}@h6WhfB`1ҪxVZyX!V ' sj]O4g)-m#e'zzC× p[^.Y[Ȁtɒ^NF[(fsiM%)K$AAhO/0#{[ F6.I=3jפkI]T%v`磅%x>B*W[E}ͺf#Ѕa_[Bo6 x稥޺מ'ݧancyCFqpZZ'w6JqnK2iv!.mTŏݲ,smn1WzKɫǤKk @%rW<ھ<(OKvwj Ԛ k|*1%2mP9u{(/fqg[jf^+IVoU?_(r((((((((( k>//}QErZéCq=Oٍ]0۴sǽf:mncx&`7dV" CpswP^ŝGJxRc4 =;`qSE\H׷'1ȷ˰a: )ܞ-e-۔!`:Ozq'"o+C_۷ٍwc8gWFje嫗Qbs:j-& :k bGKh|2<-Np[py> [io6 Eb 1du\y]\Ck<2F#H %X6@SҸ{ZH]NFTzStM Eqr &5sfpH>3޺,4.h%Lњ@-44fE&h!E%f4f4vhŢ?Vo'>kC9?#_F-qF6+ ;Pץj]MofqO Zk+25ʳKsVQp3gjEsVi]v_szWc-ﬦ tFѧUٳ#:(SzxߙG@wy`Oݜ5\R+H#7k/ӄm5wư ^Zu~VȓјSbqϸJ+kA"K:FʒdzdWb| +>Kq RJ_N^k{{Me-b Qb8'N [-qSۄ!f㞀.šb1!T/XV웋hǩ:׉O0j]/ZY,Zn]o&%Pa4J8=^?5'mhi:3A$sMd C-k*6>l8$`cKPE,*$"[8Y _;c.̿]}2(b CF2ĒxI$5m:Y"E"yRɴl܏NjX[k7Q d P]y.p7w>WsLhyF FJjUM6+[7hli'9˕ L|$`pE sHsP7Shҭ??%-"_JdT6c89r\ikwVWW;⻆p$|r6xױy1vsq2ZO&?ȠG\ѡ]yaRm.+G9FĒi#8xsU_&<]Hd{IA QHI8e=I'CJ,h$`QAG9Xʒ3J}:=_Pw϶岌ǘWx9z<p.,m~R#"O&8yqob͵@b(i#Uis,pOjf^+IVoU?_(r(((((((((K2fLW aТ3/4}_'kB O (?2y?3Ge~f( aТ IXı?>ld aТ3/4}_'kB O (?2y?3Ge~f( aТ3/5fB ',jz((;Z4ȎO?TP%N"ԂZP(R Z((((((((((((((((((((((((((((((((+wj\eG_kZ.3|2jHPOrRr7 TӬӴ[+gG.2{լT|BI3ZW;levQsz|_A:7_/̷v-o돗5ٽa c{* N;SR/lf{HpWjm?!@Ic3Kl7XdX)?{IRl?uAuci !f8jz5XMklQJI#=j%^\1>_@];S)?2r?? |T}Qe>kYZr~=qy{m|q-dv b,@yPXdZ~wF-幚Lq'$r>#]Rh$ieeF͕ ijWZݟD[Ku !lzeٗ(V?Y[ɮb{EV '; F:ի}Y%KpYg#1\^S5K$HO*YeYLy$۵ R*̾ S{KؒY^,D`<ְCr*};TͶ;OUu! `=iuTĺ|~j 9)8#(wT׭RSLo2='5^zuh${1@Yzrp*?j euwƟe[tVEr6+z5iMtSk+ٹ܃o9RF4Z5 7K)KP7:i_p:f|oP QsǭmApgyɖ?*Mpw/Pkί<7g[;Y5qF2*9v9Ϥˍ>jM-Ժskk#F<fEGWPd iPEqqh%Ҷw1iQih_Ό`D)1-7NN?aMj;}BéQxj0i:]ΡtXAmzojQiŞet${|hF7FN$r }j-[Ffæet1'q,uHe6 ׫%/2%I^J7Bk?Pnw)pNNf,<8k2@ qq%e"u2҃dqڣ{v@&H'|g@4ZtjH!g| r@9~BztZ"Nu<1w%ս 3I'">]ؑZ[*{i㱰%Ԭ|m< \cӣWm/3BN=)V9 p\ElڝEL ۀ6OP@4Tu* WuRu*B@k&>β&>^秥CiCywSoso+FeO1l=*LcVG:۫D  8zի B;&ɚHf2Cڰ/.mt tf+vKxܳx*3@ Zh;m[ c洣p8۷~ge*חG@vQytytWe*חG@vQytytWe*חG@vQytytWe*חG@vQytytWe*חG@vQytytWe*חG@vQytytWe*חG@vQytytWe*חG@vQytytWe(J)0#*tZUJRViaQ@)QKH)hh((((((((((((((((((((((((((((((((+Y#Yڸ>3:lDaSЎAS沣\В3nߘkfMunZ-╣UiYYXQ٘'t q}yeO> = carsCtZ^%=Q34m*@e`Ȍ7:VąlcP'W m(2szWR&*#Q 0JZM)8#6>u> g2'˭XCI7)PXuxhcH ,j2$9䎵W1\15fE*ƅ@prA8n;uG:}%sn)#@,N GL.$\`G'iCz!~dwo5kTyeb߯? u{oOk*AejgVYU{R{ժzdg EУmݡ `=A:B"SbơI'qrOO" w9q+BQom[CUp*Z(((((((((((E P!HbHг1TPXO$sO cEȒ4hdLb2W=p{S((((((((E>EFE0a Q %\d f:aJcjO/ڭt]UחG@|<_..ytytWˣ˫^]]UחG@|<_..ytytWˣ˫^]]UחG@|<_..ytytWˣ˫^]]UחG@|<_..ytytWˣ˫^]]UחG@|ucuKC{\Zi/.rIK]w# p PkEeqy{m|q-ѓ;ZEPcۼ2^K4^5feRJn iewt db*3ÏN7hpO\iѫ`96a9 rE!{]̬Z8AI= n-A ""EJE4iZ4BReTX]jK.Rh!x6(SmA>6(SmA>6(SmA>6(SmA>6(SmA>6(SmA>6(SmA>6)vTh@vѶ((@)h ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( |`޾91_|%Tw&[^kH//!nd/W=5 4hm?Z z.BfFb~R f ,I?{å\f*Iocn" ټDtS~l 9њmȦQlfю"TyL-HGuH?4f0"u/V0-7~1^hҵ-/Ödf+RX#s[P3 >r?l6ۙ AIswLJ.m-s) 2.p;zWMFh\:֩5 iVĕEnYen1HVu%Zmg"ey< K mJ WK@ X],$A,#![gp(\TQrٛ(ńyʑgy-økUkv2%O_Һ|敆r_Wdz02]9"RY bv,?Yu]"x6-2T0Pԥ%//HQgvf\v5v5ŀYGmFª80sE9Cr3\#m5m2,1lӑޥ4!Pw<[yp@=kw4-2]zO_Z@U ;RRŮjJԮ簸㸐9x[9:n5Q@v q(p7B Eif43n??b_[} :L WWxO/FG+c|C^OGaתKQEIʵÂF1ZMaeEQ*giaZA;Nz+XzܑOOKuuɯ=}o.PdFxk+pdѼ;P8p֨3mډ?쪱##s^>,ugx{;o \ǧ^^M/rdy Icr9ź]|M} -ńlǖ`#8=rPik15=8]j`a S̄cil'j.Ѵ{ ĺ-{5Ϛꁥ I9 2M{0EōBN$΀9;mKGJ5 4iI *dftmۈl{ot[4_ꊋ56'pI~ld{h.׎D =<Evom pB HT{J-P2iڤ4va% ,6vJOrOj^Hi*^[ >h 2MOJEy7./1_gh1X#!sp7umm'R1;E[-.Up:½bdy;scOz[\ { Xª ڀf0#-lhjj1M>T$N8P3Vm-o"#v>M_灱 p1؂=EEQEQEmfF<; ˩XGpMiSzsmŬ'3I"Hd1s Z[/i'u6$JQNXz^Ey=fT #k!2mYQ?3eA#Zַg)aQ$AbveCR4,U$rI' i׾ / R)ʖO&vM`zsRƗ2\iP[&];p+"cEȒ4hdLb2W=p{P!TkSڧmO4:|ҭé TXK%4Px&qMI|q]hA&WWq\ifUMdW,oxy" oG[jƙ gE6M;v$`s^EsbȮ4 ˸;B6\Sg=&n!%$GnXxM2 8 0`^Er Xiy]u B7[#$izsYRiG49eh<@+pIN@ɯD9? IkPԕp(7|F{`N(GUd(pkBK qpMk5Qy'ʎFݎs!5rO7Keu.ѷOّAQ89z@\Z=t]ZTZ|b6DW* }: qKMӯӏoSZP`DrrT}:b};RM,+#sD66bq#݉Qj74}}45Gk, Kw$g98צQ@m=R),KYDd 83S|MpIhWW|8ēto +s]3;4f+q٬g͌M~PZy-!y2g#cgtYr:KK, 40Ac|UN\=+n_ +\*(VuޝI9 3(+谮wm$P404ZQ UYsg5 [VU1|pO0JɤJnӼ28 `5q \HIF*p}PmV[iU֒(m#rBE/kZlHgZ.! p4{lRA `xZ]7Wq-Xz`<JsН/NH/fGʝ78Oݱ11֋[ˋx`[ *f!/y+z/ZE6M+-$J0z t&-,sO d29SEȿ䚄scFSp} X%Әjwqkpː۔ˎ+JDn|K3'?/9<ޙg[Z3\}O.#qd3;J Q^Q;6Ē^HÔ'}ixF2$ai w V6חhd(d F1ǥf=Ť3FmVTs q@/u^mI"Y2H($Tx [IW{:ɌjŢc-98 'jFoutgǔ g8RN3{or-]brY[B0xpMSo4l,{ 1`Ao8'a ʦ1Xu@~=X֤K~YİF*6?dd7[41}ЗmN뚎wVeQKtE\$sҭ?t,m1dCeۊ8㎂moSI0"K`m㎽@%Wأ:ZqʩoWb@r0{Zv☵J۴aQpsA郟LU˭i3 !9{ cdQc=6;2|gppF}i3KHb-ơ}y$TIAY$"r21޶kvWK&C>l߫DX$|NrUoO5ji|Z]E$D슫Ub_[} `1[YjҬQ"AǦEiW)Rk[bTI]ZugRԝsbҥO˿wo4G}; ^=MM-P\`ƻQ.\6pS`jIG+H4=.ȲH"ޭQEy2**hQE!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@| ͩ=?%}^-o XGJ/`̯8"n> !hwAҽ[5YzP\њLf6jʐFHf5?lz@ywzVpH`y[ $sV\e^߷٤KHOk3G$qY#aqҁEk{+CJ)4/c*Ԓ$Q:(,ԓ\v:YXo%\e8(g`u\^&'QXkxģ1FLzΝ5.@nq{d5薍3Mml[:Xgsڡx:{(ؿE(_Ճ1ҸX(vy"o`ǠڣuYO0mB:{YGԢӞ\i&p60?(7ЏIX8gcu$ ew~*>5 E kc| +5;[-&Nh]B 9#qWNd6(;t>jRI,iyn&EYTFxOMmuiJ̤rf+ p~Q[ hH,7ۼ7}Z.naa# g+1wlb_E@9=6|:1q*4ךbDU^cx}y!TixlS#hΪ=[Nj Je.1[yY ?qvyA-#MZY|01lzZ9uxՈԤB HoMߙmYWF'=*k-izV6.dT9ǧzyk-]f-L!d)%nrqc ;*;e|ǿVwc;8Wi Z3I7Pԧ-B8m[o&g3U##>QY5jIs FGUпb[} `h_1[^q6p*utWחɗ8rzz2 ҬR R>ik$[[4\8$nMCZS3tee9WS"+U`Xy]mn=LۄU 냞?Ju섴e=wg( -0st'5cǷsXš\[3&6&ڀs?.Y12>/ me-PC Fo`ݏ3'kl-'ʧ;r4鏩^ip^p[_,Kpay!xJdv b,@yPXdZ~wF-幚Lq'$r>#]Rh$ieeF͕ ijWZݟD[Ku !lzeٗ(V?Y[ɮb{EV '; F:ի}Y%KpYg#1\^S5K$HO*YeYLy$۵ R*̾ S{KؒY^,D`<ְCr*};TͶ;OUu! `=iuTĺ|~j 9)8#(wT׭RSLo2='5^zuh${1@Yzrp*?j euwƟe[tVEr6+z5iMtSk+ٹ܃o9RF4Z5 7K)KP7:i_p:f|oP QsǭmApgyɖ?*Mpw/Pkί<7g[;Y5qF2*9v9Ϥˍ>jM-Ժskk#F<fEGWPd iPEqqh%Ҷw1iQih_Ό`D)1-7NN?aMj;}BéQx0i:]Ρt[ȶFN=kӵ(bJ=~4Moq#nf'9]ؐ`>}GK^Dvd rFycw:ww[kՉf` Ƭ̪IRWК;LծSL BX!Fyq]=R),KYDd 83S]ؑZ|Uca{oKXy7E< LcӣQL.WhaySI=`n-A ">jtkk/H3 ۀ6N2iɦZVXPF8f(( ص%t"&L]:GjO͵ݬ%g QF dDaǡz}ya 䱩'vyZOe;EJcT23( ";3elT 0eY%r9k1zUD6DWFYXdz+#L֞u{tȮѲ;0% :#+ IY`Dݏ`I6'|c)ehlְ5Z#*ĜGC?Z%[loo2H@]@ݜ%߈m-.eg"l([]VU~ln=duNsUq@:N̹$JoHNtkH$?#OJs%PG,^x 6Vc!#B(URIE:O34Up8ŷ||n#%Mn&a|)(4ax§S⛐7ҽ51fŬ~$f4Q?ҥ5\ޗ]i "U6/Ѱ#mE Gi{%ޟu)è,ҶEFCs]}>86 1>K{-@pst Lp|4rw7^"PykkcHt<0ORqi.umkj#j=GtTPF˯4n)rx8=k7JH|= d_C˃m F+Kxeyb4,O4 ńͦݾb=7MlvXiD4{7"h˿nwVKs&nlli]^c>.t(dմ9-[{)bc1'ں79B#W p~#m4)- ݻit_9n*vlMpX2eLe1:WsKH6IV(流7yby~h'G8a^N3{q]=-*M2>Ҫ>Iq`c)Oz8g8N bӅȾJjŎW偔 _MeZƷ \uA 0}Wv<̞T׊|*}P~hGǀ+#SVʷiu"FOlGsK˩ܒRzk1~Yo^ip^p[_,Kpay!xJEe*B\Jci-R5\8NDOsf(1B9NnQYƭ`.{ ERT u5ZOPMq_qq16X̲0Ra NKETӵ}N/-ز UAVN/%5 -9mкX6qNYf,}Wxn+lln ;[x9[KC`HQ~(z+:\:82EoFq9[P\ueʓfd\9(*w:o"3#8 tU V;M-ijIm0VfU$+FMPvXwIpNNf,<8hzƝ#fPs1Whj7e/3}I g,-żS(!d@#4%Q@Q@Ε\xGRH{AQ 3ķ1lc2H# Ҁ5G\OKU1+X2&q䁌NM{`,Ԑu6F`HH.X,+[!KDhU1<0d8$g8*ũq-nmP~RA= iQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWc {cpC7179)qKcONJ4exG9?u2IE"P߷1̞øzUڧ k\PJĞ?JC.?^Y8US`/+Ypx듸$;}Bxiv¶_!@q@"{z(!=wsR(n$fm2)l㷶 lhю"Tךv.*h^5- 3 _vˤm(Zb2R1cEljOX^I-#p{+ jcɦ]XgƋn de=zPr+<1-ՓJ#W)PG?QWu6qY( 9V~[;s<>Nݒ1%[B;u: ZK  H y0VV}-jsIg"9ke߱jZ4wv w' .>H=^-gۋ80y,i'3[7qAfs$`)ZZ[I.XFq R6,IhY-K[grcev @[[{WŢbD C}N3H gՕ^[e~HY2x9'>jW'Fo6E_I0psPuֶ!Kamz3qd4&Y\l:|n$@1v:.$6/nh-Yf#;'0:]^EXr qGIA~%1=h4(,$ծUP HCTVId1>ib? 5_ E$Iuhm՜]- p2+V}kO[IUg%AI O@ .{d4Nx{k)-QCNĒ3R,,3FTPoRiu{|>Fɓ$29Ee~ei-kubQǩ-cΓH.5Ķq .@K1v5oY"I,@;3(?=IټUv70#r\|q>ǽMP;fWSm GcxTg:ojR*y*6 qiW9>Ǩa? ':5Ӧ@ 3qsBC#/BCRVnc6'?23H B z@r?WBkr?WDi uy,qP$̞_#䌏W'9vïU ;z5ċkfk݀bXx~Ό*d\=aםp}ȣUS yH% |v8W"Q^zߧ]÷X1hWLɩy& v,q˭Qfۭz#]Ub (%NFGC_5+b?$}Y(vNR_:-@P$s]to d[ ُ,Fp2zat0bjz-ͥq<ONZ]iS]áu+Zk5J6SrA9ld `5M$I?s6vږo>jh AT۷2iL!Qkm|NN1]][1Xzx"{h,8*@_QKHY⸷/ #p>K-7NU_ŗiIKg4P~Z_Ύ]6b\*t'JM6xR!b " rA8E :})h ( ( ( ( ( 6sieԬ#`O@N׎OA[TP 4JSrn$2 Gjw-ݗ4 \K|Y:Z%(,rW=CiVԆDRF@,}Rk[i^]o<kC8>HƊui PK+4*ۦ+< \УѭVLoYX&y 09G9m1EWfh~!T\.r`)3ǓXٷKaPHӒx#FMb<&e0HC9 ,4Ү溅n!P-A=94#Vٲqo4ZPv $ dעQ@$ z(DrJmbm#=ٰNks]H#J2X8w5Ey8gk8&(XrM"+ѕ >8i7Gz^"9axu9]>1^EyžEzUБk{p18ăqm>^w #S%f;3@[uk(6@ qq%e"u2҃dqڙW<o#i7q%ā>M'NZ(ʹ7\][C<@{h#݉uϥ\OmٮJ}/Z[hf{_9[(3`qzj3ZŨ&4Q9` quPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_=q'6C8rS[4cR?⪸?WV;-%YOm)$7NXCCRE%rK]\$-40CuB2r8=(](Z[f-oF7TdmeZo"g*.@f GPĉFF$ Lg UUtucpp$M;pH#EQRזpP`\eTvvj3m9_7)kNk- ׋ln崅99:1=;JӡdӋ18yv|JŦY!H#ߏ8JF8sMEuKL@Ӭ2Cc+,7OgIE`HVKs3mO<(ra2KhyD7##)R;iig^&kS!X<`u\US qa'DJ)?2T>3@eAs%ԗ3 6[cL1[;jj_b{6ր6ZTKԦkւHTI/9>RzٱxMr2+!":+Q\܉.X_Ev3ۚi/nKb#(+$D^xFF;V/tRxFÂ\?|=#tOVniZ-sHhcqZTUBr想&PդB;@? #kzxeZȾUl" ;sOzE'vm%QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEn@OJ~ Y5sYJ9Ź{yT=¯I~t^g6 QRP V3>v9$SR++ dz@:<0qg.%xY7OJo _n"NX-pZϋ,Y$UWU#My?5 My5=H-%Ψ\V̐#O8yyJ+6K`w[1qHzŷ %9- `˺MX hg(Uדi^^;F +ثr) Z))hh(Z)3K)i( (Q@hP3E3Fh њ(( JOշ/ I\O?RERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW qWw\n/jƿkGЍ$k d"8担sx4c}ƢY]e LQ2A NTGLgm*;DR<-a(P?/\,_{aG=j\ ?ox5z.{awiq^}x&s\#q>OtyLFd9s98Jn3GeH|~pIp=(_IE;hv # r=++BO5Qs!>}xdW H-xV"((Ϳxn)nI3ڹv6Bq@0(\xʹ<ӏ1~:iV%VFӈU؁:VP76wsrԯ(NrX薺u .f#fG,Gl$JҢ((((((((((;KwHe#hmfEu]ʖ9FS~EVק[o#O(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((GʧV]W]\@:mX>Z?⍭|A#FAyn*(nsijETQEQEQEQEQEQEQEQEQEQEQEQEQEQEUfEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWuzE#5 endstream endobj 39 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| ,-maRiY#p qg'==M%%~5IPġc RV)ɑ qEϬQ?EEVEϬQ?EadW ~aX?ثQvEϬQOEadW ~aX?ثQvEϬQOEadW ~aX?ثQvEϬQOEadW ~aX?ثQvE *xX$)d\s\ ;>L^%pI7*/VAkDRuA#"]>X@=lJ* !jR1JS z 0=-AKEQ)hz 0=-LAF)0=8S0=&hSAFAKE&)0=:nRLB`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z 0=-AKE&R@ (P`z #ʇ55^+%Ůi)cnc^uoA%@p}hbVg<&B_>\RR }lG;ۓ eq]:/]WrC1vVEHOqu2g{ tQEuIKE%b EP0) %)RbQ@QLAEPIKE%Q@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((!qZ@f :O2X܄yr+t<(?{Ҍ !rnd~/9EtvE[P.3L$6IESQM4'TWVV PGqY_ӿ?Sc!"#@UFSV~q5 <xaJP3Zsm̒9&@,Ur gה!sS.Ӱ9'HY'oڅsoyjMx2F O| }6#! &Ѽ)b(QL~R/JZ:Š(f((((((((2 CGB=k>KIN3v"mQ3R)Re܇>ޔVe9RAc? s](%w5yuP'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~<EVL2~,U'L 4Uo<>'[2~А=4fE`NA"Ļ*G=@誟mm[}}ngQgP*=G=@誟mm[}}ngQgP*=G=@誟mm[}}ngTL8# (((($*b$G壴{ivg0xmSWZ+gB?ggB?gW~+>o\|KB>ʽd[EvzvoXwjqޱA^libiUvj(cp((((((((((((}>M _>GҖŠ?ntحoli3w{/`3@(H?Ȫq\b-QEQMwf @?r+JHcb\z( (cǓj^rm-ڰ D,G]x` A,: <.cF1&$uVQE ųΦn@Ukd0;r+!/&LS3STT曺E,<Ǚ3i2O$߷${U/5nY"^,b[95gEp*^8=`$-О20qvVwՌxHFɊy^>jhA|m3Í6ӤA7wHI08$YYI$_E7!d@2moq~xފu1i]0ZA} @ev5kX-uymV_1JLzܢ=r-MfqI$v0gloG9qJV;K0[ܢ(+@'u[ lg;'ےN;]W#F6ccUH1b:*Ci6aK")uGAx?>G9zEcPzIF@n?'zVƖ4tGuV8,q^?>ѯb#R4.rDa@P뻜 ]K(fOңkeFv"8b@ }@Ǹ>jlf[׏R7Y(B,'+{Y?Ҁ)$MS4=JT >֣[gKҮ9fd=%1 |ҬM֚Ȑ<(^ B  ?ER.l/&@o2Gjv2|H@ ñMa%q~f{f} pa8}F*yipa_1 b2_ 1Y\eX02ygT Jl"y~\+>[\gϽ]d//z6;}:} m:wұ7hܙfgBv'<P ?oHU*|T* <@U@T7TQEQEQEQEQEU?گV,hQ@V4I}O.Tz4Eyش/no>>y{nlvP$2YEo3q/dd> }xf88Ȏ ҟ0sp<^ם_y?1+|=>OK^3ogꎬ#+O (;Eۼ=_חZv3)ܸ;+qٷ2ֶME"b`77\SYi :8?+ Zϣ9,]n[{xFU*O\%711xۼրfo1~эrj&y+ Xf v;c=vޭջO N#!xS8byeuHgcu$׍&ci|Wox 8xpN0A ITOU8þFyV ( a Rp0 @Q@UԬ"t:r 0VR5j((*ɓ̉ր)]Agv9׭o4/GJ`0sՍۿћSTW6BL}4um>K)EB`è=XGJލQMY#9S42ݝ:O,u)i*ʨo ۯ Y^^^O$/7"q-@haC8 Gҏ:?~hm52 _ʼnT!|F9]"*LQ%kayHF>S$n:?~y{ 6wWK7[#bHѓ9ia=2Kɣ۴Ld `\:?~y{[Y> y/e8m@P;>+yRJTe(z@|ּqX~VlbkE?YOI%AnspqpA[P5y[ì5 y@W%HHJ|Q@ھq}iSKYK#' U@SKd1,#AVm&'0yU'=?~uiZ]N`fICnz}3ڙ,4hvghH<èzT2_"YlzfJ>F)ͦ3qO5>y3e?cp. )tʰ`v)%:4j,n %%\eH={Q5;6QK^i(# i-t m gv:f2_ H$Y<~68U]WG0r ŔɼE#DS9.F%mI4;!NsZ~`HMki+,IUxeCzvcB_]0Z^&Fn9r92z-vjvF[oOm;NH N1VOmشt 锬o. n'?(t#{ 籸I3;1AV%/!tKo|[tXo=s4sW=+7.kGXQt4ИRbbZ((S%-RъJ; kȃ"YP0#>WW2TP) Aюz;jwrG8b2 ݹgCEbڊ(3FeE,$ȓD !A+|ǩíj1`HX,'!nA!Q+7zuŻf뚝օ0dx #;T`Uvuo(fB3ϧԵzɹu[_Uu#{ZFfTPrpzסxemFcbRuJP O^(hRHbI$DʲJϝp2qWKP%4g"HN+mlXPX1֟˦$oa 6!Xnx11@$›uDd֑ehD2(08${UxMKI-=y绾xtFj =㍣5[/M4/xnVJZ<=xIq#2 ʿ3(p=iD`:(N^jYjwvH׶дB.؊BI -`M/Tv&llkh`I˱QX_pyGim2Nz DWF VSG$?&uf);=1I#kSYK cFHE$ RHbWSHP QEQEQEQEpHR98TVQK1@$$r$$:n+`z{_qpZn gcHcmqnٺui`,.(#nq@[%/du[4F S[8aD$\Eyua=Q }0+=oo{%OcdIr qf۱Е@/n^{rHl   Z*@ +յot q-(ϒ?s5GZ'7ΘJ'VڸQL u{eq:+5^--gh~v\v-v7PfmA 6 ى)RAG0rW?.N2Iq}>X߃wf,d~U-<XVDZaHq Ccsn ֬u'68!a,:WmF3y溪(|G5ִ頂 4G"yOVmkIhʹdd_JcI柨iij&w1@1;WOEQEQEQETrdN'5% ~/Bi8?$u;bS{ؐ$Y+ }zc0dҵ[ўKq+FT07 jծ,&Ե6t0[[Š fRI֥|9ֵ ,D͑I~79KǍ;TM#f]FmijlpkF<4"mTӐlѐtr 9Yhw7ZaAdy,2`l.0:GET:u\kDdLT0<ʭEQEQEQEQEQEQEQEQEQEQEQEQE2ODwtxh"N7NB v8օ%y3J畿G%Woa#5I0[|IҀ% Y9vUW` zpqK4PidHԐ2z }ފ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((dw>E4#yzX+nm䵸xeR+貖5&bHO04#A%;c30Ͽ\T7b2[BcpAy9k-y1+cE!~_}sFM;5oQ3A$ꠖ;q1a=>rDWRK [%C#`A`uyخm P#8 [x\]I-ĎnX)%WH>*IR:pթŸ,SoQBmU'|QG2a3uϭR)4J!K8 +r۹ ѵ]>8Us G$a@<E2zv[$Kn Ӹ 3Nƾ*gI4b,2oU\*09c{ n$fv]ʤyU-%HV(*3>juԦIFKFGЩV>TgExQEQEQEQEQEQEQEQEWK/|WU[y#fRBsXy_:IzQ_ }(QEx{Q?הinD 0?Z_xkS?'k˼)'`c +5eo9vgǛ=V|\dO9Miٰ[Fnfq@RV־Z\~;⚵ޞyqegݢܟx? [&) #>5mvnX`2S>/vZGqRW^f`0zZqxR$㴓T&d{bٱcF*Z]ǩj6i3\%>lیoF ۉ#П9Oxv]ΫLQA1dWS)vkJo&I&du>V΃ /n.D2\ɳ!Bvimu0eUy(1K;Y<9[{/4;Vw,x6 Tmp+_FԡVK$Z F"s"$.H9_|%٬lm#08rxUcIZMm 瓞I NLpmF Pú x%ڭ{#3c6FAO罰KkRM ̈9`A=Ccz֦e{mHV1Bz|qN=in+o~;GR  862g!vk-ֵi+qqoȚYB5\.pxֹJϫXgыIYnMW+3 RbI.38'@ l;鬭W y*yx̾ƀ9_ jw:U=դ%lL{1aepEV|A}]!%icI)e/?+;3Oa7uwyp#1G%ԁhH%Wu d,m[W5DTۀx?Z׽_6bMkt(2AىϿk]O7)rۣ4ȥDdy՘|"]EjJm;cV݅a~]#3z( ( ( ( ( ( ( ( ( ( ( ( ('hOM x*9mtɎ~u((wG3IVͱ+Gm[ߥ EWvOc&9td؂ı¬#mH,ٽOm$_t 8*杩ۻp'$:*aF.\O3ɨY>Kˋ(Y$pwbpGrI5 b@JJF)ܫ|JX^mPZ1gxp(A$c dgWYCaOm6ʼnC496?H+Gq^kQ=J lz$ͩx s?n2%׳;QtK]߻bg5xO[ḭhn 2\!&<: vI pӬV<3b '֗{\~=FJ9eLr<1\UBdX[tI |sȮjIt-FW ;Cg,( Ipw`=0:UJV­QGvUͱ+Gu[ߥ E}#ثbE_ vܷcQKdvTQQX~uQQyyW\AwK-RTWR ʰ၅^ץ^.dc잩/u um-*lu5ӞmR1U c-Lb{ݙY.Kn-8ZPEv%[$\5жܞX$n[tQ`0+ODۣfg%8$~58-[>r.PpҒJ.I1jE]5]*-[OYe)F ;AeXXofH@BI$U) _vbD̍$d`YSr:v$\E5sZ4h3B_qRK2~QYKKalݥh՝1,ŎO~I1Q@PEPLR@ E-%b1EvU OPI2( o]MäL}+ӒbOSסg F98{M5(I3ITOonGY3]Hc eHJcُqL44QpkЭXre MJ3S=|{=gI ^'J}m5Qu +Oe7)P4)eh-;~F_1nI7ASQP%l,`{y!AٟN>lEQEQXً^) IKQ噜yێzW/u8l.-I (U3 zgE`6kkY[j 2Rꀎy\9iyjQK9 ,9a@h(([ۼ9$3%;V4>#Eo[#;vCԊjVwż,8+)*Gb# tV^ê\9 ch8;Ns${f%j 05FA2(3N@u┵$N4ۥD*۱$A[V0Cun57ĤdQEQEQEQEQEQEQEQEQEQEQEQEQE2ODw9Q@K9Ģ罜Vw$#?'R.g 0|RhsP.v8R `su+ c\IdјkYcC, e>zWyKEguKm -]Q)nA$}oivWUԬNda!Ch;8^E.QI 4bQ(!myiRъHR(PEPEPEPEPE.(S(P0(5=A'\gP(3m@STs "8ޤOהhZ3+“ hnp$QJ |烚IG'ecNBR3oqCW f;۶ LzbuIg:Yp@1篵z΍k:);i_ϵ|۴kOAZ{WݷhwGOf*ة3XzK᫫k B/ᬭt51f냌]y$K֏W2{WK"NIk>*(UP8 @g w#:TMsڒ:m=G"gD3xҟ{+/]HFFldQ^M(A!U?*|'-om^UO ghX1-@sZ-6lGޘhFr39tW;T 6CgofH;d4{gM_^j`ST(;-Ɏ zUuu\\>|-NzMzuedܵҕ]Y!qSR5jB`d(y"uap!so#]8&Pnu O!9R1tojޢ((((((((((((((((((((((((((((((((((((((((((((((+?[GgD%E{V1KO5! ǘ 2p=jYKHdHtog^;I&$L;ۂwEyƻۿ tbE%Ռ~vb:N 65k=C:VESK9b9buH`;xƢ^H@;F᝸^8]2Hg> vQh5ȶžgYU o=')&ߩC ȼ,Ap`WmE`5o=6hH(u\6Z[YQ/d$0_>n0s >IX4xt-%JPe$͎3ҳ$&T:]$ZDRU $QT`$ C8S B[)#"pL|.ppq6E[ CRYh2?$̅O]xKO-L6ڊB-о&!a ®1]P hz޽ir,v+|YITn*-?J+9H5E16e#8Sr=k(6Ox#SF .1;;_18Zke,]ތ-l0#JE`KKLFk<]?RFTȚ&<'T@0M^x߬7EP!x9@<vXka,m:o11 zuO [\ kDF@; b(m4F:Y-(no& I 4ӯPB]qu&{oT 8נ@woviԤ7рi-EÝ3GQgkk=*!PpAx]]QEQETich2G< J'*O^§8/^g6&60ؒwے6-mo7{V]Ws1@/CMҬzbѡ>`fPP'oPH(:}j=K÷^(5o &k"cÏBHgs]Z6.|BmĶq\G̥ar7/5~;ؼ=Ǩ[ʪEQEQEQEQEQEQEQEQEQEQEQEQEQE2ODw9Q@((P#ە%cO @wUoIA$nNpp0NzѵY0<6Yjjאg'p=aR*+>$ӷۻU]aVg#종ďF #wZ.yH̒4e*Y0 cVկԮ Y4,͇w=%b及fy=>(\Hl ?1~6Vmm/v?1ټ7_M]]Ke hY 2GOˎt};RXԵDچTf`7$?Q\((((&)h1EPEPEPES($Xjz+*V [Ko2T(\K0.Jj7!s*SJA$$jY̷-ؒw`bÌXF-# ?G+ ? \?89[zNmZ ؂8to޺jc3kri|zX|`2[h:x.ww<*EySIN힤!EF*hTTUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@jOM )hsbPErqghș}>ѲKDm !lx9P+/onMi[y=p3$Ȍg%G^v&uMf}.,Sym"||aq;:+˼Q_iw:z~_3,RD{]Nii*C .# KPwrzǪΊ^[kHV 4=j]Ñ߭s1j:W6պϧxhդ? sڀ=׼EUkh/tO,{yr-ԒHT1ZP#c8#wWFmZēii2nP$=ʀ023]Wq6&YtցR tH:Kk] Z[%!AJNW6`.F8=@Er׈H#MKSuno8#+ r}SaPxBAV8wid|U Zk4|zPQ^uQ_m[Eɔnp ^N}] h}ѹKE*$m$K97*IBX0qU.<9]kjrimZT"7\uJҸh~0U&<+:@eA3ec$d~uɡөoG Ť׷6Ba0D#ӹVk.*=6I~9 F1ɭ( ;QF,rw[M FGXV62*.+`98tW1.a@LdEt`!E]RxF v" qFNHbA9''#z^/ ^_ZV;A(S2 瑃nQE]VVw=P)YI$: 2@j7ާ!e'?k{;I#^SI>RX!v]84QE1(PLR@ E-R@ F)hb(먢:( xkS?'k˼)'`c +5崷 YmPz/요 lnU -J* w< ٸWucҴ[-ݣ|38}Wvu+kk_-.|ioF?v~Vrg ]bQ RwN=4=Cn<kCͅ՗7p-4\`q*v}F5};AM[[x@eU-'I>muj_Iwvn F0B?kb; ;KϽ.|3"nlI3~sZ˻m׉WXhп̻ ]ąVcWKE`hz6j/H'40[4!v77q$P6r99h Cf(u ersX3U+MH㱂Y^Xp&Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@gkBDtv6 {֍HOMSPuf튂fQ0A8o|iwkcoڥpm"H|9 r զ%2꺅@ٙy0AZtP?KNhF{럴˼ة={2~o=^TR6#nm8kRT^ Z)MV #i洎ƤҬfYN?3^LU A 5U$2l'rDjW9'^ZNvГ-ٌx*}4b$365ܘc3YJO$z ,׉KY doXEJq]Yߢ!f8k^ECjm$!V 3cih.qL i%ìqGXL[\ukjqiQ !󌀖9ou7F#qeo1̑+ldqMKX[ W[p!R#8( r[ܨfo$ ` #?z`$i2cW$|]ӬM6v }c5ދ_^iW2nWlzd,W-@-UrTwipDDhQ`(aO$LQKE0Z((Z()qEQE (j6k֤$XkTPw?w?PԦ{}2h"fSq^no7zp)bi33Qj=_gg'7#~$1$%Ns1޵ThiԚvw?w?EEyKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EEKTPw?w?EE>I_֊E4#E((J)h 59*N2!ե *FbKpGL'Ҹn :Z:%Ԓ#?R 1ySrQgGyyi%Ԟ\1YN9AzgOp%e]G\AǾ3'3j `xPi46rÑ{WݏI+HTwp_wld xeOCqiOL5Yy.DKKy浼i텄w+$&2ou'5QKEQ"QKE%PQKE%PQKE%S)qEQEaE.(%u OPI!2( ƅ|*rAN3^xP?כh˜XHU*Ҍxy>Ec&zɚwD}T85jBR3oqCW fG87h뭏'ג;_yRlBz8kKnѮO<k_/"iw>',4q̵<;f'T$kZOso&7 tܬWq޼WV>_MyY[Aow3$k !b޺? kjHeRD1}k64[:ڼ[$6R\rTv=JqPM=֙sg,6tNQ_lhr #,t=R6 jHvE8 #5WJ-$ffYvy : OEuکCex?} p}a؎+;WNE\hZ{3mMYWT'-om^UO ghX1-@sZ-6lGޘhFr39Iqegn:]\,6%c==he崂H'd)_6nڽhf6z (^Ok AXD햮J"񆌲08ȠxUFҍQC)"@Z;X/zö,OTS8`R#nFjOm'_,PqHYEdn'q V!!upZGN<=W@xNҴsvgsl9-젪U u~kg< 9-|;.#j5]K<]12KC8$9Hx#hHL\4DL3fX}C<nIhtmF[Koup!PHV#֚j u4猣xܞ$\t¸ˢ>VqX8{o,,~`b[M鷐.tˉ$V;űJNtYu b0^?*,R 5CuF:_Rѵt͌BIBX|3Cq(Դtx6m?վJ1(hSGauen=m"$4}'q|d/ ўO*"w[@3B6'wdwQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE^lmL `vbNbM}&{쮯dA[]ݏA8S_5ȺH/ض;ۻf31i۽WWFB 9h6 s61KO5! ǘ 2p=jYKHdHtog^;I&$L;ۂ+LxI$H.^@F@d׺Z5rkk;1O޸mw׷~2ċ+m*K[k |*Ae!*xK[Rk{g}jU<[ I# @,|Sn8?@4#y##j֗6G`OB9?2IԼAfSg\YHg]ȱv~a"G=Z. [GoY؋`(h3 i]6?my^hޑ]\ Z{+)#TB ڀ:( ( aon`ȗsRich2G< J'*O^€3a7:-mc}:ݏA@szU?SSVHw pᔐAAWz?lF$&[NrP394z]F̞{V'c'f=2]SiN"*2JHd9 KwK&a0kBeyQPg$9Xg}w_-r3HԂ3oy9sڗoQkVhVM6D32LJ7RCIԦtXȅ[v3䟑=+jHn<ƲFJ}KėwW>!K{6b[8#fR0?^UR-eUtˏJҢ((((((((((((((MAK@()1KE% m |Kqaubs+sN:G h_Q1c\GEk:\7$PJ28 z _;QEE%Z((1EPEPEPEb:O$XkEPg<7vA5LH!.I'qҽ;im]Jz0k%MF.xT⽜Jtkԩz=֧u{vS1LVI5,T7 09<?G+ ? \GeV_W}EC?γt=AŝviW*ƭyN6}P%UQBQ` 8d-t4Wx ?PU=p*:u䷐u7ْ%0?Y (G.1e3:U>w,m7 @9ؒG֭@ hѢ1)HLzP(TQUz u0EnƱvv 1'$I9`'eDdu%QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQES#(G$FU@.'E"]dh2}G@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ CƊhL-p(Dդd_F_B4[x=aχ{'2>Iq Qd4p]?>jijՌR,rJc8A$u[BSd}c[Wbc#~ |wcTL@qȤc3|B)@zpkU:힑ok| `E@hSb9Y"d 4bZ((Z((b`bP)qF((R@ E-RPSEW)QE2iR$CK1Šcb;@pA?y0q r%:r<Vn;N-_Vޓ[k{v !&2l5sMcU)<0]7*9X{]QU'U,VvޕzEt}'J.QRPQFid~o|gzҀE291DpQ8`pA(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((kƊ4SBg.:QJ: +sKQn$7F%봞LpxiY'tꤪcEee ,,zՊmgXӬ.V[)=DU [&@h}[E-9.ox$SPK(77s Y崀GwK`zsr7+MX ZKpOTݹ8=j,˺9kBicԵ#mye$v# F8T^%<_T[중Z UTy{@eTg4M:m>Qr7q1;w3g=+UT:5le-9$pgM;1&oY [+\6Ȫ3DWuh/.b#k} d/vwo3Cw/$ @Ivx7$z?ڦ(..oZhɈg|=GZ&Bѭ-OyƠ2[*y$z65 /7Y#Toh!@vmYjzƝ'n`Xbh>To1Bgk'V}b ŵrOdo%y+fXAͼR1#4/>TϷ܋q$xyk.^%]aBg72$r]wXq@ k,^\hn\"yHXr9Ȭ v·ڍk쭧In%@\X@;X:+mWO_jOoikhahC}ߙ01noZxӵ]r-cDr3I'ќ*Ns@,q]j^c$p*l OCZ)\i>uf3q`e KzsIxi5) CfH٣_0t`H%7V___^G-q,g X>$r=(o^${Y-ҡm!3M"a+NOWӞn(blvrEHvk̠۫ my^ԋKof\%UJy˿_iZo%s*4w>g(2; N+OIQ!ts''F |3 E}L]\v rsii|v0K+"1+Qs|p3( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( }jז  $dЊY携>%ݤs %}H<Z^ܛM6;/uy`[{xg IJMh76]&XEV 0?1 iHOMSPuf튂fQ0A8o|iwkcoڥpmc[uOF-$LѫIus$v f{yܼ3۰WBA  UI|3l0^_EFUmrNN2׼EUkh\N"~Z(}A7- ]Xm'8QhoYu}P|5[V=+;n;UKBQnnڸVh zdv"2Ɖs._Ha% rSR\x-;vw"J9U-r⧟XpFH\#vA9R'O; einyk5eH!zhεjH4#f021@Eo#e6\̃I$rrPW}h[_.2z|I2b\߫D\BdHpIA֮ XX$w/u9Fr1b:tt8t&KdK YB:PQ@Q@Es ,"8ewg$hM냞7+nA@΃j^'կbDq:yt/ wyky/_0#d0o,p6\sx "[\ Ro$B<ڴ$6]WPh3:3p" uphE?ޛmCS{]:%&dcGʅqVF |U -6OkIK(yY؀ǥw~=ᵃ[ܫM0D33G8,1)ìZM.Y6h,ʦ7@oJ57[F VҲlf q2s'[KȝGy%FIpZ Q(R@((((bZ((:jO$XkeQ@ m54WoNշ8 BIGTf7" 1!S&[' JV['!MbB$ ~vEȰiKcZz}QMyk rdȊO,(<(c@ Zx{Kȶ%O8o2D1qjDK9YV;H34Ea^,Ҵ $Il^!d9I0J{%Y.w/86`8c>qE%Y̋pysV]u@̖W:=[zh\xl~- G#3!n3 xMLַmoHn G wlQT5=V-0@ ! @$ HEQVo ٞ&˂,q`7hj@ZYoa.ۜuW%NLTt#c/+9 B;rI:GKa"heCxpHHi$åj m׮"TxWH/:qn Qq oL̽pyVW7-'P[K$Dl&u+e\1CV:yi!m᷅K:x*yd>Úڢ> G&u{{9yMGbHDICո͹],-H7"\c&w `@W%k(nHx cg޽=iRH`GX-AO4EeG+ aCH`y$FUA`A1aG,zD׃N;~90G];*+:^cBۊ)>SdfVܫRG\˵ִne%ʪ81!xNw:ɽͲ ;@% qnUOR>D#Eɸ .s|Soo,1 6PT1Es [[0Ut)(`QE ( 1E-0 ( ( ( (J)hgKPI s ($`,>[} Kpe$ss@r?"BQ EIaP)OBC[ʤ ӿҥo}G+m\-^bmqo*|,n^yVth|/6U'nTm@8PU@,1RsP=G9)`1[sdZ=[2լ8miO$HWw!,w.n sK{X0 (bfk5CYK11,dzԳ:Ȑu*vMHwͷ/确w׷~2ċ+m*K[k |*Ae!*xK[Rk{g}jU<[ I# ]=28bxDi[|T2}N@(ϮtKl6ke=z%Ŕuڭ< gr*sŨ=5vEv;pq޻dG.24}w >kmik}?Qϒŵ@-v) {O\SEqvŹS\FD\ o BlkvqޤK{$yi dI ⻚(qgZmlZ;}UMBi;+\c(;:яUfhWOyq.aup.ܒA-'?ɣ3"MW\Xʢ@GLFN8Iz+m;p1FNLkrzuTS`q%9>ϩ-wQxF 7bR@;F{x$] l AFA;MtOA ඔ%Đm!2/*p#: n,:::~ קi@Wmn"D @=)r=EU/UP N8*((((((((((((((ҊЌ@8R@̷-[\"ofBVt7Nog%ķ6w{)Km-ՁP犨yѵM6[9>X-4`UA 89}biu4D!4.P}'=2]x~v4H!S H޼WH5E0&:~-֖6sqqQ]W2I `W6DX5OȎNR JC 5GƟ4:8R"z9W5+ymDچq|bWxtw ܃xknZGZLm1 9΋;ES$(P0PEPF( (Š((EPEPEPKEVEPdVCP܁&[} G?A(%3Vm5*GpB}H5|A' =$7iM__)yX hRXgTXmp;N8 4׸8G5D8v,SOn]0{&XE\Jhd/tKFvt Dx<6>x hV?1HDQ%nY/!-ݜ`T$*b{Sb9I$2:0* UK[!{I9p΋$@@@;wWm$}l%¨Dmlqhh}kZKmm~=/# V6|A +>?I:m[VkmZѧPL}2@8֯ĥnAw 8TD$@HQ<@x01@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@UkVhL0V9#$gG>P+/onMi[y=p3$Ȍg%G^v&uMf}.,Sym"||aq;:+˼Q_iw:z~_3,RD{]Nii*C .# KPwrzǪΊ^[kHV 4=j]Ñ߭s1j:W6պϧxhդ? sڀ=׼EUkh/tO,{yr-ԒHT1ZP#c8#wWFmZēii2nP$=ʀ023]2IDM70z}yߏZ[SVsqymm@!@dL ؏Tjz/\[6D_ GQ/$ex'9#wF2# )B{o k9̴++O> ACn4e4v H7;Lynݸm)D9#nK4P&H!w;2N$ A.&+=^໻1o/7$mbr N _%7t+E [~y],Lh pŚ'Yʀ@*Lwl~?#IM"E7)⹋[[;dĚi68bb&+BOYsȠQiIR&I3K[p;ГE+ȑʎ6X^il|[iͨj5x8 E(T:`wUW3m$om7xzeQ@Q@Q@V/=la@9ݷ=9Eyⶭ^ic[IqS&<ϼGQj|-q.[x& RuXcXgNEs)1ݰ4A^dsY>*tYk$n|@tҀ=uWz:ضm,/,nLso'jbvWf@\2)Q#m G9hQEQEQEQEQEQEQEQEQEQEQEQEQEEҊhLJ)GAEjd%Q@DŽ0Zl̻!=˅P=Qyn~{uSI/9N%A21}j4Kkq9uAF2.OFzxI֤h ;DP]P{CKA\֬SZ#r?9]#r);n#0y GS&_~ٴcw-JIk%e_ ,@rrH5 `'Lva+:.1sۛH(F%0SjM+Vkp/,Z;gh 0mgg0;[M-5tVm2JQ0jQ$;K e${d,v[RS"RqN~ow AZhRUWP=piC袊`QEQE!Q@Q@Q1KIE-PIEJ3@-QXQ@ [} Crd99*i?շI|1hy3$KARO??:rDsM0yRܤ*HGQHySAT;f x |h,w6bޠ ̚]NZ{1$'c:a⃏C]e6~[M&u_fs<҆Vh@ W=OQvkf}R4"1ol2t,vgErw[H[حf[ŒB1#w[^Z@lu'ܜP1F5-u)$cʷ-\.y I=zԮp ~9u]jZ1⾞k9w) }<J@^Cs :$g~ `Vk;O鍭d1i$WVqnEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEi ^]G0ZWdZ4P?"M>;I5MBo!KYW* F0H`⡽Uݭzj5^3͸&`͸= c2nto?>{uqr'Mq r.Fx>Khu+l,pB!E<Yߧl~tP563mq\>5|O*A,ww+C]ɸ>ws8@W#jV!XRQ ci/QQƖ;~#7l8^+ ( ( ( {kW\On[Ku  Պ(|3v\7S4q<[p00Oe[ij*H'wf$cZPᣥj7:ܗ8õAcS:ՙ|?cpڷ*jq6~Es*/Z+YY,̱X9*EmXXG89X ( ( ( ( ( ( ( ( ( ( ( ( (QCtptҊ(4ۙO&\kW0T5cU4o|5wrۧEIep:rkC X_k:RH1, Ř={֚AD8lV*XJ\2ijw~u;gq6Ͻ+մ}gOk+ keX0@Po iLKj#4 {qwJG6Ta>rpNkSOɧ[q5Updc oOIt]-)+^qꣵAcl Ŧ2C`y+Y5ri:}ԯN+ R|)$ac]׃/SIӧ`[>唈ՃgjҭxWI`6Q'؏Kme:5+o%ğhv۶ǓŽHM4QE ( ( ( (Q@ E%Z)(`tKRT5;֏;֢$3 ğDjiݐޝؓbp2"HY@)gl_)(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(_;֏;֢%hj*(FQ7J)(_Ũ5א$m! ֦E+L;b+!ROO1s$FFqEռWmޭPߖټhNEz:G8\*Krz\g_xj _ڃ%䞤jKK&ݭ9\tU2Oqt]u+N@a2;ElrH+J J5KiL )fa qSOvH"+ʰ>E.(!(ZC@Q@Š(((aER(((($XjzO4EP_[} Mp3Bj[FRp$ Ǿ1UyͿS„!G0d)HR@$w7> u o*(F NJ@qxv{?BH[v"dXpsg=W]nXc2dE'b1u3΢I+Fry"Ϙ>A&`qڗICaZHxLd Y2J-c чIv0Hy g&צCHb Lio?vI6{9??1g}e o?(/Фe7 ˜Э\]EgwnV_:HQ/,p =;iwrBm X*WϾh׾/k]5do@vyRȠYH!mɩ4]1O@Nb61'LXj_^РXu{e&:"?p1 * n-N5c%T&0ぎW⛨.VnRA҅NB)>jmZZ}NA| E@4`mo8ܹpKU }%pF-ٌ6ssw$gŵѐ1CZ2Z|?k.-Ψ%2'̒PXgxjd[[д;>%[&I#LTN0Oi^%(a΄[\D/\G\'=bm+b ʬnRIݐ\*7-'P[K$Dl&u+e\1CV:yi!m᷅K:x*yd>Ú|mu.tcY&l!mYoE3i59ؑIfndb<`g > G&u{{9yMGbHDICո͹],-H7"\c&w `^mgeAyrWPI|_>vXع8p1We;Y^XxH%#=M8f!ONcM677PΑ%3@aj7Wz{K-E,!Ա[ԟ@k>WOyq.aup.ܒA-'?ɣ3"MW\Xʢ@GLFN8qmjh3IdPXzqXQ@c%$15ӧĎF߻~Lv93oNԖJ~>,]?<2;`V=8%/rwB@Fw,@*H`21Q!y.wIe: ;~^NG˜Sto_V6[) 0=NUq_jMtOA ඔ%Đm!2/*p#: n,:::~ קi@WQEQEQEOSԠҬ4Hs; $W++Vh%$F\`@]VbQc{%EY*d+ ybn'{FlfkbZ^]ݬHx d # Vφ~ӧAi=%n7}őd9xp;P[GRYHG$91늆Ė6M,`-2 I9sY~NJu wM%Ō+)s۽f^F[}]Y4XDzFS?ÚߺJZY 'RHmvc"mϒ~F[om!pOR2k(m/]_X-4ul⸏-K#n^kwx{OQUKķETӎ?.=(J(((((((((((((nP(#ttVBQKE%ilZww:\P3Gj:ŕZ&f2u4BCF]H,3EUEV{Ե-QHS\L:ì,oc7n`YyH:zMedfvi 13Ԃ8|3{SB+|;F"?2xdl+ǠQEQL((EP0(((QE(%4PMPI fX(j[ .A#TQSyA"FB0Ǩ<qOh6['!IA;d?J(0=chadd c sǩ>֬Q@sC8#FӤ{T"1AsWeD$ H8?KEWAl Gb(8JF$ric(DjO@,-.ft֎@ۈJb94Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@28bxDi[|T2}N@)PL(F.ᝧz}QEQEQEQEQE[$ fT*2p{tLsf`DFlIE$D2W>IE5I6Emr(ɍs@.׆6\PFG Ԅ# EDb+@>@zҜG#3Fw!eƟEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE#tE1VaER6z:K܊b>ԬLΤ3C t#ߥhhºXv-,#i'64D؀۞Mu4X.UoѦbğSڸtMrY># #w By(T08[HUtaod yplUتNI'2xE i]0# 2=ʻsTgRE4SheI1RQ8;b ( )((QEQE%4QH )3E(f:z(((F8F#1ߟAN[} CrT~ pBrMHR$R~`Npi@Ė9$V(\)Ӏp}?5 7QۭKE0MT9pǡ hc1r 3@t^j'\.ᒣӑ$*b{Sb9I$2:0* EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE2IDM70z}Sc9Wtn#*r2"@ȥxđH!{uY7"F p= P袘D<+"PeA8?>cMJ4IXބ)^DTwȪ8}Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@(QLG::QH: *Š(DM]N<6+nu(ʙ žr <sSi~*F="eFv @8Pyl[iŖ51cYv(b&ugҡӼ*w|c4 [Z4mRs4H9;;?Exm zd'޼Ok ̳G 8(6Pg$_im$9eVdQXzwmSor'(Zb<̆Uo▌~>0yko\3Mji&[Dդflgydj>[YiByϮǰt t1]G"wSH.iIE1Q@Q@Ţ3ER(QEJ(RR%PEPIN?3tmnjgizS ('~?Ton@,''84> KӃq5_< % o'Ҙmqʹt{}i+ @bdu.\]k}c=\,^"St?I:m[VkmZѧPL}2@8>Դcmu{}=ro+!uIG9bA#5,-1)[]:hDz{6)@Ť][q#c@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@VkV]Ol l2AzdsEYsH]wHN9ג¾$z-/onMi[y=p3$Ȍg%G^v&uMf}.,Sym"||aqIi&M:k3yJAQb(  T7 RmRfK|Y$ތG?Lr(EV=?]m/wr)A ctȮSR״n!MI6 L }ܭ+Ϥ^\\xdgCg 8OJa,QOcw>xsNl^hwҬYDlG8VCXIRFEa" D H\r.8EiYxKMY#c=&GX`=c^p:94wq<1K%ܛۣ1~A'<@Z֙zڍtKZFf1l{`>.# KPwrzǪM_˫ڑ5+Ym)nb(`6>zVLwZ4* qme8B9^[kHV 4=j]Ñ߭s1j:W6պϧxhդ? sڻ]SHT3M=^+ AG*k {//md|yܶA9''p@Pk"*Mvgu9\pڳeӴSNE-hQG]V`NkCPQ.o-$)4#nVP:Ё1UG no&Xkהy BKi+Q0eŤ;̛XrIWSϳK >^\i$tVT]%`;ks<( p6m>PTi[3rGq SoH)RG'i#,N8{ڵ7߉&Ӌ%.dܠIz&3`:df|5ms˗l.d *9ڼOkj<ǭqi~^9޶g e2&]|Ǵ*H N= ė-ޛo"W2zV{NC}owi!,@0v3ŷx4 [CNȇh rKBɒId'~؉B7AItOmt;K)Z vOYCGk~എs眀یGixjQ] 1'Ww&0~iҶ'Ytassܙ c.A=y-q%]?LM#Vzwvc &_(nH8KۛoWYe;kFdž}/VX%elW$2`cr0R; ᤹o%GId؎#9@d4cD_i4, $ 4IoPޤpz-mo?j≤[;ቋ4|C =dQ"#Zɭlm`a[m9<cNjdNu;Mbu[{(ޓ4Y vz9l|[iͨj5x8 E(T:`wUW3m$om7xt>EC$wn3~RéMez4ŎY1;de:@Q@Q@Q@b>ѻ΋6˺QsӟNq[U^햯ܶAA[Vm41Qsu-Cg#u>}b-<KupӺȬC1,3cJEp_L]0dr0Kdmn=m-Ot#4=%O}PEm-qM O&큢 B'sψ/V$p=s_)%,tgb#w>:Vs]s9. ;X59cY7 y򦨊poq@vF~[in7&QH7{1;yt+}iF.Z[tf#ڳT^ Z)MVGSr}{ l P|(tu :)Kkab!ɡV&XL6)=[HR@$wzER kU:57Z+ǓyG*۷C"ÇÝ,>=iE5+w=py")<hdt4Wiu/"G>ʒ?!rPNzz_Uwvg-jpŁ#Z%ַ4qpK26EUbӥ/#HXBPܒ@䎴EQXuHh`437l:0<9Z{,0ZE4누;,{ |Eci^%(a΄[\D/\G@mY2uerI2 :G-eoyc4 . v(f((^BNZL]2,wgs׊qImqpd H—gs>b_zѢbMAqmsnTMop:nS h((7^%) 4/0@OG 摶c&EG<-)P:61FAjڜZ>-K*Gyq]0PH'Ms[.2#Ԏ ehdH +f=2]SiN"*2JHd9 KwK&a0kBeyQPg$9 +)kId4Ji!K%Uc?>IҶna(#NKy&c+Jcyv\vxeA{D*F_|Ozr*8* t9k VR T210?*, *F7*`6 W(*΀W`z9#0-S}ϭtvrgqo+J~%Bk !capqyCRiws01M{cG ~؟1F}=_hssht6¯jF3(#}*YL\ 8Э\]EgwnV_:HQ/,p =;iwrBm X*WϾkAl Gb<~׭x;Ba-h,py|7CȨ|,u;kKJ-̕PLdp~C;סEVV5,XP$;K1D\&T }Myco-m5M \%+c,Lg7ۓ#w<]X[6cp &1V#:Uf8X]j/;o ,-1+vWkEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV~{>K{X0 (bfk5CYK11,dzԳ:Ȑu*vMHwͷ/确w׷~2ċ+m*K[k |*Ae!*xK[Rk{g}jU<[ I# ]=28bxDi[|T2}N@(ϮtKl6ke=z%Ŕuڭ< gr*sŨ=5vEv;pq޻dG.24}w >kmik}?Qϒŵ@-v) {O\SEqvŹS\FD\ o BlkvqޤK{$yi dI ⻚(qgZmlZ;}UMBi;+\c(VK{ShXVso$*2ԩ \TZ~you$VsDkZbm8F;YULzK.@ 滆mJ=@ǛhMDŽbF:uUyzB[]K7##Ά8 =I\>ppXHmt9ǘKSfujh ?mkHmb| s߽[]-忶f_21`s#8 }FලD oⰽ-f;a>ZcRlGA.e~H24~QEs[TP {R׊5jɠ&ȆfXВ2}Ey֥ Kn-W)d}\v~/i*x貪`:qǥiQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@(~P#t4rMr5Ĭ\IY^ (A8 {Ծg:K+$]JҹvڳȪ IdW7.:pDa&SW<Gz$'K+Zi4dqi˷vo_aH mvZHH>lhGW׶'/|Ww Ffݕ-~ks˦%5<;-y<8 ?^'y[HĊs3@BpsGu˻mY5_7|OFt-Xc(KhTFz @j)m֗(y$Ť)ĎKv>fRfI3@ E&hLHbњJ3@ E%1sKLf )(fŒQ@mA'S@hTPH%]ȥz0koe[Aw3JBHp\x1k1 W.&8v׶= Oۮߩ V&/+oK-kQ= U>(`;E+ΪΔy&ȧ-m0,],OUEw*)F;EJ'9UQTAVEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VES~UEr( < }u-Ol h~mʎzw TVTػd#aaB8OE8i9qyaK p @*,H w,r{cKaK$dI8\4T%،"tR$Hf44f4f3I3HE%Z\sK.h&h Fi3HbIERf袊(h?1CZsUVs8?0CǶk_#}Kћ%fثm\J)<0"z@*+;x!3&qX}:JQ=Ir&^d. 㝮]Wg:Ew?vo+`!?3m;ZJJO8JyST9$V(\)Ӏp}?5 7QۭxlL..㵾`/O)g,;EamVI<'lct(X0[XgH#9>zp#;@%C2s)$BAǖbNgּgGpEPHl*A$p˫rч#:tMBD{dga,wJdNzJc:/5ce.pQq|5kVV~qo.Gke; A5&v6 Mo-U#46\ǃAI~<Rpik<>|s+,`A6<#n`.q }O\-4]Mk%-p"UʤOnQ\1P3I3wk-nb#qT^LM:aqa,E=WIQ#rrvVV׮f, Cgw}xߠ(((((((((((((((((((((((((((((((((((((((((((*^Z{`H&H+`3#B(&M^X-8}dFzy 캦 qs )UB>f`0z@(EV=?]m/wr)A ctȮSR״n!MI6 L }ܠCÚugCgr'c`?OFmJ5oMj2+ g2 RB䃕q(]NLpmF Pú x%ڭ{#3c6FAO罰KkRM ̈9`A=CczgE`k-ֵi+qqoȚYB5\.pxֹJϫXgыIZ㰋ESttW*4KtŵB@S9,Tƹ4 n]9K~h RU1ʩlx8Q^cqx\aLAaA$ee yўWWxr76%\&,ꌀ3wOuWai"lA\[]o~̳< |sjy ݉^;X\eF2'tWg{>k&.%F-1&$g5e-8I-lbIF1Q08ssEsRxU]JNM M\5Q;N 8KӞ&heD!X#lwp*j[ܕMPк#(nݑ<`sRAH:s_ .U;V@uۻR(dXr x~LvK(#9Sw"5Efjv7!:0o+zF8t薆Gr[&wTd}hbmjokDw,8 rP= |A}]!%icI)e/?+;3F]{^mmv-vK (ڽپ4r--3LTHFHx@QEQEQEQEQEQEQEQEQEQEQEQEQE5QC< 3MKЁA {}=ISRf3@xb}TY4$Qo\9qV"Pm-nʹiRYjM4ibFF%@1?x^ҷMd~#e\ʠk#)fIlfcJec8:p:t0ѡ༚"I\}oYdV^G5H"Y-6'@LsUti?j."[: m< 7(J) Z\is@ )3E!f44QI(sIE%!QLAEPvq@ӻ#^a]TޥS<^[u7K$=Ir8u+I%R2Ƭ%mkWyyopOFmU*_h6ǜyRJͳ݆#~5|kaH TY\R&b0v 5T#s&.mw9< ^p/rXo6#el[|K2٫O|9vL0?PqkNl GtMǔky ѱ(l{֞GThDb84!ّ>eY:Ίjt[^̷3c9!I*G\ #ҷt:-+J%0O9'W(cOk[XSH,Ǖo[ɠ T8\@5{+ ݙ۩\.19s]]Qdڵʗ7Pyq33w-/y4 o523+I$`zSEq /m1ik{dv[fA In8l:֩_U[庲Hml9A;QW[Erzw~=W/&#Qcf B9\rۏ ]=9NI Ł@nqr+9 |& 1l%4٠]H$$9+HxRKZ1 YA\h }+V:5d2`Do9آ(((((((((((((((((((((((((((((((((((((((((((+;\]'Ӥap޴h~/ D|vjC7lT6"`C{K[x-F-&kg͕qMq$zMEd0x~}"O %̛<<(\j}WSfXWBx#N4 +/ ik$lgH} kN\q8xVܤ77C-TIHN+r|5ms˗l.d *9ڼOkj(*o7W1sI T0ZiP̤8pxl95Es6njOK5JesmjhPj7q^-՝qW  9LV?,&OYӢb6<NFsOsNv-k *CU$ ?gmK/\Uٴ1=qVxm,n'!+3rG8([9)mxte>A ]׃4M{s,݉]m@tTPxOJ]ZP13 dzN:<<+ 5qS]B`$LIۜ[P3W,㸺O& ۷PqV[– Ysc-M S<͌AebFskv_!cON  dKsr [oٽWecl,6lTsj@4i:|vIuqqCj5ʠ`.@>%LKy 2r*($ҮWR#{/wt|ͬY  01ޮXh+Χ8u`#8UۊԢ(\tD3\]6L Aޡ7 y򦨊poqZP8|"]EjJm;cV݅a~]#3zՊ((((((((((((((hEq(4.kC1sFi4CA،X߿1=ZD~~;h߳;wcgiHR`r'#Jfd{=01| >[AoGqkp1iFkW뻫r0+mN%vhJQuG+eMp2I9$$I=sYڇlI<2ݧ4,BHwOYMgL]>c(eyr呲@ {K IbU0 ޽*]NgK`$" N$A0q 5~b7;,Uȑ!DxU1+^ ӴH JimImEr 6Z-ltTG˸vd,0ڬTn'w;b)<)K Q0 3./%d'vI#'Jd^ 9cjle?}8Es.ifh!6٢.isM.i44RfCPgESQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE?h,.i(hf.h&hWb)ofϊuI,+s=yWɂ8P=C 30!u8f =={ ]i72DblÔ- -Z_i$Udy<*U/?ڰ-nⷒ ھ<0.y[pǃܚCH%ee FA&0AKf/=ޱn'x K$0Y# P זegL վUht8g%p96ys^Fѿ=q|?6-/|\?mUtC|o fO?7:RΎYIkܱ(y[znrzdU{Gm4VvE ;?A89^+I?0TwOWЭ^ibTn!sӲoǫn4R#@hQx#@=ϥOe}ˋXH|l5ys♯.ٔfӤ-Nɣ[aB9 e=zZg*OOu5rt@hDrLQכo%UhՔ898ϾjfekmnS7_7#0$ 쥿Kk{M͜lUϾKU䥵F6#m:4$$g دUFKx&nJۈJf'H$l{asWY^(Q*7}0i.|>m䴲[_@w|1ǿ!3\旨䝦I;'dCo$0+I57(W>o#;cҀ43EVȞ_>i< Gǀvgjh$Hi$`3RjkZI|ȉ+H#~5▟>| (3S( Lq8G#U4˯4qq$(ב8IFi(E((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((lwO@H< 3HֆbfT54%$NF;6;{q$I#WF VSA(Vvu5Q0K،6;C¹ֶ$:Jq1NB*Wp\v;\P%մW*SȬnE6mv̯r(G:Etf y,gn3_Kw+a'#8?.:Lњj2Ť\_>$ZǕnEn9c6ӨuE\i.CyYo9b's;qӽ+W+}OVIɲ)ehb@=νMk_ibڄ± rCpT\``*$Pjk ᰖXCkܘ ͵ǯCo!ks"ZFb$>R7ϠY_S$rڱF͸+'Z+i̫v;hyXAJ_(MFE9'I'%ưE; [#ur ?18G zSF*z P1j*Oأ5yЍdW1 \ c'i$T1xDXKZF6p>RO@qC6Fk4`ԿԿiݳnӷv3v6jm T82y@nc4fxi9Qm°;[Tf}{g%ڃQbx rN_LԤa-$yo߀yV ;P%EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE>E}ƊQh< \ֆbfg%6S=Wv\==j*ƊUFP&k{5=)Z.0\ǃH6v 84nzk4Xf~m=mmpxPBn;A qzVvjzi9\# dPO"њ\O0є}d~5F=S֭/5l$+AqȮ<&A,m d8댂ym%ũP0sDBc7gj3Ene{{&-zkيMK.>8Znek"gi †xKf)h ֟]^Z$ua#Bؕ3udҝOn%+;s3E!l~Ne.m\2 `}#Ytv-%-Wy 9=UE 9nu=]|E10+yi2R]u%34gLN۳q:ߢ5ڵjQ@N ~^5,Sd5֥O2A1ty+$XyXG=^w21m= tb IPc(F$C#nrxJ(#^lKfpFy@z z]7KՓXQhԋn8!`#Jh(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((dw>E80xftf vh fY-|CN>\ }xZ7m0ˑЏZ{Hn'YTE' w#MX2% LTp3>.rDw*c$72`m\dRA09tΊҟ@;Ño`:mW#Б+5ȼEi5Vn]Jڣ(oKKf`Uv-;Qɢ<*`F8ցs5sE=ɆEM9 իu?6Wf2.'j0zj7V&yݭդ`w)*IlO* }ZKq~,p,NH@:r\qrtWPA!I#V摯Yz5[y& 8Z X9#q 1ҥ4kM+睧*0p8P3ծNKlګF` B 7dP2/3fD!$mj=srI4[Mi#>hgY$-OS%Cwurv I%29IWL/(9@y#|8Zgo=X€[nsܟw-_&ep8̤jw@nn.["-3+OT766de#?Bk9'í Ę;;#1m|M&mK(jPrG8x 9=;洴[Ix8d)f$x%A%&W Q'8ª~o>mjZRP#(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE>E}8R暿tSZ3I3@]CF@$KׄM ;7h?c1c~g~lv>sb\inHeH, %$ YM 5__;2E<BPO)\(&Oh4w7\@~|XcY]A}@jv׺%0L[um!/,1.}qOz>i}'v3wgn39Dws\=WP<@Y~el)p~?|ħ??֠Kk Bs'nϏ,wdnm;MBXfW8R 3vmra Z?A46ꐵŴIy)_, 697 W6ռg9}\gqqgi"Gsw/'Y$ [ZZҬ0]jvpJ0LrΪ&m?Ao7F?b{gwN|,Qcy&Eb:-%ku9>1AcԐe~=)u>&Ypy ~u5rEPL}١wcPtP_붲·Qb <`s=h6}sPkӯvw6bVpwqizղ\&cGhmdqt@v٢}sZd!Q'Z(ϗ.i.kC0R`"y`fbrUs؞޸5b+^i6HWԉQTIhYj_=F ,[#a@cgu"Isii# WOJHbgItܠaЏCX^'Z+"J&T6~`UnPy(ʯL9$= RѠ$ǘ^ <O[EKhgT9U0OQ`{XCJo黑>ԓxމDD)˙3Tu,q`rhmm%+DARKV09(?w>_N=*춞LMQ 6Jm;kőH7߶io[g-/ ]9sN- 1Bya>՛oq [7sp)=j7~uhʛVlO %pEZ@cQIZ2ȭk Y$13as*+F+.5u6pUloU,n^0\T/~5+%Rib߀SUZ)(vz*^Arr4a>"M6\c|0*6=2[D6F 31w,5 ]N\ZI2HJGb"4QJhC ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ('Z(GҖt})kB~ `e<2(j ~L)}sr}i#k[,l,35L{pHj¶HwjD@G {@`A@MwP33Ò0YC%C2pk/'ۉūC-0"9X'=\vt^MԾqOh pp;-᲼<ڄ-d1cߌc޺J(&-.mEһ С9JzkgiˬFFH5J8=N˩k5$Ӵf'!(7wTzV BH'(ܓ#BFpssWmeyw1Peo-T㪐G~9W%:K<Eo5obG+q֊#WU}6[(}N*|z{V-fdmG8Ҋ@sXi$l#ۘTfG{Kƻ%%&F HG]eŴqyw6\*E 0Pfe%'U2۳3C( .polU4]R+i fn2!UžO(9m(mޚ]sƒXt%mb/.L||AV(gdUmT`>Ÿ@mm'4۝t{TPۤp41@Rcr9隫x6eX|; ml(##q]5&hi(wQE!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ I(;4KZ- Z9&KPHvlwj:ȊU*29xiea>=j"*"U \Q۰[،1+KgjLJa)NBT kk]Ol.J@d,-L^`b.0\ȃI~8tnzh[Η6OJX"-ev"(eq Wt;ikk{C 'h8!qqjRyZrmq9(g >l;2660 &YoA,+m1- >^ɪz7sb3E$[LaJy9<4b/Eg==A2:;ђ}s@˗sI 7 lTO͕# -Ʀ զαYZ[ 'fݾ^z?+tk3Oi #ǩ ?T#@,}Vqiwr\&S#`# :ΐYxT/*%pёmn{ q߽]״ۧ"!Ue;pq3ڱ<9MK{-R܋v`w!Z&c<F×ٳH!LYr}t4}^7mm_†>TImc 3HoRA\ߵQ4mol- el<;dBw{}lzYBdtZ^u Ammp.3alOSUTvoʇP#|dc-UÓũeq\'9cDDwxHkiv+O]_Lw "Y <՟kI%z[ianL3Vt~H cm*}hTŁ =VpMk6\+& )8=4җ< oʥ'9|aT)NoϰTl~Iwm o"wkAC}@Ŏ^ypAGf?3֍d~)NyVFK0U]P}h Ȭ,乛;Pp2Xܓ>_M^͆אAvڠ,.VlKf9ǭUӴU5x5 ͑`n8!`L;=(=(0((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((}Ɗ$>jLŢK=uQo9ʧgz^;aI62oP\a= $q2(,ԓU-.a g .ΛMJŴ_98 DZդC*J ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ('hOH9^N>QEC57,XK"r;zdtY#{FV[#쩗^OI$1[@au`X~tI\sZO-]E`ct@8ڲ&ԭ!Pdeb$/38'<օLjMJK Ƞ4W&W >lrzVaơizKP#pjK@Z^\9{Wp`F8VBKsuloJ8 <kRe]x~5.o- `8ġ4QH7JC v֥ \jjGRI"HDZTr; }ta[ּ'q%Iیc%*ܥ [ Z_=no! R6<=*ѝA!i<$`d8qih"CE.[6#P0r݀2ʠARMm1AE!m;{hE\yvAZmDŽ"x{r[%y'=q[PE//?{Ijm=PGYl}uS?M^J(sIFi(ASF?sg4}qktkfJz-QRPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE>E}ƊGҖ}3AKL(t6w,آ[mg}@Sۍ_Xn.w],]2>PEcsv:~og CmF`j7`ax7my b\.-Qkk$fhh8G. #zR;rzj(+7r4'7ʒNȋ @v :[tQLB@ (E%- Q@ Knihh4QH)(i(J-%G*J ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (wKHt})kS0(h (-%-QEQE.h E% Z3I\撌њ( 3IERQ@ETQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE5E) KHtR֦AEP@Z(JZ(f(hLњ@-PњJ(fFiQI(sIE%-%f J(I*J ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (wE!n>R>P((Z)3K@ii(4fJ(RQ@ Fi((J(sFi(E&h)(J(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ҊyJZhZ-%QEQEQEPIFhhE%f( )(%&hbIE-%R)3EQEIAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_?J) aGҖAxO@+Kis+3"sG<z.fc[_sEYV<z?tf=O?^/y?]Fkc _ 3#4/y?a9ע,z+c _3[_sEYV<z?Һ V<zOyz.f=O?G<?z+c^yz.1El`CO?E5%m`C`CҸXƤyz}d80h(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ((QHgtֶI0=p2ؑ.֥}XuMP#\8s 3N:U=[נwS(/uIpG(XpHgRH:'s& liLZW]ɴdu8':m{y=zkO6`ypc@1G(˕KF`À2H="+FVSG [#"X^Pˍ[Kɬ]#Ḟ$g> L4fUꁆz}*J'F|Ku%棩qTI䢐Gy ((((((((((* ɿ?TrZ\ 6^7c{>֙w<:K@[#.v@mQ@Q@Q@Tmq \GnFH23;# (帆|g`+?^Y8C hI'wmO{[sMLok## 5nS`ikU4*O8#zEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_?J) t5;{%X+ o XdZ98q=zoumKLd\L1']Z+2"0[ˡIcg Y'sYrrqɪ7uT6Esmi&:`Q@9)s:φo5;s$jex$py;I:~] $KmEN~z(P-P y|9иqǥ&od>W;aLӡ-@Ɓ!Uin<Ǽ0+ MtQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ ďkz.o*eWQ@`h((7S[ΥuskNt}##vw0jVokqOSQ@ j7j(y>Q@}Raa>Q@}PjO0QE!}PjQ@}P0a9sz (y(QE'ނ9P7oAEy(QEsz <Q@ނ9P7oAEy(QEsz <Q@ނ9P7oAEy(QEsz _9P7oAEy(QEsz <Q@ނ9P7oAEy(QEsz <Q@ނ5P淠oAEy([QE/ނ1P7oAEy(QEcz <Q@ނ0 (ayڊ(ayڊ(ayڊ(a> /Font <> /XObject <>>> /Type /Page>> endobj 37 0 obj <> stream xVj0}/\27e(m Mgc c;tF3ghS|>AFEgyg|Y|o~`~c m^ݿfb4$`_7dwE0hڧ#Ol]b&uFth1l}j)pEDK2.%t0- ʒ*eaOBk#PsȆ]V~?JAَh;T啳Sc>#<A!TVARNQ"-ZYֵZE(t,Q*X7U*fJIC1Yh["6KNVěwLbqv{gpw·5sҌu b*HZdjMxlb:~gד9 ?ttb҈.ذcArSNϟ0(b:&:&͑P@9FC7ѽ/`T*B+ ;KN=XP&OЩTQ9K) ̣;y[a<5? 2@>?z\~|VYIBt/P`j]I9^Q;ՒE54p} cO][N endstream endobj 42 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?++!3A {<:wiagKH}` (#@֓+(?chGTT]dC;_}}MEadC;_}}MEadC;_}}MEadC;_}}MEadC;_}}MEadC;_}}MEadC;_}}MEadC;_}}MEadC;_,z}75$)j,>g>߱Gl*]_}} bQEYg>߱G,*]_}} bQEYg>߱G,*]_}} bQEYg>߱G,*]_}} bQEYg>߱G,*]_]єieG.* k^ /DT|2}dVї2`z Z*Q)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)h`z Z(0=LAFQ)hŝF;#SQ@e.2\[n6O&6돦:} V\T ֊!fsʂnrWޣ8kt9Dfܟ1-sWpBfta" $u>˂)I/S(fK51Ē*pHrk?Ҽ7Z4 F+'@(I*Nʬɲ^A 䶫LS$v`@kiyee Hc¾@oXmuB_D+n4FBнZ< jA[[# M{fz0$JȌ@' zjz\gY̞P̡s=^"ү{*mF$ROspx\n-=Z7D3Wֶ4j5[dV\!X %s1,~ .촳qĿhTubz 9p ijij[_]䎣EsZޗ_[]!-Tuhn 7:\zmΪՕ50%y[6`q9P{ֲKvQp=J4&e-Ԗ۽C2B=r+m#[ ̶+[jrL,f&Io,w.Ǯp1-? \xGdLJVހ;KKgC pz\{˫˨HnT,F qA]Ed_k?~\Zlyw}6+'{7CӃ7u5r>ʎJ]l@p!N|TcǕh2VF/'@^'gҬkZfؙBu|qHѩm/[o5JX+!2UH MCm!VN*|;j֐C&ݭ(t'VT>y@;^:XYSLS*v'`1֭-]:D,<^`d j(+Zƽ'Tdʼ豶9Y=3ٺnجk75SdxlJ\^N6O^j+-,nJ].2瓁YMޞqT#;(_90y:4 ]7s;Shdo?:U=Ɏ3xFKK}{Ldg2w-BRayoum]A=yCc%22i7R 2G5~~U+V0U,N5ϻovoSڏS*KEnb%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%6B'1*ʹ}GpeQ˹8Wx<Pw7N5sUVBVs_)s˶?*8\g*JIbI#IxExeawօ@M7S)\fƐđD @)%g7[i۠ˑyU 5Yͼ:vxcd%W HS֓kr/?T5:v<#hK%dHQҙy@ G[ړFNq$1LT<8 3Kq/KEQ#$cqQ& ukgo3ycYy+>UU0KEQEPC&$r@ǩ8}J((((E`*SҬ!@=@((((((((((dhuIz[҂;ʞ{*i,SŕsQ$Q]*HIŝ 2Nb ˅$݈R- 3#T(TUm*QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE5QCt;KHt})k ( ( ( ( ( ( ( (OA@\~HRqI??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}ބ٠ 4R+Pr 6I%߀U>LZ>LETo3Ǩo3ǨS tUO?z?z-U>L>LETo3Ǩo3ǨS tUO?z?z-U>L>LETo3Ǩo3ǨS tUO?zeqEIEPEPEPE!!T$&=7-FcK?AoG VjקI^noEp????tfb_a?~ZUV'+ӵ }R;W>l%j #dkKJTQEsQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ IG֊hLr_>U#vlg|bOVe] B"EPE5F1XB˨R)!^apAF@(MC{mZOG`>IY|G*>0=rlȴ]7QƮ$ڌeIA+AZ;/-.ow rnTXKG#tW+oDlߋŲH!:H`"LnI8xF?22 k] t=@-"۷2A ˷M>l4[ɬⶴ%Ye,ULŹܧr}=͏|vٞq8&D'у+PGZ]+ (7Ei+G3OxelR;}%r(#W,z:!0O4p8S(eR ^L:-ų-oou).7f  uw-KKGR_#]5/Pl2h("OkЩik{Kž%s$$9"v ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (iz9->Y?Ҁ)$MS4=JT >֣[gKҮ9fd=%1 |ҬM֚Ȑ<(^ B  ?ER.l/&@o2Gjv2|H@ ñMa%q~f{f} pa8}F*yipa_1 b2_ 1Y\eX02ygT Jl"y~\+>[\gϽ]d//z6;}:} m:wұ7hܙfgBv'<P ?oHU*|T* <@U@T7TQEQEQEQEQEU?گV,hQ@V4I}O.Tz4Eyش/no>>y{nlvP$2YEo3q/dd> }xf88Ȏ ҟ0sp<^ם_y?1+|=>OK^3ogꎬ#+O (;Eۼ=_חZv3)ܸ;+qٷ2ֶME"b`77\SYi :8?+ Zϣ9,]n[{xFU*O\%711xۼրfo1~эrj&y+ Xf v;c=vޭջO N#!xS8byeuHgcu$׍&ci|Wox 8xpN0Ajl:%"!RLday{ΏߥoF(S)˚[snV'p4aeTQuzm׆,//'[|*#:cdtGҏ:?~&TZ%z20eu4 Y8b@ aKsZGJ4 궚fHHu/ bĪqo#xwI[IU#)ےqݷVϝJ<P ;%q-t$mh4%&ivl20?jJ<P-MDԅI q26 (zSWdW{%db7ށp6=[t(@6;.KI< uy*@@F1`qRI-!^,2ev88ky{Ώߥ,1P{(]r͎?:?~y{ o3Ȃ82?w1Ij?:?~y{ +Yk:ķ,|rFQ6$p1[^t(@wntV%k[[wR "H_3V$ MiX+](Y?C+"&:?~y{dVq}GJi6]P5,OAR/ꉨXKܤ{=<ˉ?WcGҏ:?~I,ʡR/,ؔ#tIӤUg? ShZݓjZDVBxJ`(pGҏ:?~^xE:qrum-חL\2y}5~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ:?~%J<PT~t(@QQ{ΏߥIEGGҏ9O >PrOG{,S[criq@ +I$3Dd3@xdg f]:g-E*kj"gf ]ܥKw0Bt 8'J78 p3N1Nx[ēECYc03R˦=ť=[@28~Ec9 K*{Tzn(zzZ4Pivlm%G6eA1~dw\@ķGZ ( ( ( ( ( ( gUݜl r01@袊+?ZӤt߳C:* Pʲ # :օ5n_ϱ7}o}}NJ~4<4"Gi%i 9=+B^_׈׻_}N ǟL?x֝rPR)u%yex٤].ZR2TほH>I/6UҬ$`:_qQgg`Os"}H#Efc 2Mz߃4Gr%C)Cr~fgn[_(=r|vWMi/ [sĤtc آ9xaXU[Ͳr(wNbBGBT{祢SJBZfY8V ['t5a F[MjH.W4hy2r$8=6]^ZHWu0Yn gs8:{wMShqCy͜Z4E%RW B]-yMsί]>fPIzSBc-b ݿZA9#=71Tɻ$K7sJ %jWY>ڟsLlo+G&6?߷sJ %h_a::+cc Z>WX{j}Ύ?1~VVڟsLlo+I %VڟsLlo+cc _(oJ~W(_a:J+G&6_޷ >WX{j}Β1Q %VڟsLlo+ce _(o/[JzW(_a:J+G&V_޷ >WX{j}Β2Q %VڟsLl?¬:5#7(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((dt}hO>SBg: 󻛉.iXsAW]Igp%)GV#Ҿ)_K]?SºkQZW7։ç@ !)S޳kٌWjǙ$ܳX˦Ki3#I$@=ĵdT1Gkuuٚ3bdeb@B _Oԕ֭c,rWR zOj&*{~u=;{o`ϸ,{ϦN+R}&&5= ?1󊥋W$}EmnnmʑK'T ĮssV9EFM'p* ( ( ( ( 'xdhدLʤampIZ@GU]B[#A+I<^~mEr7s $JT{g?\t\O/mmV_C֨_[Yě ĢRw9ZKQEQUfԬi^ C".ϜRH?2j(j=ii9%v=7Ηz?iPF:_ѦUMF:&iVA dSձԁ:_ѣΗz?k<kIg-l`\ޭF.,nq܁]![vVb <F:_Ѯ_E񝖭kqq4&\-\aGsׂ@Ct綶IH=e^:]K=4yGC <̠CztS[8Ucj>?kQEQ\k/$<ɞY]a%T7g#p6:F9.\,j B NA1@WMӭitۘ"6V6WnḨp#+TUmvsݕ0o3 PYز&GuW)7bwj˷K*7fU-s Uiv~!:Duhi`##UJ4$(ʤYZ_hEw\V<3~E H,1gRQIu헿 _K*xZ Z;YU{*ʾ{IgxOZ\pv` RE>VCR5݉u Cۺo!ƳT}cRA=feu;*s%.+.N7Un5mV~`:Z,j߯EBMgQ8'hOY"8tx]i-%Qv>FߦtW}EJ.gZ0$r[Eo筿]}_0VzM+}=m ׊BcO Lq]ƂH19•9kybqO4p9Eo筿G"o^]XhdT| ywL!}z -gLOj<3u6W2^]$w*NE@ S@H&TЮ|ElPzm0y6шϴ1I'񪶚&co4[t ,[r ϭ;GJhpDF[O4*I19$$՚)tEPA c%ǗHSS |bQxGlΕ(Xƥtb]BfܠX\g'ylgkhd L{#*tkvKHAFFw(P.;{u Ȩr A#ktY.U\4Guf v?;#p[%]&Ē۫Q 6? h|:U6w?롊 ' fQU=ޕ^Z2SǧUF6jiyak%Vd)NݪmX zM2mjZ4vR%,̹%$ WEcIZ6YZ\Hm!HaP?.y>>% i(ĒKjHc0|򳴜vpU)5ljL+)ohn\RMoFN߻p=1VŶj5m=yK:#$8 gӊͅu]BΡ 2==hMz޺z:OUu+\YY9!(vl<ԓփS=;wETyʃWEOecv}db({xM'9#\!]JmxD|eFzUiQpwJqI#"э[lj߯~>z/dw>E`N֛G[md Y:8IPϝ?U|7ͦj|UO+,_wp=Qg^)c9G V gkE僽ݣf{!VUgOU-KŤGm^$z9)2IӉe V0+sG(s3o|.\ "X23643g|Eih66 l^6yB)SZr)w w N\p9 ҭEP(Z((&()QE(PLR@Q\gPQEQEQEP@ EQ{0ǽ72y?3ZPe~fhQ@a>/5Eg_'h Oօ2y?3ZPe~f܂G@*QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWfIˈ)6F{ȯBAޛ1B-k͇&VkԨ\Gu<-k͇&VkԨW}CVhk͇&J?q<-k͇&>{%^Y(w_pgPgD&\9z]gIZd :\>|u8r[C̟ƪeYi¹?&>_qC<α?6?4¹?&>?=<0s͏MuOlOuu3?\c}G+c~lk[}CW:o X(_pgP<α?6?5$ 2T?1?z]mᬺb:}6 E\Պ(9ڒJdw>EAEtEP2Zr&ihpO<%"6NAx<*ad뼁j͆,nc\ .uRL3y׌xPڻ4Mn{޼1Sgi}eѰmIXcQ)8Tlf g!  0%r@##9~aw 7]58!C=s^LlՎs%;mdboP981Ƚ0x(YiKt79pݻ=~SߜV|1wp:%ڮnd$' 0Ã.;Ux`ӔKw$wfS7lҳq[[jB'-Z*u˙`v2,NFH7AGf*ϵoqV3@}oUii8eԓO 7AGf*mu۶ْ9ϣzՊ3}o_UK}͍1Ryos50Kwʣl~Dƀ.oUf/]797f(LCQq T\a p=N8 _i3Z)?2DdzEMR$X`p ?i3 7f(LCEM >7AP@}oT4Pi]J-lT4Pn$@ý:uK@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ 4Q'h@s E-&ļ갓#mIcfV֞$H.plU;Q7vNIt;O5S.1|E=ORI伸Ș#wbpGrI<_bMrRUfB,;q9SAy=y I23+'b!Xgs?r2+͓iIgnmK]>zc@f#o;I$X߻bg53i3,s;9Ut3xOoլbFT',s[mwL+ecy-HI+Er׺LK_j2Y9DU@H*K$ҭ-TjERXl(t.ܟzEӽiwXOcvckXyw(,WǨMsʭ}4IeܒѸ< >[c`v2eXeuUeQ ͓JՎ9^)2:= ct䶷)xb%adÝ 2R0k67$K4NQNJjҗS[+y-/X88$EQY@PMɂGh.2|QRTAU2H7f< |5 {8[uKb^QKF)Q(((bPbZ((;*Ϲ?hV}|78k-[i]gk6[=Pp28KRťGL[3Y4Q* pF,wW? n+rXl)^w/sak4ɯEᴷ7J0'1znh/.|=jZ˦=wBDyd_!AˎdwZe3ypKxҶ#N0>R3{ֆGm4Vw!9‚'N{VC>6$cM>~$D\C3FN,h(><箻''އ%⭙lŤ1L% =¤Kx"I4@ tN(i+53m>ռםۺ{cT6+w^'^M8FB>m˴nq+:}?7fqczhӵeon/X/59U ۇ;=zgouK\(mX`ڲg12z 4[H"9aN'֘4<]cm̾J,:9KN?uJlq\XyKU(*qV=Mu[4 E¥CI`1䓟SNNKy, \JU؜F0I<9;}gW&ӭ"/2a2D>WPxwbK[M \tBgX(Q\`e瑊L%mDXAFN8⡓IfbOx́ 99-u'sՕo$۸qSPJ((@Xs[{n^:_24ۭCS7oV cV?u=Oz( o_Ե{Z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((dw>E4&r#R⺎a(b3ΉƤKK \t;j85*Xd1tՕDFE,p 3؟JNVm1OT:L 6:idqF1ۦ1[W=os#T%ss-Ŵ"r85WBU] mNvIշE O io$M6mFrPGVly{r[WrN0ǁkR,졪P|R,1J6a /uSE{=ps8 $H=4Sŵ͸;IB tY $\E5sYh3B_qRK2~QYKKalݥh՝1,ŎO~Ib1E0 (Q@)1KE&(Z((%vUVݝ'TWgo~MVi>7?QZP٦(4EhQ@fsEgo~MVi>7?QZP٦(4EhQ@fsEgo~MVi>7?QZP٦(4EhQ@fZO+|օFGjuPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP$I)3-`(rǐ{Z-ܒ~lF+x(8qC%.m8 'nᓅ ;G\b k{}3P:UĖO}6V41" ]^Ey\Nǝmt$VwZMĶo`E2V2HZo/fe}]JKw" Hz5r~$?fJ%699*Z* ( ( (Q@Q@LQKE%PQKF((PcTnc!LGjY?߇3|Gg\˲Uv8Tp sT?E[KmQFV"sb H8$OahmVxftWpDAA/!eas۽4N#Vw8c@W c*R)eT -&f,̪7.8韬 uwty3j'R_,~ϵvV5?1[&;n1 [QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE썕85Fk*Rq#< P\ƽ഼{Ho/&C̶o$rqdPXd220*Z(c[LS"ʋʹBA evĹ<35Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@>96Q@ՙ$#OzVdEaY }:f k8Քed wsܸ=E%ϊ4:}U!TP;Px + ȉIf4: sF4XquoD}V*۶;Thj3a޼|IWBy&$Ԯ!H(\Hl ?1~m}ugRkm,}gMZmJ[(. EB|H9ˎt};RYԵDچTf`7%種U%Q@Q@QLb(1E-R@ E ((³Zu *:WŗbͫnYlT[os'?^Mzϝf !9/psIu(ĭ(D.$4nΊTzP\hY_N!{l5;i4PQOG3nM=0~RA86vo%[a2k9l$ӥbI 6.>[M;K t1.\{%"n";N#iې<V֣S .[.{"|č"w 8$FGY&xX۪:4y14Er..K7Ek-B9†;;V5lKu=+h*(((((((((((((((tٮ^; ݳE=Q^-Kw{0PuD 'ܞMiQ@i?ZqOEg5G,P̗mȍPĉx4+˛VIog?$-XFCN#ҢմKSiq.K9_Fz iXx]A{(O ޽|dڳ6 Z541¤ʄ0I8 vGGq:sҴ4WEc<3\@p"r((((((((((((((((KMXtI#&)׶9 3G^NL37>9Cݬ cأ 0{ )E0ƱĊ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@wV-Vj8e,zy;>;t|p#HˍNsҴh (*@%IGj5b(dw>E4&rctWIQEW*v@>O8+ԸFI2u 8FM#fVd/ ^5Ė5Ybbs">MVٵ ^&|(N#٫i(q@X.$C d[Xj?Gf^H.! Ɂp]8k^s gK+M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPUPf*P,G/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/iT`۱V(hŕڬQ@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@Elw=PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP$I)3.(s(P:Y1:YcuLvcǯ\XӥKK۪\C#mIfVT8rsƍ%}ONU䳝cr Rzuֱ&,lKjֲ~Ic8zV43G +4YΥ2s;)өoG Ť׷6Da0D#ӹV~k.*=6I~9 F1ɮ,tK}{i*)]m= k*V)e-եyJ˴;N~bN:q]-B[˨m' '&GY]20ʲ=h O b6A.< H'$`O_ ZkV;gh% fD]u0y( &B6 6I8FI'=5u i5uDKroS2ҵsw/)k$K@Z,$1Ĉ]*|e8?QL((b(( E-&()QEQEuKq*ŘeFvd*kmd1GB=k>u;NOa;5IPCF݊iYMTNҴVKh ,I=^ItN-o>;UQ^'Iuwnush%;!\K\3v8S]g[r4gk.#rp<84ZD,%1G ̫#F9編"\ON㉦w؈%WJ{{yW)4MeB2N9f%4qͧa$R!~ULd,S$q@DKs$b_40:$g֮$x"/ڄrck#żfܧWw@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@OS:uOt* j.Oc' kH!캭J30WL}垛;pFyBN>=Y}#@\j[^Yo:踍@ $Gf~/5=R<`WmBg8It[_ں޶UY!W)A(lr}n+4Q[7ҵY^Ku+f7h Gx#U );,?Tj\\}$֬mnh8}O~@ϯ9]&lhVZg9EPEPEPEPEPEPEPEPEPEPEPEPEP$)(ts E-Ǿ"E@s;b隑rHy#iomYEwץSBPiEp UI6̯"+u܌VϷ]:7t˶ogSh!@f|#7SȹF VreGo+bM2]Moqa0n 1**CJbҭ5[Givf<:tQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@2$(}<{iP "RBt0Ic#{T6%3I5R|4 Op+VNV+nYqԲxWM}.M_>+;Ds#!|c+n[:9Pڎ'tߨ"F 2@Vn3azos9X3 0B>kfȲ奍RV\Hvgש=8kEQEQEQEN 4([0$op V3F$mZM&vKj ˂Jآ)CA&!KhsdmFf9sthlty#GO1\ vv 8h D𞌺|ojҬ$iefts!;;bŴC.?5)'0ݏ|ۢ9_vL ƭJRibwFI\CEQEQEQES B3kx"; [ ,#nVHLet(OPG1EB QEQEQEQF)q@ E.)1@QL(QE&(Z1@]Q\HQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE?hXtR9ĢJ+ B6qn>du]幎0:9•9kXyS!'cBK,O(B"'eM>+˸7LZiFw|lF|ǎ}s]gO1T}R-gK WF]7QbSRJ)hJ)qF((PEPEP1ELQKEuTQEr!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_4Pw9qQ@ Z (d&E隍-<`F{t>|;<7.Kb$Ѥcl%4ppM\ 6McBEITlg qYrhΣqs)E-ʣ1 (tdbQ HN=9y>bC 3N pCC-oſ,@ŕ@+F$md9 E-J)h%PQKE%J(QEQEQ(:(N(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((M (t\Ũ7 uO &8ʛ0B28EwG-M,zu=m41laB|`dp$cEi? y=+C: /h #~R:z&MY.]y.&1nlgb Qgm4L$~^vɧga&o1Y [+\6Ȫ3ESux/.b#km dhѶbXa 9'ҼX(-4\rZY,2Ď88- omBJ bO+(dR8MuLI_li; C<W" SC!+ʹ%R-'J~nG!s[6k72{;ihfrPQ(9<;u9%K4vFyTHTNj4yin%Э}6+UHe[#.ۉ}k}r rGy d׷+,^YB\|s0Gidvn^,2*A  EZ? I[H(AEZ((Z((RLJ1KI%S+ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (wE4&sCҊEUE @`JyR3Rp4l<ՖKE~9TsVޝe~b7\yGt~laQ-_[Ƃ+iY6³89n@ح卥KqN[$v'a8$dzdJmϟf>[ &@ʞ?z>c`7-en|8!Mgh, `1;oL\{>i#Z,C$+"䎠dphzxrc[Mwwpڌf9畗~һ@Pί r( x_=͹UT[YRrQaˈ!=Qf(dw<8i6 g ";Ř:U'$H"4k& ;!`ghQEQEQKE%PQKEZ((% (Q@&)h(cp((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ҊЎht1j6o)u2m! ՑҖ11l..HTa;tj \A$2gdQppF:ԴbW6\ExVc-'cF*X*8jicJ: Ib2.Ȁ'O xlmya9$nI䎼OX!IEAf cr~TwK/*ą: R-{b"vw 4J8ܪ62}v%Gclup%\;֧ Xc]*WЎ`nnqڎ xm`H-acB`:T ((((((`b(((#+((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((kE4#(t1 (9+n52Z[;{xfx@I7! Xn:p'T[; LwIT169"iyѵM6[9>X-4`UA 89i}ֺbu4D!4.P}'=2^zv4!S Hn5yNtX+tS ڀaro^.1}icj71Uqc$yi>$KTI1hfDt0ݸw2<U#Kqk>i urpTA<o=ͨk%w{WHp<8'*ťzu]IyV@ AT-Zz|]a_V$HI8;Q:EQLAEPES((((((Ph EPMEW9QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEEҊhG6VAEPEry.H{HYmݐđI&4/X%[7⑭X˧mdeTcxgeC,`3aA Msoj3YnZ[Hm]xSP\ury u&2vߑ@3RnJQ^s+kth`46P dP@&tmttIj=pwA9#pQE1J(hE%RQ.))sE%Q@QLJ((QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEE+tlt )kS!(^si-F6A]e¨([|<7?hd' >Vǚ%Ḋ 庠#y' #=E[ѼYR4\r"g|(l`=Z5ֵH윳@3nFH\|1[Hɗߺ"6m c-KRlZ踉YWBK0P+4A&XrI'̝CڇmmͤPvebT^=*M'Vkp/,;gh 0mgg0;[M-5tm2JQ@$isI}Nd;I ԷEfj:ӌt|gBBLERQ@ E%Z)((EPEP (QE1Rf:z(5 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (QCtltt2RP3nf->8 q_G(9PMoWIWw-yYdY'&?&ZE$/ʬYX׽Ki{yq]αHӈbԪJC)@I_>:oۭn_j'l߻bm[Kt2ɉU T1!+.}qwJG6Ta>rpNkSOɧ[[Oq5Updc oOIt]-)+^qꣵC4sl Ŧ2C`y+Y5ri:}ԯ+ R|)$acxB8 4]:{Y YX6v*Džt ['؏Kmei:5+o%ğhvS#nګ'I ESQEQEQEQEQEQFi()QIE (:z(5 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (QCtptU]B-Fѭ.W02̵IXzeQkQ\5 Bzy?GnuwA"26v ``+oV(^l4'"=[O}z ܂x`I%t9 =AnMү5kcnB/AOR}5r [K%yrfz/Li\e3V]JSP/n|[>ܒ? Ү.RRZtC-JYHRqSOvH"+ʰ>EPEPEPEP0)QJ(sI(( PQEfJ)h((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((FE Ҋb9Q@EhfQIZ+tصXn.u)WDRgEQ*.u+Mb9cDs!B!#.QU-Ki-<$R;W= &;XMܛ3xshX郞:zMedfvi 13Ԃ8<3{SB+|;F"?2xdl+ǠQFi)QH4QIEEf (&hQEQI(sIE(%-%uTQEdhQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE#tE1FiJ+C0([=RA%E򴏵+,3 >Z:,0`0݋[;;I썷$0v ?q]==;;U ~=Oj5-d6<\ra.N! pFX=M7΋z(jLڮc{&[˃bWrrNy8hYZ,PMW׏j虅?$xyWzq徔MZE T`@ ;QEQIZ)(1h4( (4QE&hh%(\QI@ J)3LGYEVFEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP7J(nSRVaFh8=4 t :É"FQ[ 55_$i2,vQ$n'`y\ŶYiijZ{ZC5Owb`"` ^}* ;i{a4yv-#–֍eԩ#0z{x( -/r2 <*2Oӽy֟k&Vf=2pPmU4w@I5 5M$ZIsʭ(%Fzq<)E5+(rU sFKjN*YYsKG.0ykk2\3K_ qmoVzte)K8 ǘ\{ iKo N WQl Ӣ)QE((4RQHb撌f i(%Rf4f J3ILGWN?3tmnjgizS3@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((7J)f4P2eȱ\L|ѤUwjwv񴱙(Tl `gc_r&~|u EHcӑijI`Y yw !5Vqu4pƒ-$WMb'8k/cL9F9u2i2s-K N>ea=+ ڕ^ yXHErr>_Ac򓁏aIHO.%@c$lyO֣Ԧ G{so?c0_=+ڔ^Ya_[,2v2?E, D/i$fp@}I3Q@ J3E!()(P 4S%fJ)…Phy[:y(#wNM":~,1s쉴y^'gVЭ{}KQ;rFi\,ze.k𕕽}p+XY;.2K}.hcFi3Fi44.i3FhRQ@RRQLP!sIE%0 J)) %@QRPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEE(Fi)sZ`{ػP71N:z44 3}_mi'e +[y[*JW%6`qޡ$ @ZC9hnZD7(p/C0:QU.MP\A$7-#;ox'j;gG gGJH汞4f@iExj0XQ!E`zVi3Y:tW+w1Y`آ"Jec8:#uif51tBfWG nY$ 9ϡq\𷾴΄IkбIC%[ U^Rzt<[i!H2q"CpOG&#s5[|rGP8P{<H4-CR[x'e[g{lvG%ֳtK[)>ytU%26 / 6خO RL.ˋA,ݒpHqңD,mW!lGv^>t3.hsFi3FhQIE )3E J(IE% CE!J %1%E%Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ I'h th@@\_Akqo P1X؏s؞޸5f5fϊuI,+s=yWɂ8P=!7O3̲Xov!Tg'a, &>HSLK*9BЪ_޵IXGȏrRO?9 c}+y ݫqQ x=ɤ3Еe 2YW! B[4xvop9MľQp;~sc=4qEA q!8<$ͺ7e R7x :;Agy%vrġo黑끓UWomX_]]XgT(t|~G,,/nt- !J7~AZGiԭ"$#8=v3_yzǽf6*g%*rsZNZ̑ٶk 3W*#e-@:N%m}] '@Ke} J)#h6P ǎ@\sxy-,װ5]b,zdq@%pf/v$k{[yvD6@PpCs]iӲmygOi A 5ľtylNŽÌjcLI"EI#Ew:l.-$$rH#Y'in@xBJ|!Ig#iWww^ i͟c"']vxPG^F8☎J(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((}Ɗ$Ž4)٭L@QT54%$\2͎s㯭]IHD`e9{ق\\̐FgnNY %'dD raR'835 umG1ʁ2+ [si`ʗl qӡR y,gn3_N5CWvKefOgIZ㴯jRŤ\_>$ZǕnEn9c6ӨuE]i.Cy@n:wWEsjzzLV~MN%,C\}2NyTnu^{].Nk&x]6`stgm&J620z໽}n e6}ɉ0PI |zTx渿2%kf(R@PI sEo撹7ϠY_S$rڱF͸+'Z+i̫v;hyXAJQ›"#\€I?RI?:5Q.f1[Y(I28W8Ҝ'ѥY/A v6>sH'ÏzפLXnm"Q*22vEAtY8媔`Cg[#$F%gsM>ԿhݳnӍlԨas P;/oۭ0&4+:*9Viy@ IP_^Eag%ڃbx rNm7Q{6^C2`x?ژzQRXQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE>E}ƊAN5z \ք E%28 -N|^Ass*"E Q@]z{>{QtR#01yO~\`=oKis};X~{b=y5H {-$` A!7㌅8=+?^Mae'NkY1"0ߦJ 2Et4R-tĿa)l rjz.q=vgVN sd+g'I Y߅)p8`qԃڡB(B7 ں)&Y^ɤg޿6bhRˏ=4:nek"gi †xKfsKuCwWI3i\b4-cf*:gɠw}Ɩct,\#\EL/ӝeKhD3@&C&bVn&Cİ^Ź|jRrO!'ꨠu۝OFF*QicL EZgqS<ג?˦'pm8?tحZx$ю@N ~^ORQ,ԣBIH]axn=ŷa0kP <.rDw*c$2`m\d O溍gD]Z[iOY]ynsYӷ?Mi]6 y+H~j^#՚+yeI.%mQ{r{W[X /N\0B*Te`gvTRhs>e"*21.ю8$PX>w"rfbBq9B'jvyg]SΕ٨΋ڠzU43,ֶn#IRK`r~Q֠jw5ۆ,NH@ZSU÷q\O9K{".[Eu!I#V٤z>VI1F.{rN=i`)&oG$q`rsǰJHѭ4Hb#vBqHCV:5j[{Bs7Nث ڣgfkmvc)޽ v-$ \]7PΔPj pI>.ACSF?sg4}qhF J !#ѨŠ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((IG֊$EpБh0ܻ73Zז&Cg~lv3w|g ;w$H2j$ I|SF>MWLed{9P1| +\ixH@Pfgoq]}@jv׺%0L[5k!/,1.l>G' :߷Z}jݳیxo<~5_+[>\.WaJ~N}j>&-g2} T}ݱu(:c./Yna[s(K\m0=@%q뚅^x#P݌Jn: Fu-j.fRYRQѐۨ3+b:I1ӽvf5f\FȎ۴$F 0zD?.~ǿ7Ǔcژ j?ǚ^Cc$sکhk{B%1.!sLKILGEaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP$E}<~襦AUP1v` `sZ=33)o=pj *i6H{I8ȩՃ(e 2Yw> {KƬA23$@~L_h6sg3ڢO9ȑ(f2kj\,ld$FU<HZ4X '@`9ߩwVsm *3To5-.;kPҭ[znr}O6Y[/dHٲ;UGR&.ɦKlX< r4JTP1֖a-m 0Ô/^SkO&h(uyNP>)hZV"Td',~٠ (4h,e˱-0G8T0C'(޻sYɯ[ 7sp)=j7~uhꛤVlO 6q i o5FrcyYdV$pcgSTvZW]jmg0>9`0TWk 7mi\c.?]jzy{Y"X.f00X*wcg~\*pCmiSyuT U"YwWڍ|dʕ E,RRJ`zEQYQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE2O>Q'Z(χJZEKVHRR3;W[oEWXcwoAj ~L1ǽb,p:Ө 5IXG&=ʻTyXVQ[[GYh <ɮ~Ȟ^r)*{\yq-[U V[4`Ds0O{2(kbzn[{FE px=q/Mg=ڌ-$1cߌc޺Z(&-.mEһ С9Jl#Gex[@#EܣrH$E 8Rͩk5$Ӵf'#eÝ٩4sVHO QE=&G 7!0 (o/n/Qca,ZU sToKG_u(x.-ⷚ7VI#mErkɫ>~GgD-e>R/_@uj[ܙl̍f9%F, ZI$6>kU ${[ƻ%%&F HG]eŴqyw$]r2:SU"(UQ<6.ml :ݘ>Rnv8*_:}cD7PB<5RP;؋}FPw&h#-;pR-?KEe˓.Asi"6vHZFF7p)Cmoh2N^בJi())M%0=&( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ('Z(u5z ZБh@S/d`2Z|^Z1ٱsuh"+F)#U[9/pvZʼ4秭[DXQ*P0eZh-ق\LgnO  Yړ{;$+ډ&+Ju[ٮ'E 2) @ {Ȭ4mn>oj#MV f}smg1ʁ2+~&2_2zn#r*m=mmpxPBd.33ұ_kR٘5#rmq9(g >l;2667@[`TWat ǖO/RO=_Bյ+q[-Ȧ0I]ۢa Q>9 sɦN 7 jPeH pEIq }Za,vl`^c.yq(I-Qo@YB|ܒctOJE12남?:@Mek{P6qBGFFg!1~z^n-,%V 0q3ڱ.<9MK{-R܋v`w!Z&c<F̋ˏ$FhCs,˺?u4oe-mu8Pӊt^#g[Hh`7 qxکi\di\O!=6=,2C-QXavlt@c[Ӛ\f+"19 r߃Fa[z'Sq\ GƦ.-g",lZ] =q@'{x'Lw "Y <՟kI%z[kanL3W2lmO !B?YwQA5Cmyk±bc ~Ss@RH-Ya]$G'Iu ȼMm`'I|̮޽n'סQ~QW۶j{=^_ZE٭p쯷'1y"J{ϲL`il`՝. [` >0HMPDn~Yw` c9*>vtjPh1c9Kyfq  tb8W/@OyVVK0U]P}){yj'IZjwsٮǙx?ڡnV٬lI0@1=}?麢j#d\+pq gsǥnRSҨ̰((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((}>@x: u4} ZZ%_A:x\fS=ޯ' rltޡ.GB=it6FTE:jN Ԭ畲BE:DgOZ)dm2eҮ-9{f-ԇ+xF83}SNωؖQHEPju4W y%յ-^,#DR7ϽnܶG<|v1doQ\Dz| >%l*63H>_R_]lrذ =;P3fiVʸ +o'WX}cRC[Ɍ~`#_R.w]݉0a;GSr:{P񕥎-X uI_-#=;4L?eԗjsfI|㑷T s~@m5F[! BBC=3s_iasyFG͜׌"@ >ơizKP#xjEVݺ!BqT`\[<#dS3K@7^bKh" p 20i.<7aq(lE9 }MƄ.5#_$d$jcتqyLkٺ?^1Òߕnf1o|1iz9+41H;| 8Fuݤ` <3Ң2.44]BUb5 `h7Ոv?*}H:fEwsnBn OJҢ0–"!vј PgE <}_>EP:Y[{hQ-vʷ0 qq ގ륋&Q} sG8C{8"-ݔn7{QG wm P(6K{ E 8G. #zR;rzr(+7r47IHdE… !qA)i(IE-PњJ(PQEQ@()((`zeQYQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE2ODw;^NJZZ)3KQ((.i(IE-h(QE(RQERPIERQEQ@ETQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE6OCƊ@y>QE ( 3EQEQE((4f(IE%JQI@%-%))i(ӨŠ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((hE!rt})iGҖ23EQFhQI3@ E4(RQ@ E%(4 )3EQIEQI@RPEQE%z}QPPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE?J) KM_>RQ@ Fi(bIE-RhJ(sI3I-P(-%PIEQE%QI@Q@ EPEQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ ~P(3͇JZKqI=?y?c[?`Ct;fO?^L<z?t1y?`9ץpEl`CO?E=O?G<? V<??\,c5O?G<? V<?\v1+k^yz.1hyzOyz.1+k^yz.1i+ozz?עc^zz.1(G!-XViv6m N Ggm^i:}Əkm;09b`d/ Q.Rѷ0 p52:ȊU+­/)nȖ?4(/,@2qk/iWvnQAk!a!֣K Ie-p};v(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((J(~Ri0j6w6 J VV!54D6;s/qg^zxU[i[ɘ<VyǭSܕxr] K8MrI;[MQW󬲡+n>sI6xHg3fS71j@~ʶ&WO>׉5/_`$Qs^^wc*qQEtG}ayڊ(yڏ0QE 0Qj(>yڊ(aQ@7oAE#9sz (oAGނ(Q7<y(9sz (oAGނ(Q7<sz (oAGނ(Q7<y(9sz (oAGނ(Q7<y(5kz (oAGނ([Q淠<kz (oAGނ(Q7<y(1cz (oAG}Pڏ0QEa>Q@}PjQ@}PjQ@}PjQ@FEo>o>Q@FEo>o>Q@FEo>o>Q@FEo>o>Q@FEo>o>Q@FEo>o>Q@FEϵϵPQQEϵϵPQQEϵϵPQQEϵϵPV^ϧۣBXQELdK 9g=Q\Y endstream endobj 43 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?~x~t5# q3p> }zEr CӾ׮Ka K,hO@m]UMQH AQEQEQEQEQEQEQEQET{X`?!@Lh*FMiQDOB `(I,q(i$T$ϛFc8TCEVaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEg>E#G&` rFjխfK;.N C pE+QE1Q@Q@ȇ}eѯUXjAuX7LՏ!Yk֦-^gi6~dJ)RPQE H`PD*$䟔w1m(t+-<.NNWQ%H%sg33huɠMټª̮P V|WAk^,`<' H 5vU{ǞIص['# ܟoj[XI3\yYM2r{g><1/s{]B (D2@̣sL꺊߯o-"m7N.qü>bFcSR8tI#!_@FO<<3ez,Gm6$r>,}XϬ i%^H _8M綶ΡeeiprZ¯# tOA,6x~}^CstB*l'SM<7v~]VM{)gR`|'?.uQ:riZh+* 0݃%gD !lVz,IY=II-U֐ç$7qI`bwsVl9 (M@^̣P})Кlf,ʎy!VZErR,[6ߛ׮+WQơ2L Zʹ28*A_CF[K)t}8۬.fJ'#'847DV:rT AZ)Myi}vngia ``!XӯsVN4-3^Z%(%I݀r ܃y\r/owylLϰaK8dc @6>>Ȭ" MpѲ!zv ]YVzZ}-˳x0bI c#N3jՠ 1X 660L,p6m|1:1mJӈr-7 ʎwghoI8BF qzu4Keo5DrA8:pxbm-E2y|P/c`CJw;-y1A(@^h6؇M*9clu{{ I.kbpRyէUCSry䜗sm\ l<ۋE@0ϫKjd$>(Stb8ry$~no(E"VR\20Ġp jJrq}48_Nd~^+;{5Fdb#3c5YE5:]ަB$b.C}-`y>4<(%T2z&^|>k鯮VyVOY^6E$2P;#8, "H;AjsrTOSeBV־<. @f'dyx~V1޽CFN{jE϶Cݘ]α[lvUC%n8\ Iu-S_Am5yNCsz`[X5Yai.UTÿv~CV|%w{%{LKS3*pV<(|1iZmMy,_fh0Xh:d5}ym : y t$NqҶ4I=Ŧܛ3~bIJn 600kzC=݅ĺƐsOJ֒uR]QKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKE%PQKEsVۮ?ZrwũlF窯0=>ڰ5N˻9wo~=6]i^F3%?*ͭvqiIݙyO¥-JlZ*.t[I{hHe{Y丈[6qb~=-%P_SOyD'glO56[hR mf#Pƫ 'zVlM4u=I6vpUQvR.QKEQ"W9\iw. *'ia?Ō*-a2)̎s{/a'oJ߽id}/`\a&w2Ǯo^;@^o"FuU7NZIzz_}( ( ( ( ( ( ( ( ( ~ HT~r<4%9} IEG/_C@QQhT~r<4%9} IEG/_C@QQhT~r<4%9} s^%3TnӸUQUeb.? 2RR.%"U}mɕOT.? ?.? Xw+}\6*G(G+U(-Uu=!Qu=!SqrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]HsǸrKEj`]H`]H39%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? 99%ʢ.? ?.? |eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__eQZ__AD=;/jV`𥥨bLdck~599GftF. F[*FQEQEQEQEQEQEQETCt*KGJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (hW4΀$ty_:GJ*?+?GM (!!1NvڤUK?xѽ:EU? TU]ѽ:EU? TU]ѽ:EU? TU]ѽ:EU? TU]ѽ:EU? TU]ѽ:EU?:9pEPEPEPE&6&=Ė@0"mG=k &|9Џ4JkoV?NkũՇhտ'{ 5x+ZX9O1$=q>EcQ];Q!VjƊ(, ( ( ( ( ( ( ( ( ( ( ( (2>UAOQ4GX l$ݴ@l hBŶ+ɶd1y;.Xɷ0= 3U񕮍-lH $ yWc;[pNdkm>Хp/tyVvGZDx[s%`GxM<_2픜08Ѿ#i, )k IQ3gS[٤A < :crq1\tmB]>tm6{'Z:sZ}/eũVg]Hw 'MzEύm>H6ii=WKi4KU)`yrC:oHA㓞+OqCyvoq1Q{KI1ʍn#!Dr s':K\ϳÚ(%Ac=P֎j+7P`<^ceI=e/AK6YOw._J"F`EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP44F;Ynn$X Ek j̩4ڞUe ;Qp}qSxk d3,M\!Hj:֛>olZFp=Cg@_AYչmC 28*ñGY/ ʗ GH;dluv3 HWYj@w|Rj<% "48ܓyȨ;bqʜL{ad1 Ye)0<ʸ¨ls*i5;Hs#"I}$da]I rIr$1;Z0|ſ@ƀ'㈬p>Ʒ~uC@qF 7S;ywRDƲH4ƍ#tA,E;tӽt TA(%Coo>8P?P*j((((()M'_Z((vYɴkvbQ@rAï _hO8h g(cvcn⸛)lmtvh6kOq`k"d&~ﰮÐMcQȼ[{y_)w(U(pU5tZB5ZB5dėf}Hl.uVp2Ap:=MKMxʲ*u9?w;_,18#ҒK-b]V<~&jn=n24gGFEdu$2v"[BZO.yx$C>cgV="(>*{_L[B8A!A'V~'NO>h6ZJl2G?29{_YVG(-~F;a9V*F㜂Ay̺yws"K e>]]seS;zeP9k> έmi7:ԑ"C0H;HPOsV$ًy6O%$?~#w0{kTsm *l [89ǧzE5MI,. UӚ/"K ),d`z3VŜM,BXyDLzp#>oFF.hfS":sUAoox^OuIAQ"(JGFL-'ٵk[ "W*J<*A D?"Ko[Tᓻ@ k'o-ǫ9gGqF/VQEhfQEQEQEQEQEQEQEG+RI6В阧e$=c#/\jMlĎ&#zjZ~~ֈ1V6Tc~B$c A{us7/!K s N&m*H㏔֊$% jTtp'hIEG?Z?}@U=/MIӢB w5c~~ր$c (}GhJ*??Z{kx |nT x +4,If{uf$;c w-p`˕Т6܀T+^*Fk$a`G̫. G ?Z?}@3xCw<$1wwBY$OAoZ+ x{0t\g 3Zc~۫h-d $d8e Cei b8!E4@c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z~~ր$c (}GhJ*??Z?ieViQ6䓖=M)TRh\M6ȝR3ZFQ7 pOm h$,]ry$տ!ECzoQGޢ"Q7|y (**_!ECzoQGޢ"Q7OS-$QEQEg[xE3]":1["z`rl47J ;O`cݎzEZB5xO=Ȓd 3^WFGaFu`p0\k!{yZ7*H⢮?_4¾տ&߯awGJNχ[BZ?_je}]O4rI$[FQ+*evΌ&%d(d(淆O r`]CA<RQEV,-;), RFBGTQߥVo2M4C+̞[Fmp0+Jηt[!W˺ 9"Iྞ9rz}\{U(|m֞]hI~sOOZqoݴӦeC?yHIEb/5t'WI"Tue zߚ6p5b ItOEU].b|zI'jPEPEPEPviB=BBf9"UIB SU3 )I̚d:RA 3J~ҷh ~k@Z]IZGvfbX9=n(w)wK90V2`7_·q<~TEPd @W5/ I)$#D0rTKTwWis<is<_Wis<is<eb_;gQ^g Q Q_hPEy&?_?G&?_?GV'aCze\9 \9 XWWis<gs<eb_;g^e Q Q_hPEy&w?o?G&w?o?GV'aCzm_ro?G&w_ܛQ_hPEy&w?ܛQ Q_hPEy&w?o?G&w_ܛQ_hPEy&w_ܛQ &eb_;g^e &guɿ XWWguɿ ro?GV'aCzm_ro?WIi 2j*eǚKB7QU4i4سjq5gc;) ((((}oJΏߥt(J*?:?~y{ (QGҀ$GJΏߥ(74( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (!S$ӌ\䢮tV&Z??2? Uf_XF ?O&L24}V?(:ѻEaeA- MUa?ξnX_hdh@'Gk#}bT fIc$0~N}}}>'0-Euq<Əh'8l⡏_ *Y=&7)$8攣|B;1g51,PϽcU5 =ɑۏg5>e gtU CYϫFd bӂ?N'3cXtCЍ]ZO!F.M)xy7B@'|L4U8b"F]`HSY䳖\# OR2#5#/QYiny/#q2;wm?P<5%DfSz `99@VL:i7w1I_&7;ԅqROvq,~l{zҢŨO,10v/:<N9NP)dli@UmB-l%P1I UPI@hQCi9WKlJ*;ڃ&Hn-aStb@bբv#j'PuEydžq*N,nT0O5﵋Tx-;KvhnxA=x=h(u-]=Ԑ]UU2VQE^CR{Zj_PEs^8nu̖cr gl#nd95ZyoRC4ݞWP\NF2XN(Ҩ?R/[j"BօFI\[HNW ڡM_wz}/upnf@^{ u^GEios Jywzlr9$EA/\r ր;+ĺΙS$yIU-˹pPGCԭ6ձ#++ǢA>i 5Z]63Wu֞16N =_I-mS;JN.*RoKTVށXZXV+˭P{b`o 4Zoi/N('5H>d ԑWncCA5!'hӜuh#AbcFFQN!]p%lzs5lT_>4dž->j[ݠYWzƩxva2^1*y8#v+NͩEJ>+3ho*[4}[hJ։A2j]N#dd@Ǡ"`Bc(xEIMZ1N /aieМ#ݶ˺V'Њ֟=P:+b%T?2G<$Q5,Ue凤Aυuho1 XsK`!w]gv3kYu'жZp\DX6G]O ƒxua5Ґ83IBVo{Zo4Zo5G$M1$w*90SX'Bo Mo}ƴ1O5^—o Mo}ƴh=/_qϷ>G>hO=/_qϷ>G>hG+;{ _ʾ;No}Ə=;}ѢVw4}w>zw5LQƗӿoӿo(o{cKWgaի{H-*OSJUIZRo8ӄ]:]'A5 wn*X^5-晥25r%3;#:fs[ (H^9:&yrno&heDLktNrrq]#0%KEQI K"0w c8Zv}>530K)t4P7et:|lVo"HG5>K^ZHeIY"s%@ӯJݢ2[6VhaK/1֭P'XSMH1#g֛@5ت3,@QS+Muv'5#;s'574OZHnnoC^Esk=Z[Y#nU*I1Ӧk()Ԉ0aK@ߍ_Bx,gph@HA;0>Ԭa.\w 4+4Q܉ |H9 z%uKwNy,+t #ZDY*vFx/3QmaAm7RArX3֞ѭ#hFZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@yGk ^^YXdBʨ}@P?5d~5g=["}Om}rGOE}y)vKXe^?8a=EsVrM+]nǽss \Fq4SBZU>#}!տ%[^&ukŸ-`([ܰ ~V/aeG*@Z??}6{Kd) ];H=ֹ$;Q_E:qkr*'x18+3 |?4UvU}7@'G!i?ݢWw_r0 ?#IQn>O*hU>G܌/CtTχF({E" K+kQv~=V RIY7~i,QnYs!i?ݢXYMФW܌/CtTχF)j󿽋}7@'G!i?ݢWw_r0 ?#IQn>O*hU>G܌/CtTχF({E# |?4U(v~_({E"8 8aUF(mvlGxOEOյXjmWeOpPQE%:S$d}TVф# t v[C| !(p )IaǼK9Zh3ޛvX_U9@; G"v&eKH>V0A<ךA;%$h0{˲̱mfbeP]8qYXW#FĦUFϴLR@uoͤXIB$p]$mloΪ7e 2e,X!ܳscG%j&mu.A3?ˌu# /L< aٌP۲[M 6C|GJt!K?g(1N_"b|8]GԟJxRDI Fe-Q=sPKEc]W#RMӍp0Sw!A :dm`:]OӁPy?L*]OdT Jv&qq4$@i;FyӢ|Qiu| P'rdi:pO^} ^`o-NGp#t;P*Zq#miRU* Qfℑ+Uƀ,Q[][@ݒ' EK@Q@S%+xYH_&E2Y3$$h'O $=*Z*j((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ȵi j7 [61~Gkm96*mJ LRH$d }qTr腶˒~VNw0YN6#PC =1 JPM|ՌHr;,UI?p.I71 {e]WPC6TAeNpF88?y|m<0O$1nd2Yw漙MYi&-> EPasPXU 6 fidq$̻r[!Gc@#x;Bx[.̨B2v@v~jЖV.ȒE$L;ROӊn;M9^A$OMܴq$0[$LM2rHT-Rd-vV+pnUKqI26ܬlٴXPh8+"xmB9)3HG!Fw/?hM4o~K[ڡ)I$>^zLy緺25#Xf {J[xQ66+_\>/3-:&شצ[x(fC lM82+2ņI5Sgn%L^nvp _]kwy ?o+3zmgkҷʲ_D?"Eo6+rBc ԓ46+f{MjJa3 E6ʜgGx_TwsqyYM6Ӵ!cJB5 m$~^JjG|G+J2NێFy'Z(*úzʋuqù$E/P2x柪蚖6"Z}\q,+R## ]:EΚRY8VBe+!bhs8D7"ŹǡZ:s.$2@B<͟.J֢()óFMuil۬e@}ItR~(& j(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((4fŎ6?3WjKK|Tz]1~8т}%U Ee][L1# P<ѷs.8ޫbm&fVM _)M 9p@<1kbȶ \m/b!1HC NyCq[;=*lcO#qEYU/UVY"Icxd1KF88$t {b (QZ(( E-bQF(AEPEPF(R@ E-if5SKt_5m(ɮY|Lg!}Mz<5#!}MB<4yh/_CG!}MB<4yh/_CG!}MB<4yh/_CG!}MB<4yh/_CG!}MB<4yh/_CG!}MB<4yhF-G篡_C@QQhT~z<4%=} IEG篡_C@QQhT~z<4%=} IEG篡dtF`&篡 (4yhJ*?=} z_CG$篡 (4yhJ*?=} z_CG$篡 (4yhJ*?=} z_CG$篡 (4yhJ*?=} z_CG$篡 (4yhJ*?=} z_CG$&R{)hT~z<4%=} IEG篡_C@QQhT~z<4%=} IEG篡_C@QQhT~z<4%=} IEG篡_C@QQhT~z<4%=} IEG篡_C@QQhT~z<4%=} IEG篡_C@QQhTbe'I@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@.ǧFMD mi\{T }J!mqLvGGQTjb:Ȅ>8$0Uqh?1дmi7<8ъxjRmnR2FF2:m|%#jyd$V9|\c9?utdW#W:SC#[*<ւu`ێ$9ԩ+5V"Y'i@ySF(ȥʃ:tet~U\1Z*QE ( (Q@Q@Q@LQKE%PQKF(K?Zggƹe3;"+7\ӤF.V`d8^mҹ6MoFݴro%V tT [oU{"gQ^^j.l-|ymcOK)yx^TWZ䤗eOٜ8fܜq2@;J+%jjKimnH/L 708[چc:Mq,Eqs`B ; % F>wWWw͢ViW3Hc ((((((((((((((((Zm]N(GѮ݈F\0X V W}OҢg\{xڽc:KspP9Hhƒ\u(nǗR}{kfԯgRUdWHQFOPlqf L# :$m qצ@7b_N7U 2 0A=zq]~ -9IceGJעO,47_jO.6?0~j/XfpuR68 1@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQT NLGFOM'r5(Tm:=LJPebc؞îzUP 2cQլ+kntP#D{s(yZ̼F.5սіp[l{|:s@ŭj65͊ğ,([^:Z:Mar4Cs:X* 1tWxEXi_ȖXѳcFޙgwڬGY$1Fԁ%y=( ]5=V;Jh# >DX*C y㲠(((((((((((((((s_[Zob3ُ`zmJ ۛkpbI@@{[_JV/Ҁ(((((((((((((((((((((*H:;>:ǻq# W3xI6IvE; rq5PEPE^ZkU"' tee= NS`L,6W/Jߖ;NWs&IZť{kӤs=YO\;q@.oGY$?h\v+_H9hX]-;ۛs427_tjޢ9|/--W̲4Wfe+;TVSQYM& $ެ;,F c( #_Sir}lqߖC^O?t4HbPIQF=;WEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEbx)Qdԫ# "ETӴ=&Z lI$$O֭EYXeXjxjƀ0?Zhü{U^-OT8mFq$hA<1jo/meefnɁ|` ZTƩϩin٢*97'8] ^KRm&7enH@=릢(((((((((((((((P% ~ԶıA¢>5UM[QPEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEXIXws8)mgg޴( ( ( (|A!#=p* MRM9#.#SR㑋~XdB7m,qG0r}}m=(1 ܋^=?ٴH70$FOQuZv~MF3 < ⋅Ky⺶eH8>-cZ:s[̒` ئQEQEb(1E-Rъ`%Q@bPC>/=j kBInΘ숼gQ?ҭQHe_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+y_TP_+җktUfʓZ*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_GU((OU?Y*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*_Z*1Roʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@OF[ktTmoʬ@c2Vtm[UCFIH.>Њ F]72pSԓzr|TU(Omo+nY4ejE9bMux2$Il#R2An l3$8:V1.QEs^2BncݤwP# OQX\#nP ߏ]qDw֒wP9K@|JGJXS4u(IC8ﭕ'8eONy)o.Sgdnf۴#U<=ZZilLKC"H9XgW@bSQ(b 1EJ)h(43Zgf!*ȣhsޮu}J*܊KagA\忋5{×?KDs[)`ؗX#WMUSf>\pJ6:p+ѡ<3|\9ahDMJ:h7(f5,tY j k#@sW<1{GJR/H^2RfPQ?. SxWZ\O;1{s# y47RK}"E2Dѫ}Aq2KF]=d >墻 \|hxw=g5܉l(XB2 rqvVc!?ȕ& ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( u4kI7'<38q1W]K#(b\d{9/iYqN|wӊڨ>qSJFYfO1Odx\&*Gyyq W3'uBX5D4ex$;Ax5{H49L*ǨY܌sq ,t{m6=*+!+=Ѽ{ 0@-WR^iٍqo@r!9?wC^k0QMh/ 4>YۀOkm2|C O sg~q %%"3A 7a{Oǀ,՘d|} Yӯ[h1j{Fe Xm0p~R9z`k3h>[|q&25bS<9+ gNY[eFm^9P*o@E%-.U(N  gixPeC2 Z[Z XIF '8`2WPEPEPEPQK-E/?? Pu,Z,Vq"Vʆs ?2+ƺJڢxztkD;y-ċ*02qFr+[+ JY)l95vڞ6k]`OsbG5܈>YV28v lqֱdzb&og?(} sՅS狵GX{[ e2gi 2~Vp ϰx=ynM~W,kyd^DZg֣t+Ƭj˴bAFV01<?Sе'&B!)RH& '$'kI`;O;QKw|`[8]X'=[=b#N\J\.z#L1*89\ m%ޟ6SshDÂv1I8 I#Ҽo4E?Y5m؍۰17taGD, ekRVQ%8dHE2hWb ϩi-j#W!@XCZ(((oTz*[ɦ>4S2[pCaL°qfN%VU`mbS_QnI}LSQmKkd\d#Gj|gjFՌŴfXRUd* x2\j<1e-[./d@<[!f y <1t3",ar>Ӓ3^:}i1XJ\UF 9,o+4̅G AA`Wm/';NT^GPtF6}l~ceyX֢(((((((((((((-<^_7̑fd#==jج xrVvN*2iwI1F)h޵%e8ʤҩA*dWcEwC(GhrKI+o ihB$G{ uY \wWW;םHNox e|ˀpGSZ尸3iQ'i7d `]zPat[(oV&V $P=A%{i=]Jl6a@ H$ JX!($+P3ڸ'cm/VۨæE =ѳ !g)c9A\Kau9.E2#0# 6N;9S5 u _!m?3# r9^+UmVB ADc.ww5F(\ iGl3g'#$Z`%PQK1@ ERbJ)hBQKJ`toP\W)cȱEC7oAHdT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@Rmc ~oAGނ&Q7 |y(j*9sz oAGނ&Q7 |y(j*9sz bT~Xނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5#(t**T^sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9MEC7oA@QP(PT>sz <5ނ9yׅ]ձYN_C5]>7qPRYc^>Ɍ1(#jT0=A9Vw^]^]K$rƱ8$0xr,LqWp\SΒ)>UH83RB.28-CCV(QEQE1KJ)qE%PQK1@ E.)1@b`b(1E-DŽ_5jZYnΈ삊(0(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((z|V?C[0.?le_tTFƍEoB5&+ԥ᥵Fʒd@ޠS CWtpRTSX8bSeDTI,2^}ey9t8ԭsy"IÝ2xsݎԮzxZ]RyS}u G{$j ]&) J)hBQKE%PQKE0Z((E-RP!(b ( 1Eӿ?UtfW,gDv (Š(((((((((((((((((((((3IZ)3Fhh4fLњZ)3Fhh4fLњZ)3Fhh4fLњZ)3Fhh4fLњZ)3Fhh4fLњZ)3Fhh4PEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEiQ4:|h $}XW* )ͪLhlzFڟWg^WŠ;eh#ZXH PŜɭQEtǝ+MRcpM}H$}GBUq֫x-y.MKʑ#iKLak8\=s^u߽mveagoΣ]mۅE62o$V''_CZx=>F' 7NQxcY^ib}BkƖU4ѽe$; ے:p~aozI!F>>=՜#2eZ6nn8ϼQӥjJnEbAE.(J)hJ)hJ)hPQF(EP1?՚nj5f7[QHaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPERfBh&M &7n؇IT*GJG{fxf$i=ԍnJXZ]D7g#g=紿Q_w(}J-[ޭ茔4]S=缿Q_w**g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{[*g ?{K[*}缿QV āh?@*~GZUVG$%a8?AQ@KL :JZ(((((((((((((((((((((((((((((((((((((((((t :|bN~ǍkV{Fʒ1Ok'SyDjQUY>ߥ]ۋ o8jںjrIǰ OaZub9zlg@T5ҲÞ`I:`2{~:퐊(edz8W ֧$v>Io-ëmB G%"xrFٴiq]Kv۸Rjn;+ EskGR9L"Ɍω/Mդqy"P3lỄ$zfmWII=Nn"s*OU gֶO .bj7*1L@8VGCx쮬kH#ڇW*\HzfI <+\$ddlg8htm/LE\ykUTQp:5jGQ XƠ po^}KYkB1K%4IZ+d"PYlmx[r ˹sȮƩlKZbJ)hJ1KEQ@ )h1IZ)R@ I\Q@?`mơFI,Tc# Il|)sj0 y6]$ ۸k$ LF6zcAOw^a/RhUmlޤ=;+5kk0;N!F+E.̐Cڬw0Tݬw;զ3\ܕ#pے.+xZU}NɞKY,."KxXtǥu5H,QE ( (-PLR@ )hJ(QEQEmXǔ5fXǔ5f干(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( J M4cq5BcUG:?Sܬ Fߠ K:.0NY*F jS]%qUc6!Ӄwi[Agn[C0QŶaER(((((((((((((((((((xI_U>_(%SUՄ4`MH( ` u- QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEf-|?k u7֬eB&D`/f^:pONyiWCwХb53}>WoN:UZ) uk VMvy-eO+ z iv}ƖI M&ֆPx$:}k-tKa%*Ic}1)]fw4O${OTocEx*On'Z>q+D#9%pYWst? $ѭ Z(ЪdMOT=^Er :[b((((((()bJ(VYYynj (Š(((((((((((((((((Hh 4Ӎ4 FjCQWsSVzN?2Ft #]QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQESTtnb1#5a UՔ4ajEZS0S8RAK@ EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPƋC<7n1.N LG@[B5z1 (-cXxTlpBFQXbGc7ڭ WZeͭgC<2(.'?c=敬Ťܵ7m;啽x5V#Eߢtg'S?/\z?sc :Z::ԫQ-J h pQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEq ƓoօP#-"VY#5Z,*'~AIKIL9=~KenmlIu(8<޹Ǩ/ۇUV(mGڭn [v4R\lkq5 yg:b? u ^E %ܺcAۯ u e_ps4[M,I5g2C˕o㠯X aVHb(o@ bpd/"x|{xtu?"K9.ZBI<#ֻj(QE1Q@Q@Q@Q@Q( (P Jܰ(jV?Y=ERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEiiTn g/UN/諝'tTVP1k~*NSvB*** *S?/AFue*ue(uVZhANNKH)hh(((((((((((((((((((((((((((((((((((((((,9jǩ/knƍտ#ZU\-rR{$QIHԣyY yJ z Ny඙1,O@#*`q޳5Za㰴:8:"T縂xDŴcM-P`p)\v;ʂ{{iP\H` ~ԮDdloLu}$'w طj s(; ni\i֊h9P}E-(Q@Š)3@ E%QE( E&hf`fӿ?UtfVorERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEiiTn g/UN/諝'tTUmF?K}fqjVjo-ޅ@HrXdEV۱M В,**UJnF0i#L6ϼNsPWEPEPEPEPEP/^]3FXmؤLSpkkҾ#YI6kvrkkra[z?:{t (3<[mBZy+Dqԟ^xǵuom/ c}xO5h&\- !<Ěf;x^iXAf8WkX+g}Ah8_ @XÜb9uj)CnHBWS{V nށTOK1W* +0)U)@Jԫ@ pp ZAK@ EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP#Ť@.2V$t5|1:bfbAERu^i<2,PB*Fp~vQK귆(7I 6Q ,íe၂JUpxljő?lom¡nJڲ w4n-mAZ_,I&u*3πPu\9}+?m[t0mܝ(^4h᧛L}JkMpBRFIf}JݵIRYJ @ll}cL/9, ΄ HZяQko sfǶր.REQJ-&h E%QEQE!)(i( )(AEEӿ/U4ӛ( IɹIݳ1QV[S?/\z?YJYJjUZS4S8R ZZ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (8ưVƵ3X$=QH¶+y;b((monupC5KxlQ+9o-x<_M#SEv.C#*kbsʆ` =Uދjy׺e̠c|+zd?hQHG"-83IWåK,[Fe6[$/*vJ221X?ÑF% 0 B$hh]kRWZ 1z5i4;KmgO<1wnJnҺ{_i.ka >\d0$*vKm&IOׂzҰRf`-QH(3IH4P0ZJ3I))4S{L#23so®=3?aOqOG\{(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPRZC@iij6RP/UVz~!8kwM_5PEPEPEPEPEPEPEPEPEҀ *a}/([u!GۢB,U_}/([u!GۢB,U_}/([u!R:MPNOJobcY}/(t_P*ۢBEY}/(t_P*ۢBEY}/(t_P*8I; :_TOK1@QՔՔ ֥ZjU 8SE8P- ((((((((((((((((((((((((((((((((((((((((#l}Amv="nЍhfhj-f6 *TC''`œWĖӋQ`" *ɓ.xn lCRUqJZp66ҝs0=1\kj3v0[lgn0fmȤ3fTRUFI' ஼qsɼ"GpBB#{ּYd:$-䊅.BQ2 I{jweR8Bde|m'n=zݴCR"" r,+)/鶏dnner"]rpٳUQwyYƩ}] -žquew*sBs J~\}YOl v&FHXΡ9<:d&(`tO3yeam۸('Wh >SU XBg  ր-dzγ+RK:]QE[PS,nA0΅i4P%tonu-M~ǓnH Wn;09e/ylfh~qccn59Ȩ57Ѯtt2'odַo/ keJʅ,BIwK]BIcY" Γ[è$cHe%qpU5$6vzdhQW1 =9"f dpIi3MQJ$V0f@aCs^;T8L7] 08^Ny#zt5knT63篵C9&SU 2?rgLSeIS(IE!E%Z3IEQJCJZJ)(sIE%1%RPi))3E%0:}+AKId?/ڂ((((((((((((((((((4M8M0mҤ5P/UVz~!8kwM_5PEPEPEPEPEPEPEPEPEPLqE'Ry14E>gOQ<(L_?GOSy14EL_?O<y14E>gOSP i69(u&/ȣɋy)P<"&/ȧ@ bi|<"E3ɋy(bi|}&/ȣɋy)P 0KEU=SAUʧ:_ŽV ֥ZjU 8SE8P- ((((((((((((((((((((((((((((((((((((((((#H=2g'9;hVU#[d}_sw_RQXzwek34N%,acKHU'E,f/"k#JX,:EI!^)FD *i I^Ny :*w<Δ0zW-[it!j>|D6"bA ݁Q׍rpKw4:VٮkxU&<^p}ޣnG‘Ksw n?f[>j5,sCmq3,s8#v b n8Q[¶@ (h E%RQHbQIZLFhLJ)) %@%4CЍ]ZGFԔQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE%E4MPFeʹqL4HgX "?1rK猒zSI ')c88z[ko-dAaސȿOѭ GOq be9#Tʐv)f>uW{Wh"Qd# vvi>\YjReۀGc <֍Q$-:\E#*MHb0G>'%y嵾ӌMs# Z?jWk"#4k}??%r}+>O>-KmXџ ˝I,p) (OL=nԖ5KviI9(a剕`ϱXJݐ~n*@c4TV sM27!CG*LRQ@ E%04f J)(QIE0 J)) 6HMi(Y*jPѿ B5~((((((((((((((((((())Ԕ)SSL"+ld0T)L)VS PC'VRl ]]ZFխl ]]ZFխl ]]ZFխl ]]ZFխl ]]ZFխl ]]ZFխl ]]ZFխl ]]ZFխl ]]ZFխl ](t( N @*TS O)P8 -QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE Cc$1GI V[oq'ig1iy%lwA[I*!٣4RQ\-TF >ïQi ۴jI$s,ztJ\8۫3Ȣ3K$rk5ǝ, a v]=ޥ$htd6[vS?ZKwiS]$bdhpng'84P3ϴk9O{m# ʱ@(2CK{yo:l(2T&@%סR +]\:<\@%QeCVnuw,ҵ.!0@ aqI#o=(2;[t@td?`jZJ(h% Z3IFiQIE J))RP 4R`!SRQI@n ?_CEL/((((((((((((((((((((:E4aZi ZM6MM66;hSmmC66;hSmmC66;hSmmC66;hSmmC66;hSmmC66;hSmmC66;hSmmC66;hSmmC66;hSmmC66;)vTh@ Rmm0-<-8 P(J.(Z((((((((((((((((((((((((((((((((((((((((((?R]J]3f/(7׮53[ȮZxTzc5.ld)#s;u~-/xg:q ¨Hb^%ܟrC5]ֳ2j.!HZhd$ۗePk62jZL֑V}X`FGLGF>p}YLKi;B]YBOsOė׶F4lZ,sT&qa=Ӏ}]Iq cڷZ_\dp:)lQyCoZ@&Jiؒ_^ k{ϴ#QhubK乷˷13s.9xYโY&%P lT<0g'j ߋ/7>xMhf1,J g=34!lk[O.9 W?;c=pwhgu3PK7c X_w8ێ*^/EHuKrJܣQ9qP{w,?Kwgٛ\lǙw^>Nz?li-W-u8@\ I0N:隳oX]"4Su)W=?N vI=MEKgֺKuB/ࣁDqjvG6溈FGZh4d$Q0T@YSXֺ\% ml>91kGP|n%R8$~8 DV#7n),a9 硸#phED/? B5RPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQERR@ &)bSF(Qf(?b1ObSF(Qf(?b1ObSF(Qf(?b1ObSF(Qf(?b1ObSF(Qf(?b1ObSF(Qn)qK1@)h ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (Ԁ3FklGyZyv \2HK{irH{]=Y<ϟfs*4,q!yQVc(2ƲEHBy c$lι4(tJ@/{y /~a-ֹY-\\Рo69*mږ~g#2sөI{d1ŏ(!(cWqiV0iXEnm"~mrz}sPlaI1p~8EkztZA^~2O|cg9LYa Z1S##Ţecve唑s3^0qx5K+ TTy%ITpkZ}ɶ܆7,Q(8;C+h \9} $֞t=9t灞cGؓÿjX>y#H7fh#! `0pZޟd.)A6#f*_su\ -.jyG(EB;t{q!$O)uŵ|˾`1`cRF lIyKcE>^̶I|n11:=2a8px7 /`@'=:SS1=['I]YWp9#ؚUTEEUڣk# }4aI6nP[c@4RSɢYhV% ¨.j%$G_ѨR &捥b+ɞFGlC</օgШeQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@1D=7/zꫛ%x6 +c+G%%f Cg+MK67q9Wѭg)۝Ĥ{y(z&ǭ_ Ah2یruaXW6zӮbR mapAbx)cY#qGzT? * !'s}m-I 㟺}s׽g}KXkS*\+J$.^ZXXc1TO9dt!ȍ h{h/n.!i{hC?]E FRC-ƞAIOЊMK/8 4E,#( ,-G2K!A$Xby\K1sl8bxIC1I8pVRyՙP*; ' I9s I%lɍE#`eV@ur^ZU'GUN#،j SV{ KK{aqut[VFP yq5]2MWIͫۂv䞊9u$8NnrYL\ߜH?~$ٵSr6q!J^]5ZAi`H>ҡ.7aAv5M;D+qHic6늓^_R bАxOw+{O[];i~ӵppjMvyneLӚB1Fa(9l{G5xjUI m6#UE $ZK_g oJThdQgL"I͵{i&a^' ر-1L[ RtU[>4$%pJ M©yʖ= t{s@|ty屆 ;t -0{U5[ M&š{s<''Z(-Y.28+.m^}jK?tOGa*;w?{s@ ֖u K!$ 鞵=pkmO&=md!` ޺m";eWͪڻ;̀c y#Bh-aR,<1yDŽ㎾¬PYֺŵ얱ail=x5.1۴3FB,H;kRchcSp~{d@e6GgѬ @/֍fAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP4&w%O$})=󬗲0elra&mKIE qhC ʓj=:o팞[G"9X;u~.R{ˤCH~fV[SmIM $3rZpSҐ!{ii%.,!>k/5My+=ػA";$PLmMttW(ơZjW"&BjHF2A^{mΤVVY0Z\*I֑E!|¡9oN$5TBR<0ez?ZEs^!4"hiLWf8\bs7w R_+1,xb;@izZYc`mbapT3㞘5uyv1$ .\umGP'B[Đj3;ƫP̨3=Iҡֵ_.[g>Xv#|YI\ig{c1.%v+62\Vȳd[{;wFXV6n?4M>K[aUMow 2D?_3zSn-\}Q8>_6LrVNeg IpY"ك28'IEJn{WȂF\NƦRI$Hi$`18mܵR]BY]͂O`GCɠF%-%0;M @/֍gh?Fk6Z ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<¿.Z_C5X^O{I ~_ջZ=ͱSqwM-%akwfxKp]>R 4UJ(d5loV*G# 5ϧW*r)# @Ai30'وԗ+Xy vVnu^ +{cq$IcY#utae9zN Aݺwv>ZdK{^ˬ F+w8K0~`A 9<mi/KiH ݔTeYTF-C!߷<\}֡–jIKdЩV%`(cZ2=h~? } eyi!I- Ēctes=*wߛm$BLACn]>{29csSَEtnl|A̧ܸsq3Cu{%M,VHPaq׾kw4GZ}CH+(hzT_TSmBʱ $axw3ۤ#>yR~ԯfVN0Z]MF[L;H[#Һ Jg}>$rusp/)xDnI@%5dG,Պ3u1E֧n$]0$~ȊnDڂ`ܐps۸5RPRRJ`vm_ oЍiVlQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEx߁CcAA7Ous;s$ׯ~8n섬- 9hLJcf..d!$۷A}*EqhH Zvww(N߮+8RHu2 9Svq5ʬU!JU.;jxzUm ]#г¥oRӬ<Ѽ$URdÿbOM;siv٭$$G$V'ǭ>Wu fgI4p a݌?6:vG4"EX兑 x?Ocoz4G*̺{,Q:|xr#T;}y.9ȍF?5kivr3mh$!

'I}=s簅-;q4-O 0ơ}{{Q[u( ]T'q>ĺ&[{D\H]\ɬ\˅\GqׯjffbX`r ? 58a-A~EeJZJb;]@/֕fF6Z ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<º1\V,Ì8f'h~#ٕl\7ѐZ))h"<`YMIU%+9ZhXƩxێ* {Kvq)R=iviIpFݏ@5 H:8y$Ñ=HOȵל7OIx-VZPhՠv8~Rru-2T  vIIcgr#\rUH4?__>JދF-L$#HYyۼ,gې9j:Zns~m{e.e>/q^`d:qi{O34 %e(@H#ۊۛYy F'yPImsM[> 6*Yg#vk[#U{--nЏ ܂%H~*ٶ}pHM)R++zg 33[mDW$YAmT^E6gqTg>+]Lv_q\gA]ek42\K$.S}0bA` ekK5x!es Pd^HWC]5VR[6z1qA3E4Q(Z)(4f444RQ@ţ4PEQ@%PEQE!ES(|? ;okN(ݔնBG!Ev?bXأVcZϴ?Q+_9Ev?bhأVs:~k>߱Gح(X㨮V}}abEv?bXأV\,qfZϬ?Q+O.8]ح?(~ m}}abZϬ?Qp撻?ZϬ?Q+O.8+~bXآcOϬQOW ]%vaX?أ6\,qTWkOOϬQpQ]m?* &T#+ 4\,[_:_:C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<qoQK@2 Utw{1eH$dϳ ix: 6H$0\@Qۜ uxC%.pvi_Ҝ56{"\f~WW/Ǩ]ijE ŝzG]ķd_jJ[FY\|Fz3cd~s48s,wRBaVXn1֐J:6r˧^D.$J4$Ir FqYx9e%:[sda[q^nAX\:V[}@̆yy ľJ{bYtέiVqKʳH;m/zߍ:ۍAdTe2$`%x#+o'mIc xc!OBk5/f/o# h5!±>LVRw.=yZOݤ""W;6Nr=(sRb7W m'98+iH&@r5WK=ƍyqǨNF;YaSȋ(<ϽW}iq ղMnA(j( ( ( ( ( ( ( ( ( ( *drkvl$އ2d  s֊(Gӿl QE1=GJmm V*.IPQEQEQEQEQEQEQEQEQEQEQEG#ZtQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ endstream endobj 40 0 obj <> /XObject <>>> /Type /Page>> endobj 41 0 obj <> stream x=K A t0(B]xAPi]x}; E(i$p=k;%WȶX#e|]ƒ)]G# QyNNAybo6 Bs`2k~/%# endstream endobj 46 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?KK{XmaH`mDAMEQEQEQEQEQEQEQEQEQE UЁ"~Ub ( ( ( ( ( ( ( ( ( aI4攏2 :"*"e94p cڴ ğERGnu숄ʧO#jk - ,𞵩EJn [a2᾵]wW9ڳQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEż7P<ƲD 2 IE0<{]?5&1OO(zWNM6yRi, ,2(N72=zWD [T5c8W.vځ-a4j~G^*iTR엏wYT&3=y^i˙0fEBhд! Zs6ީϿX&[;tq\Vua8J{/{Kw 1]T'=>p8w5|^]]B{k=:qOHlxR]\ `U }\.|YkQ_xB=BM^9ԝ&b @H9[V[Queue$9 ïcKc̚{{MkWDENU~Rr9 ยӍ5iKg LNC(?7~1VGEt/>H&f.r4esVq@ _Ya~miB^El ȣM8YIo{Eh" g@%gpO"}G]D-HRΗpg*})K3옿O |ϵmA{}+P#76oh* eH~A W1-3T >\ٛ l|,>PG ۥ\mOSڅ$zg}3㏺`Ñ\N\*}5E\1銱oǡ53Mu ! Ňn}}MVMuKlfW18!`㑀Fx<ԃĐM%+x_y"yVR#HXx ^z~w2:zK+q'9 F W$3eM Y:% >XkƩ(kǢTfHWGɑd(mkQ:#ژیO?6?!¬챨P9f8'}R:bۯ wnw€?M&{;!<2AL`tcԗv{bDѴeQA$2MŤyZ6n`~&_ jnyY\~o9_R,zseZC{xǴ}Oz؊YS|m 8sm.x{_ jW:s켂 v0xkޣ8N8Hc2r?خDYcheX#i,%מF.e\(Fzk=C6_m5 K>H }fF/rpu sWMJoд,HdΒ98eI={]/棩y۾)/w99ڨZv/y5[#R0DmoS"a"N*((((dщ"Ρԩ(XgЎA9D ;jcf31=1k35,u*$###t;/;@[ ƁHc$I5SZDUc1Am+ȡL@p_  HRٕ2$cˁ 1uke9 OI0NZu=poIKo0s/^3F:ʭmeі-w0Mǀ85fʺ،~uZ"$+2)/t£J)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hJ)hQKE Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((Z((5y|fmǕۧ~Uo-f _Zm|V1C2FZt*3vWڥmas$Œ9R0ZB%P"6!<ԸmǓm$=v)Q[jS1zhf@WbQKE尳|Ktְ]$VpxÄ,n _Vf^hR,PB*Fp~vC3MLӭ-[`C ":Hk4NėKadf(dl Z1(F~mnVȕpޝ8=G4ɵKh!%НG1Ko% ogfHݻXoK$v9C" ;;9j2UO0C^=|A_ B=[~)[~yo(o忧@ 忧G2[~)[~yo(o忧@ 忧G2[~)[~yoSQCd񊖀 ( ( ( ( ( ( ( ( ( &*CunVB3N.jM=GkEw Ç7>|Bx0bP1ze Y;tغ4bԨG)]Iݤrrǭ6bϿ>ZG+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+PbϿ>G4{+y/aUКU֞$9cE d7~8*}@EQEQEQEQEQEQEQEQEC4D@QU7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(Stn{㢀-U7O=F:(St4e'1@rʧqEgoP >7A@Vf(LhQYi3EgoP >7A@Vf(LhQYi3EgoP >7A@Vf(LhQYPylbFD ;(((Ş!m3 +\JǻG\ I!>][x/36rJힽEy&!AoC G5~#J=gQ^cxR5d[g`UdWW' S $롉t:Q\EPEPEPEPEPEPEPEPEPEPEPEP|cSC__8Z׊oE{XDkEGXQHIP"7guWO3;&TdL ϳŽ0s΀ ( *)Zpʎ6\ui k .8ybG QE\B nF'u.F2@@I\6q;} ^h.BI- \YLw4QEQEVoZUf<Ǎ) A>X3 4ExЭ*nKO+uU>\g[BrNQz+5ƝWywy"|ϭ{4TMYh YN31E{;tC E9SӜ5Ƶh:Uz܋bHw.gh ЎX^'TkDG~ErBrVhHTi" H$ddv}ViOMws& X8 5=n^0R"$;|^vLjm#{ V)hHr+Pžkj>"%|o1X╛ˑ`r#1ЀޫF^]lU '޸X.u [X%ȐGa]ѐQ19G&iyo}mo 1ʏhVWPAR2<K\%66 z_V!mIۏnF1yI5WMh1gvvӅOz(ѨVVԵۨRNL1b[%FrҨh -j70H-ZX&$H#wW : w4QXڝޥ݋[=,F зfQJzulT46Ȳۖ<G=2x3*)f`I' /$>˨+ 31`{9|u!<%"ڳZgO~4:fjw|۔]̧ 'v5<]O{mlJ$ןC|>{[KYnlonFEWF+bg`7SWꫜSgsDWF 2)k'W^[HXWk;H$dZQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQETsjKP̗skN%\ɽ⵺$7aC46= 3~I$TQop\Ha OåR]VL վIU17#$*ѢsIX /onTSNJ+\v8'ʥ^6ѹqOk$۵mӧ|Sլ`k T89\/#-6yd`;^,f4r%P@`:㨠kMRm y ]=:~zs6_2E 1zJ i-]A>qy C#p9I`Rr$1NF9?Ȋ,B(в1v2@&.Kt…g >Sch㯿oZݢ((((((o_uhǺ?΀%(Oĺ^;y%hbU2XvE!Gܒ>j|a_jټfln~lOM.Qk$hv[IZ$V.N\._H?K?_e7qgw;xkg\w?mJ+rX|6|?_/m=;$D*qQWW^Tmsw<8}fm"9¥<1IHn\L7pE֮. %UU`'2mGH*$MI8#G(ßFR? j6wvN̗ײN@Vi$q5Mω4).Ak"s*Ʈ́RdE>٥Ɲ[˺5 À {s^_YluCL '',lQ #Y>\Zx}Z-#Q\Y[Vt1ph[XK#k#pTԵ-)`kY(7`*3@~:/u'I'2v!|&?xʇ?xV)Gy,e&-6#,k,`]Tn Ap8ojھ\iD.YvKGf>l$<c\m-AM ‹|)_rMo6n};HC%F~pqǵuQEx/?7^ɭF{}Q^1Q@Q@Q@Q@0LʊQ\ܪH$v~ҟEQEQEVoZPr={qcmtswPc}ݳxQ 7(&R*OUDTQQ=A5ɐkX2# 3'=WJlmxº̏wEln}=4Kf썼m"6[ `z 6<@Iag)s%^?%F_nvUh--*JD‰8a[g?lxNNh-&Zv t$׎6 qeW<F<(z{cj¥Q{ue"흽ʩʉW 6<@>('-zcWnt>EwE(,*)#[g?lZ]6yk+i%SJL8)Fb/d`/MX?Džg?lmx€MxQ Zt#enc$!q1# 6<@}K/2)E8SXYm @rhÀ}pEO 6<@E 0RmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPI 6<@ E&<(?Dž-g?lRmxxPsj~<)V$aq-VdZuN{f.Y98 kR%hhQl{fÚmk A7?QG٦(qj.'2Ȇ2J\Gli>7?Q@IAYaF1=)i̒g #io~MPqӡ"#IHAeʌǦ{╴F[!Djr\և٦(4EQKDG 2)f 9+l?*i>7?Q@S}o~MP4Tfs 7٦(4ECEMi>7?Q@S}o~MP4Tfs hǺ?Ϊ YIq(@(Oz^;yi!J"%0XuF~j(& iw_IǜG;ʱ$#4mak9amẰd yer'~Aִ vZl6K9kۏIlD?ZϹw]_k7B*F:5EcEMC~J|yI=:5E08QEQEQE:͍ʖ&EVZ_4ڍY09RmƱ|TqhA:[]^kc$1,*Y7~5$f9((~0m^)|`SCz+ Mo3ۨ_^{kq¼fO#{/Q}= =h8G(l=:+׾_ٰGQEr_^{{6(_^{kqf#{/Q}= =h8I= =h忶/8G(L=:+ؿ_0h[b{l_qd#o/Q= =h念8I= =h/8G(L=:+ؿ_/h/8ZEۤQ mJNJSMڢ+2Š(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((*9!q4qF:~fM聻QYۺ?l!?Ə?W+ +?wG'VhU?!VA[OwG'+C4(?GA[OTWthQMD5'WF2ӫ2ŠkHpΠO:/РL} <BE3΋z'(} }:/У΋z'(S<B:/РL} rʰ?C@ Eʣ,}M-:/У΋z'(S<B:/РL} <BE3΋z'(} }:/У΋z'(SVDcu'Өľ/m^)nފpG_H>Јx;Oq>5Rt5$yb3灶șGWvw̐\qZƣ6pQ{X^*6vƱ¨T:mA";Hʨ94 nGyŬw24[|뜮qx{Um?źuܦKxᙢ>do|WQqFHj;vV ׬C.́oz7o{S-/Մ!}W+uŸtQa8{ka卛 PZvw i݅ut2(4TPg8THbQE0 JZ((({vztHVE#\ iR]ipZ񌧕 㟘Cڶm,m,"1Ymm! ';V\Z?r?RΎ+>T^y;>эzn1ӽrYu 'k%7j +5mԋWx_R(xG[Y0*1-75=my{[cn&IW2vO9Fm-ZBHTJ9hLw."?\r2(((+oei4$U}:HL!dd2WOPWEr-촨$:b2ϑqO4HϦ<1A1vgM6gcmw<+ą_H99!q3xz=RIimrI n06}MGݿ{UO'u"do'_™5Daw\QI=-t;?h]X& )+$b^eFo +&Q'Wf,KsnI'$J(4 (KV ;3w}24?0FY$I jQTQOrT3pQ*[NlI*'C@7vb3uwC<>=i~h/ڠQ]0o#o\PVfI"TTs4za%d\֝-0z΀5.=jVLx:eQ@V>Kgvb7+"PTH9?0ʀ6(gS w+fO*\#T8A籠 UZ}Z}!=vX(pEkFţV=H+Z?x?7^Q$- D`<+[\\+xYH^׌[$5ԱX-xĻ0 ~0Ae{ߎIN^ ]AH"f²mm]I8=(S)r#mއ)Գ(`Yzy38`:wvn+J&<+?y Z:̊u緶6{[ЂvyOj9wW T:6V%?!N S8Lϝwi@_`QKEPS(ZJ(J)h%QE ?bW@G,tUʫj_uynm pzWUEr mMŬVQ"D239*ŞimkqtM̑c] F1:(&𾳨[Z͠7Tv "əZC#G <{ KCuuGKC}F[THlI S#$voZ==ZBi.1,y7dA@ dZ뺕՞ŜT0%R4EyO1]]1KEQEQEWs+{BAO8q4r |yEp }JBbԭ[IW; 1с/J4h,ceK!kyfߏ@^ӴbފM浼Gc/X N+v(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((!Ezyŷe}oOni3c}yq(\`=E}N2sŌZ4H'hf{R5ETW7 s#vY i~%[ .1Wgl%OF2Ო8xV`jJ_;;Y{ qKاySy>Qrڕ^j7(i1'ZE:XxR"^U4g_Fo +&Q'W8F:ˀvb ,F*Q\#˿L(ü+R!m;[Mn$n H[P-jf/$X2HxR :T ݁nڅnBD}G=vPajzyQvҭ63X2FOV^wkׅzE%|ۂS|q]7HkK:\8! +:fkb(?S?kVLx:֬'@ (/ZIz#4ܬTc[P }m"+ Nh^t=Svq+ʌjy Iﬢf+s"=:N;-vTP/H[P74R[୔x. q]PEPZ&#my'R]Cz+ '[JY}Em=պ HėhN2ǽ\$3Qqj[j_znbLl)jsa}SXzmViY:tE@<5 0~x9 +6I'*;ii)+[[$89yeurn='IFAl3늒\%$4Dؾ6˥9>t7K)2Jn,VL9; U'MmYi:fY6m#H:{"$4^O1NWoRN <L! ւ_) S9 DweM'_=GM-w(YIvޘQR::1Yn#gЬ"2r]~!FŲcԮ{V0tW8a҄K 5Q,a4 3#ŷ{sZS:IDqb3gJZ)J)h%QL(J(QE(+SAG˭M?*g𲣹QEtQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ek>ODeVp˚̙3\WG! G??\}}]}OTrtΏ?75G?t?+~ohsMuQOG! G??\}}]}}>CQCIѬ[c{C115~+R4EhW{8OYeoaEV _Q4PoaEV _Q4PoIqI5-TR)dQR@}/?*[1y1Gbb@}/?*[1y1Gbb@}/?*],V'jP|\Cz)~-m^IaʭFvQEyXU VaoRq'su8֔4"A7>*aqq''QϿ>VmalRsuWFpFHJw8?O+>G09rsq޵/u+^I}<ŧ2oWuKm?N%ЪYU@N(KO~޲nt i AQY%vIi٫QEQEQEQET2J  >/ :( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<_KkFoO k(U3WuU3;m j0#q5EVN0m"2YMjN/>~ϩDdAX@W^# C案m{ϣXXʨssv!!8= ue"6)d3*m / s!is vm 0 qha ;g6f6"idv$g#ۭO[G/럴u$I3w66v?wSi7& SA=b)m;[=[c*vI7O.1j QI{#HaiIo}XWΗ ϗ8 825^KoKhP uU gQv+#wU"K6qpʬ*7I&G`?R+4TKQ&-He+2@pHȬtt63żI!G3:faUӘsM#&$ϰ˖l e8qzmQV@Q(PQKE0\RbQ@Q@ Z?s?Vei_1S?ޗKk,@'wIԿ+85uKkv5 ) 1|ˋH#ҥ"P=Ć;,?x8ǥ@ze함naq Fx G3TD\Vo -Jl#*oBѶqѲMzE%`88q] kW:ΘNV+3NU$1 f&.f+[4u[6*FN8 5Pq\ Z)u+y߼ƆA;& kc][> m)smPˢGieZGw&$VCF >bGlO{+^^ArqP<{EpS/5~޺m L*?Tq5{M{oM$V3ʳ<5 I!8G*+ ( ( üҵ$nQs)eSAۡ=9ܢ*km vg39,ǹ'xS)OiEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEl$ފ_?6[גoE{X#ʭFvQEpaESPhO&T;& #).NR-Yq^ _A#RAЪ:9b Tc#jtgvg Л$xb4gLfPJ=(%DH$۷xQzgҩj来ʱLWwf XBsIswk=IXT`03Ectmia]FgD_EC@}7ahL? C/i/ii04ؿ;Gؿ;S}?bbM>C@}7ahL? C/i/ii04ؿ;Gؿ;S}?bbM>C@$Vn'qNL? i hCGahZ*/04}%L? i hCGahZ*/04}%L? i h ʰ4(@GahZ*/04}%L? i hCGahZ*/04}%L? i hCGahZ*/04}%L? i hCGahZ*/04}%L? i hCGahZ*/04}%L? i hCR++PA85hZ*/04}%L? i hCGahZ*/04}%L? i hCGahZ*/04}%L? i hCGahZ*/04}%L? i hCGahZ*/04}%L? i hCGahZ) 2E-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQExş?75גoE{XDQ\Xb{{E[e #8($8EŜ.\޸ui\[2ܨW.HEtUάyd<4)˙lj4M[W{ $֦%YdO-r^9{:kZHճ[Gi)p<ƊGO39;sso$_sidcR/ Y^\ʶ2Jdd,Ln3^9P]QTHQEQ(PQKE\Rb ( (Q@))h=?_+2?ʏč @5ZrMe퍰:[|.!JmUsOB~]J/ [yo{I#E`lTqs@lj-f{脱f3 GycY-4т m7nhh/CuP[yfEK,W>Սk~%TZdRخ& nE')M9<:$R#I!PYNjEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEUQ+0̲|Y_=1R\yk Z)T21R2C[$N9wm *s:΢j7m|hW\(Qӧ֜ƥ6o p z+&hM)o ɍu|fww&CH, 6(<[m};̛5^Isgc4ḻm-#bŕXH9kY1GR6[20H|W`۳$6BVP( ( ( ( ( ( ( ( ( ( ( ( ( ( ( )#9d!֪kXei?lgx\ր.U2|C ʬ?ׄxqTjs6e Hj)qBFQԢ_t֍XDb;yĠ1)%mTgӚc,Ҍ'FF㿁PeEsvy#kiٖy#&UNqvQm& :tu LK.\ax@ƿo'y_X A+Lv}9u?EiqCxn-~!xfZJ)m:,r[pV  EPEPEPEPEPEPEPEPEPEPEPEPEPEPE 6V\1Hb]Ip(j*S:eH~o0 4rȜ81V?[(((((((((((((((((((((+:kc<.'f97@߽lhEQEQEQEk$ފ_5[גoE{xuM3Jʹ~i}NB\x$s bLd0mgִ⦜"n -h 8ɍfÞ:ӥ󡑃1ZUwWֶ͜c v2ŲF;+:sPhu廰ڙn ['tYxvIgaM1ӭnj>.v)/6AWo PGUYE(S4J0ׯ#^uݡm^7m-m-4.fyܜK1=uI$e{8YK$7#*,ݡ)%בɭk 8>K9$(gߊi>,QK)Q@ )hcZ1@Q@Q@Q@)1KE%PV\Zz~s?TeC7%Rfڌ-du ]r.cKH\$d]JS+9<7Iou}]"+\_ O3#)9P9J(:_YhU5#krpd[F8jY=NN.5K8'*3! ;=vPjpkjnu8õH~8d\zƭkm>t̤Y~^㩯K34{x~=|(3׏VPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPM4@^JGw~Ŏ߮GE5|/{ꥨ&u>[P&c]X\gɹ|u qYK֯ٴJ[=&<~@dž]BI,:l6NdU#(FzjkdicXWIImS tŸҺ(C}Y6O*pxE̸m%H'0]jZޯyv Pۈ `Hs 0FH#^Ьl4 ǚBȾcI'M>9i@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@diUxY3>gM浨Y?zfǶ1@VXD ,n85v 5F8 oOoЖ&b.WZ_-5=GPq2"P29[P3蚦w Np+F*m9 m-U57Tk m*vpH*UU!iG!l"?Rڮ(Q`.%og`'#|c:WeEEmovo_2FvY'5-PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPM4>Bgɉ1Z{C֢e-?Vꭐ8sPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP&+dhHR) >#<9}-.`-" d/ L98Y0{dQ@YZ6靑BrrpMMEQEC><ؒLtޠ+h!bAlF T (Dw#U9W /'nqni yd&NOGJ<Ž'K>o:?lEkäWZJ7ːr6zyO3(e@L8(ǽ ]6bos\4/i갴/jvw Nyϡ֋ wTdӮT5{/) : Seuea6amG wc82E9Y0\K,PE$$Dp :t5᫭N.P ȎL4;DFNDx -mL zw\RbAE ( ( Q(PQKE%ih1Y-?*g#~h"s5%t}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@ H1P)Q@xO֏CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~UTP20j#m ԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP_fs5-١h4?MKEEh>?SR@}~CԴP` Z(((((((((((((((((((((((((((#UyQSFO s(E3*Xnt8=VkXђRbrITowrdյدϰjյ6"d+]3%އf' Bm7sumi7gu>'Wgnۜ=qiz{FkbŌ&%)rN12vBu4hkHۜMjjv<*4bXj?x} [<+S<E9 V8OME1WMKxvtz"P-<#-!\vlg{Qo+{o Z,9zFsqzץ*E}+ X_XT6'8pHϽ,zeO3cl8"VX3w5j`AkeicxXc &gr.m('ı[=y5|3Y6:ٛ[i$b%PFJ28 zR55SQF((JZ((Z((O?b *;`A!Fp'F`Y I=9 nNnbK* .t ?sJq ?Ժ> I0# X $r3k-w/~we<" ^{4#tK 1ox-oZt}fI3Ƙ+@[C}w3,!n9Ȉ C9c02rqZz牤Lѭ?MfWɏŋ2sO^c5ivV\,Et؈$+$<AĻZҴkIDHuN89@=VU%;$7ı$y8ퟭsZ[k{koy`gY%!1Ar1:3O;E=ޏggڽ^\bI@C󍡉NMs~uo-s,p$eYT&G$ 7= :>5=otBX?)#<vOoo+^f4HjYK[ SB7m .(n?iQi4--ȎC+ƪYvkkzFVGrsvC3(Ѵj495kPbͰ30Np8om |mW_OOg< ݨbZK[^u:kuyʀZ|Skf#Xq{C;>v8̚-Μ&u K S\*+o 9kbΩM41ܟ5HEcS-aNx/ G2ߘBf/M$dܯN܃8tiIFzMd5PEPEPEPQI`I8|oT_'|+@ψt(ޥHUh-$wQHxm+1O"j:vm$mDex)'V/t[jB3ݼcoVVy`W($OXc6K.s _n~sA2ZD#J4$PlU#ڟ# 7K0IPX@w^- 6sghm湶iټx|$IsۼBjIO7:m>]bc]Hi9d(Ϸ`abr* rY!8U G4|S?ek*c8$q\w4ѡ;h/^TԵԤo,Qp%6FN:v]MyuSK5 CG孃 {mF#$ YÆۑ ;פyӃA((؟F8V>qZZm\Ĉ-y@O\ec -kzm$wb6sG]M:nb>U96o5wP: Hyܱ).T;z2^/MՠXFE5?FC(S|985ۻI-a9Whv@@r3i6t>kVf2)&n\sЎNyRx^Zn5K]L SxyRe!F00Ct>Zm{tKr"9@?Ғyn"HSH5<-x<=iKhcX ǐ0Ys/nEg:]%5}nXkqꖗ(EZK`Zt6_G-fKxYXG˕Lkc(((((((((((((#MyRSCz+ '_hb+/]bX֦(iN|RT<\N>~ϩBbAX@V_×\׹k:=ƧY^%x4kkjN^&j6VRnn亵kW Ƭ`\.'g1躄piD.SP67rZ3^h`XJ3"J}QX嶎=:#"#\z,9(9,-ZOXjRweOW?("D1 0sJB 6gh3JKKi6',g-[xm2 zmmm쭒A$=TQ@Q@Q@Q@5P+9OQ@ 0fYhePU\ jn?JѪ6sT,UeNd!yssM6nlmX@cTZt}H;[GnEPb|;|NN/pxa>ѶhQd#H"V*%*0V&?Gԧ\E \TJ¨$8hڇV]Bn8G$9@N" }+o⟕qI[Aw$[+\yp6z6 VkwKI"d(c. xɠV?*hV|2YA~+&Űjh) g `x u[w/mN֖S(@?<:d24A (##i# EB2AJR2q=S8C+oO%HcXEHaUFSD3 y?׎*J(e˂0\EQEQEQEQEQEQEQEQEQEQEQEQEQEi4ފ__4ךoE{xDkQ^qOQh*DKᝓvAr{4Ke @H`iE'4ow P/` snT d`vֹO=[垹.?tz5ŵʅs! d0?ºg5 riIkWF9CDhʂNqs8)xFy#MFevee)#"j%n`x|']w&q [SȖ'Uu%r 1sJ"猴fط&"PKfIzЃrIj8m= i$(S#qO#֌_ EU(PE.(bQKE%PQKF((P!1E-R(PZ:/~?s?V~+GFg,U}tQ@4Wۭdgm>BcO5?گoR1jU;%GsdI 4c޺5eu H ^C)q{E9ŭ́ &3ԱYbQ梵<j+wQdS%8c2K"ƀYd׊{eU6f c$~ct^kE' zp*}qQ- s E><6ɞF=@E69cIppZu lE(jAڤ9TFfhǜvŖ98x K@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@AcOPY|!@W[Բ(ts*E; ~x7Xo]CWiCj[:7gCHv]ދ_]C=͸"wl9~jXpиxD!<y寇o-5=pjiu#ܲ|ŀT`ǖKCO7=`5%$WYP?xmiX{ pw;zUy2EG> $i fT(rxyҥ-Phzݺ,6yo曄,0ɱ؉+ mkTCFJkPZ[ @$Sy-&J].]:լam~IV16e1jD$`&9O>i::3vb$$("S"RBMzxM_r;]Ox;u VxM/kcVJuQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEM?ZI@CKFO #EyWO&E睁Eͬ~a}AW< y(9rѢuHs?ykL΂G<8qֶ52Gd]_YrYZZA5ؙ¼2wcw';>$<7qyҹVwtQF)Q\SR@ E-RH`%PQKF((Z(Rbh1Yգ3DTw6V-ҫW1QEplZm >8&9+lVĆ6-QX [D&*~yyG_Q&|2_I?J۝;P~Hl?֬h-ncumeA?WiKtF IPjO:qTӾ_OEq_\/_[{!bF@UkR+/ZMƵ3ʒ41hʐɂ>`@#uohAmȓqd*L\^J-/{6̐@ܮ Ry_FmƘyvډyk4cp6LZ湭Yzzf Bw8d/]% ^c."*!;e yr R2pk֧.sugwkKx$x̅Qm,m㜂zX_ 0ѵ:},@#2sd%R<p\j= -o@6J>B3\R컗 6-MgPP#yqI܌\Y[6imY*XBCc'jCNd$1Xh (=#?{(2 KۙA4nt(A`mϮiuf"$&Ъ35A<-=Ci_\˨ng´bB8 Ym4H#B%!#rg=(!AjDZ"VwE] :z ғşa[ص;"`Q Hk`r\` }*E]޽1+\H3ٙJiǠ=rL¶]sy-ڢI<0*vך罞k$6$h#8*SZ&Gf9r0QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW/緰Q)6NO OkSSa?5+Y@L :6\q@&}3P+tr"dE/F-6 qwşu+.fKd1V`\9Orybks$,hwGڽ0G>˭\jqzq(X١*tL{Os|Ak?miiI,GuS S8~j]\O۞'(#*9Ks}kU6m\[^]\ bH7L2xz $h/煀2A H99IuFK=!Dr_,dNL#L* r z5K=ifS pgW6Kik 6oaq/Aqu;KL-V[&@%{#<(l[;9'tj#*ݼu9<֏oKBj鎾T'f  pZղŅϚ<:C8X C`qN<;i-Ms;M8W)Hg- ˖MͻIex!}y<5PՏ$KCCrvTA2y u95x-gcRYb1ya R{JB)n-B //~\M!b$qpHh]/.bYM͝$ܤ|P AtˍYb9"8;\pAzuټ3u]X# 'dy?2)/<+ewپ{ FIg؅F@#=j"C5:-L{ex n{Ini> A#u_ ǫY^> !uUUW )#Tt67*ʠ EpG_€/b[7c-H Õ$,?n}S<>$KIY]N Ƨa'θۼ*^ZŤs1^0ǿ4:֑نiu Lwn992F?ʹ2BΡ6G$t=*M"}1< F992 mڠ{9 Yϛqw4u ԗ:$h{V KYӶ\ZX;Jx;4lWaY9Nf}2љ3BrIS֚r5帼ɺKyB˂gȭ![{xBJƁ=p(J( (9>/o.^ 4D* էԖݭ$N͕$` ĂE\^7sCpC ,Du AA8zns5\^J\ؤ( I<EPEPEPEPEPEPEPEPEPEPEPEPEP*8~Ԕ?h5ފ_3ךoE{XDk}sP}7M2 OڮX\dHEF:NȺҗ+WgbjVc!n=is[h:r?u,̦9叄N$߿6s+[m}E rۈ`?p_ڷqhԿZOeQ5g U;quCsY o=iţ# HΤƅ4g^moOPU?DhOIOPG??Ə2)4Wis[h??A=&Wish?7?A="=Cz\?g?cQ^oo??A=&o=Cz\?g?c^moOPGL+Ϳ4is[hɟ я򞓊J=Cz\3dS+CFg}kPdTdru\:̣HC,fxxՆFӡp*Y# Zk,|JA?J'ҏ'ҽ&o,=%K#V#<~ҵb#"k,GS9q.:L-<2o^>7?@tH+s(tq;o?ۜ}YZ%FOԿ'fzM8R4gE: ds|/>Gy_sP{ю1WZqI[^rE|r2;kAq"}? B2|[?u:!])r-}֚v\7wEpn?pP 9P*lk: {KFT9ӵ(],2TۇfȎƶPf/3'9J2LϢes~[(?WOtsKRύo/PGtTes~[(?Q!$(zߖ ?-{HwA.J*^>7?C|nrKRύo/PGtTes~[(?Q!$(zߖ ?-{HwA.J*^>7?C|nrKRύo/PGtTYzZע%651k,`izeNDp^>oRwgJMoQEǴQEr^6[HbY7qcιy]6.-qlxxwϕg/_K'XY2bBamGrg@72=jxu  s! V5eN<Ԣk!eb5tk|*}P~fTwcEdxEQ)eh֫,n>GJ燬l%.Xއz'20W @o}_MrapsF1Ҳ4jڇIm?j2sr;YEGJ!$$TI倉tnP *q@((((((((((((((((((((((((((((((((((((((((((((((((o[hh"A$ØSX3ӝ#lQX'ZђvD8fD(dsz3N#V2&hv, *2sX@@VHxy<ǽ[sp6 Fse8B=EEqX-n 4:\-1<~c $Eb,M\Itα[Ce,$FH㸫:m2]I cǜUPG4Ebxi}m#. *gE⣖ܿ)ҥuu l# ^2q`8# Z+KdLwD *%-)W\2VR;/n1V 9!A'PW=/t /Y>qԥ((߾C8<=kn ;Φ ٙrqPhj( (oiWZam+8 tU Z+M-BIk0FfU$+FO+;P񆛦XwIps@Y*TgC@FV ^Md2sląsE^_Gwz/;b{SA2[:D3@QEQEQYk:Ot'.aI#yGP3ķQlc2HapyPkIjw-ZPY?ՀA@ߧzgkѴ6a!y1¯H3Si?ٺm+Ku:nO&Tqo@[:z=ƨt8#/\Hv9BBxG5n֖ŕϘZOJpmsy|xuC.мV" `$n [^$W:mM$Nu I6l'-ҦTt~/tV%5{YcB0˸u5 v4Ț2iB&ߟ9^֥-ɪKjXHK?#FT1^uog-؝p] .nINP@=mwطD=rXk[?~O9>_N+[ (Q@1IZ(1E-R@ E-J)h%b(*obU#>),qڢ+(Om7?Z¹[BIdU[KvOKo[TqVlqǿҾNo I_WUS}KqE?@-)5Ato 3s?.E?PJ!}O\YxV=6Ji_c]h_ *%I/V6E6ȷw:|lǗ`#8=⺒ 4aj%եi۸_)B1@ӵyƕfmpK.{_Mu:i|sH'-^LFc@89'jSѭuGOFOrVgGݸCgs޳<]IM2UT]J1pN1]ŴVmqs@+BQmmognG0āUG 5%ޗ>\ۈKmQr^>zaXiwipү_G,|gJL,2]@<<Eyck]h5{ ,ٷrWp8%s ִ'IM2xWR5AHpKySp=A:}1KEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWM縏@m?Sk 5ս)?ꔢgQ@՝kX\pOC?yYppx8_0M7K!)HEwj멋 i#ȑ@-OzfMSējw:>C,}pr'9&H|[y{kke=C*`믢8Yl/ZI4Qgsy"U޹9њOiZ ^;>[a(3_&LX84y4X&hV}I?&Cp9[I՞m:ڢ%UNGK߁^Es7I/Ѹ)p8~?bC2K.@$9\O|AB3 {5;BDBA 1I5ZPMekV=GP$A\/S=)s+fwuکRЂ̤F8,r{ָ^DӯW,2(ܫuǵQ6gR+q$#e<[ vy9YE)(4;P`d\M./tk mmlmC*^am, S+8yL"Kz\pp80)5{Jbyw7mzSlvyׄ4FY9!ԣ(c,#qs)Ut}:;٬5b"vg{.v.⸈K $g8t`A{V%Dv9X\8 |%u lZ]:Y|H8 :WKPRLBQZ((((PEPF( )h;%_?UÎ}eUj7 (8,~)U@}Gkx4+˵_QKп[f Lo =Ɯ@քsj|=Vu{sjZ) 3Mj_FJ2zQE6?IoM4H.ePGp6 ) ={Wh&{QX4[SPHMTY, v|v~-}N]'VyHt{Kwf>mULhz}u Nxrp9\Az%X\CqN,lr9|bi^A eC4]Sjr>U#2r\͡IP1Ut.1px^  [kH4y(9M9r;˒00hԨWWWA8O-B&736̣rMsoN^jw!|9σU8ڊşj=gy6JDam| |-4k)숳dI&pltttQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ\#KmF;+xڍ9?(*>sUm'孼fişYO**Oy! #F™]/]|qOΖPp͵\@u4WGE֭S%>q`oFkәLfbС$p(Q@Q@'Ǘ}NIDNH*T:bOk,`.-zn$߶b㲒Qr(js7nuoXGpٮ>] >g9<UjZ=\Ώ}+aqrq:+ήn<u).ޥwӴmaFrN߅.?5; $lʧpg߹TȠ((((((((((((( RTp(~%o^eEފG[:W`QoY3'N9Ϧ+FL7,MN9~u(^`x~߃*o`{g }Xao.7Η𮶳u>mSI{XC,N $秲Oa:]Cj_aRcGѽUL `n sڴۧ:цQi ,8 dd o S!XT>RcL3g'>.[߷\I I(93<:qJ̫g[A #vC%\Ԓ-ޕCnu(%79e(/Sptn{3ڼla8$㌁"9;og$:F4vsׂrSieԍu{߯6,ۢ %XAgdp4 k>E/LR>] >qmZ\/u MT6A&~$X]Gaqo}Xqۃ\ x+0L%;Jo3tUGI+嵕_ŧ-?}>Sߓ$VEHd~gһBΥOO)UHr3"T7"Z݋RHNXA[HQWN{h9]WšX'+%;cB+sZӿ4;7~nwlܤgW2 8p PA^o[[*k;k/'$2,!|+g=3mm Dwy7Z4ff-\:<;iz[Ct丞DS$N7c8Ec\r o_%SI$W (/20=*!cAu{M4WK 28sЃw>[kX٤xy9A r–wdF /SrpŰI9n@EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVq0c h&z zg8(_ Yz.dGbc_lo.~Ju cy p9Z(7fڧ|ˍarm}xgy СR\ȅ]F'$jQ@sxf9꺰/&F93&O<~eSn%)nmm緷*MH TEfMG(-;<ʈJtA'<)f);ř7 vr6`q[P=3MҬo#ؖf'ԒMKyj7DzP-DžˈdSI2799a%𵌆ķ)4ןnYxe#tڸrkn˶m{m3]80U)aou-A<'ec?3E`%a)7ޙ17Rmӵ7R\Hd`P0cs[PkMRk^C%VevAFF3Vp%c@%QEQE<4^WSv!GOԚId\եo1ex\1oCҺ:(6ȁQ-f9pszFpFjޓmwg|ie2M@P@5z^7sCpC ,Du AA8zns5\^J\ؤ( I<EQEQEQEQEQEQEQEQEQEQEQEQEQEM?ZI@U+F[_ #$^Hn(-QO_O_KEpGR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@y?~y?~-GR@ E1ӨVb䎢 @1 USZp5 P1m,}kAFiq[]5Ev6py9zZR?FWMhp?QrjZVrӼ@7Vp[&3g>_޺MTVyXWڭgx.X*QjjQEQ\PQK((\RbQ@Q@Q@%-% (^ҿ?U[QEbjQEplƗ d kfݓe+Ѵ(`A0U^~pǛR-/Ԡ$y#֧PIG28j:<6x+왡bDO4xLxc,T\*5i3ӕ4i{Ȭ{nc+ɮ>0 [c?WeazыJ+w=ZͰS2jn@ gj)c~]k^-7=\5Q 6F-X-7'nU9$IO}RJmb&09#rY_mCčjb]ZߙR݇D9Al9K+mOFIu>DBa?h!ʙYv Azvu&4=UQu+ƾFÂOc'u8u0kp\k@'1y#ڎ=1ZUŨ+ LDl%#یtl:5=ZRѵo*%9tSی1`3@6CH$E\*yM&]iWcsr9#5i4ч/uX6Vqd f~@!FhШwR^Ŧ]gpyg2f eLP ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ռEoGcNybHNv\ou7=M'P׷V2̦<R~fOaPljmd$Q$c^dӇ톩h!"JV 7ugzdV-D"EmO]UoƝAkVћNӰ)#PO9'tV-mò؀Ilj`p  *N:W/ckZn.-c,RK4LۘFぎ*L_@$fYf%2Huc.om7,M\Itα[Ce,$FH㸫:m2]I cǜUPG5ĮszK:>Yppx8_0M7K!)HEwjԴ5^G\@T΋G-~RK5F&Fdp=0pGq\晬ɪxmNGeOXN@Q@$՝ o/mmu짶H}E[x~Uc/l@zck7lU h[/"b2#ݜ1K/_^oW(;oq$`Gpɝs@$fl/ZI4Qgsy"U޹9њOiZ ^;>[a(3_&LX8U5{yZTR]׻PU+G=1@7Ekg '?;MM&#=Ig,OɐwG=k/Vug}6e0If⺕A.Rwm'${[ ӨxX ]Q@?6ps0\O9 7X̲R\0ڪ^a$zϙ4d[Vt*GC\eȀa_m=VѰA QGΊdpp#tF V/-ylYJᕕ؆I{u䡌pDҰQ 8|!ouk`dQ^A-OCWuúQ# PUMSR4F谂6zmQi͞ev${lɢk{p18ċ>FiZ5:c#S%v;3@[1\f=VSlkzZxљI*JQCvMݭm"'ɾ4=PUƯqƲ`96bB@9~Bzt"Wf'$I I=4}#q6Z}Ɠs=v}6a[lxH< Lc+.P]u@7U,@~*h&[xPBȁ=pFkI"m6wtz88N q@NVM2J€0A(Q@Q@k:Ot'.aI#yGP3ķQlc2HapyV:k y~y"ZyeGcۂ1Ak Z敫̐<6vȁij(RUx@hg])VYeu;h;[l4ߊuEe ofH07#0$nqքm'^-ʼgJ'^iskZ]F[|ir>\ghkYAcs{%4*F`@cNsZZffU= qZygi4SsI)q@hP3EfJ(hQ@PiQEQE 5{Jp1J[ nlQEQEl_5pxX}:,Ru{劳g=$ş`.$)!$$xPA8s_𪬿'q|-}=cg⥈J[KNծ4ۏ6)Q"~VE5U5Y(((((((((((((((((((((((((((((*~:T_F_ #^̭񳪢+;B->9l^%k!&x$X^Fz|J rIo!fXԳIrN<M9+Oyeqy;3鷶;WQ^K|*0Dc\G#Gгrv׾;dO?۝,I&H3-ʲ)V A8^M ڲOh!I#Gvq W5NA{繞|c֐̫2 .KBQウUBQ[z-׍e qZ>4gҒ+Utb0@wIB62X=Ei.m+8າw\) &>o5p m0IP%:}w˻;H^Z\J֭v_b989n?as+7(9^WsɭvÒj7;[-"*#A=Tb54uQ@((((ERQ@(4QL?*_?R5V-ҫV&EP~ƍ*UY?ϬЪ~9}XeSO)T%< naZyB+6H% Y]C+R29W\dxcNqks kBx9Ɍ,VXy{>JzuW{Et4QEDns<4hZmԡ{-~ Hn6(U2#m2O&Ԉx}@A \>kYj:Ο/na^Eq&v|}zFv>ϭji6~I. zp2JT4[Դ[;"FS#QEQEQEQEQEQEaE%ZJ())f)3@4PWoB)Ka͆U}ՑQE@}Gkx4+˵[lwDAb|3^mmM}F\ZOs⪮~/'*ίntBK]!rZ+嵕_ŧ-?d%'UA;=>RsN~G?ɢƉ̶jhfe jT7"Z݋RHNXA_>ly֢i zks6A6:LYJZN#|Ы=XwU𦩩7V/Ihmo1ЊִM|ۼ>f7)FzkvzEޟIoV6ySz~u<.$ضviBxY~M4Tڡ6H̜3q]=']>?\J:Tvp^OqIldYB V%U{PmZLyqp V<8k_}hhpMZEȋDnf ː9~\^kmX#ɾbѣ31mUFp:֙6K%"$$g$q v|^>zL!y2ctZ5"l,~ta3avs$TQl[Y:ƤҲ0 { Ϲ E5nV|,dky O =ɨ2 kMrXKc+J/AlH{sJ!cn 0EI,lo l댎TklekH~<#9<,rGr\#0N>s鍺((((((((((((((((((((((((((((((((((((((((((((((+sI}mG|BcoQ'Gyݐ'Vq0c h&z zg8 [XE񾙨Cm92b"ᗣ8^Y/o¤SHi~Wy?~y?~tǬ]TO_O_|QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/G@QR?~y?~/.쿏תME/ eК( u_-&h.ҘV_Ǩٵ +@de`9 csZZC5܀F\o ևaKm nAwua*NǾTvfXYGYN3Agfb $2,%6,12.CFĎggxcO5nD L`v$EtZ -G2LHtsI'ՆI9!EzHhV-֝,\2*Pdlڮqvc[u m}ʲ9s׶*xcOi&y˹"63r8hֺ2\syҴ6_8ŽESf(ZJ(E&h!sIEJ(f))(iYScvv?=j^7-:_h{ޢ+#@(ŲCɸӌu}{}!tdk.@@=iؓ:YW'=@3[Q\'WO$+1 3*6@,"pcR\Z(ܥhHVPS 0POEqW;Mj:,ۼ՗OYxMPoeŬSn.(fbrN$PpsgoCuiQ-ͤK)2pŀz<cJU-cBUFca)~rskغ9@ػyQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEc"izDI#1§<$g;rGzآnQEQU5MJHҮ hW2pa@Wg[[ԅf` ƌ̪IRWП|Vv 7LcH'PT#N: *:ƛewى 9 Њ@WRK +X^wTqUR ⦂eu,f$((!-.t"'N]:GjO #kyk;Dgnd 8 z+ ])VYeu;h;[l4ߊuEe ofH07#0$nqEs>.+[!KDhU1<1f ]ҵIn-siwmorCgiIRBzӢ(((((((((((((v/S@ eК9n5Z0:N?BX($RܛHۢ\@U N[!k =LI$Zc9%$ !y_["YYWS$.5Mnnt1"\F+2I$ ZV(xTb6Q$VV  ˭[KSjw˖ IdbY}WsZSV&ݒKG%R*nC9+xh7–֎7Fvuۭ3L}km%-m& v^Ǹ'ٛMR3bᝀ矜Wַ{l"PD?5T\I>WOggf K u#"*TG-ol(v+o@zt9օat=żrp72p(BQ@Q@ IEQERRPFi(3I@ J)(Rf IE%1h1Yա3'-(4 (]kOOEr?.簋@ fMPm6 E,q˭uOE8##{h<ėvMRWZ%y Icr9UQ} -Ο71OA8`CA -!Zcuiqwn/%6!ym->^qYmau5:˩]yΨa* c$ײSQlX)$2NIkygQѤA$&n+hRiL#URkm|\8$?62pGSWmqmճ\A:x@Ѓ[[[ۥ $q UQ8S#u4va+ 3>ݪIUI]CéQ%Kaۛbd'&q']<u,"-<[2{ᦺYJHsOpj *^F<|n`9ltUt[so8D%V? gZuƺ^Pyٞ (b2d'lֺo;U1q {kv d'[O`4cznbdM @|GK Q[$c*]VoapsKBoo1mBKywG:3F1𮩔:a#Q C*GEQp8tɗQОHԢobms73-dvjCocyeEk"!),x`83J(mlm6xTmUV;1>2/F;`!@F1[TF~MS灱 v;A=%QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWM縏@m?Sk 5ս)?ꔢgQ@՝kX\pOC?yYppx8_0M7K!)HEwj멋 i#ȑ@-OzfMSējw:>C,}pr'9&H|[y{kke=C*`믢8Yl/ZI4Qgsy"U޹9њOiZ ^;>[a(3_&LX84y4X&hV}I?&Cp9[I՞m:ڢ%UNGK߁^Es7I/?"[IŴ׋qrҤm"쿼 ׸3\ŖiEmF#,a騠7? M糿Nkyb|o?(=k–MidDC[ '6q޺((((((((((((((v/S@uHhUW<7QEvE&iiQ\UX[%ni ex=kw2.i,eK  ǖdqEixc13)Pʍ gӓ=3!%I@vq?Q70a+VedӔV'.wu3yy5ŬJ4D=,I O^zu*XD"4(۷=)c8bHEHB(P:; ([EFXMSxC>9By'v4Rơ1 7=pX}zQp4h(4Z3IE (4RQQ@&h撌P!sIFi)f_?bҫVf*O YRI"LrGW5 wMi1J!;U}O&v/+UFT[~~?UeA;j[olW $F&<O]Vm2$K1rA ]V drҤpu[EU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh UN?eXW@ֈHfŲաx+F.ȹE%rYeǔQeˍ8',8cRzҢ9|fenHw݌fzFHy4Ҿ⻾yYv :(H0i|dQU=/ i^'T,ɳ]5Mxi zks6A6:LYJZN#|Ы=Xw#un &s⹶[.*Kʜ>XsSt#ŰӵJi q|Fd c)巛CV>)b\c>#=s+:Z5G<֑Zh$Q(rrw$``Q\YNQs,K&H6(~r`@9 hji?l0 0c;AI9c(^|7gok&`.fi6a7mb ,Np=H(Vu]#O{-(B͸0yQ\$ q4vӏ&dh$~mJ`sW ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (_oa%y RmF9v@:QEp&}3P+tr"dE/F-6 qzxL[}Ni%[1Vh\1@2{uTW+XP+[O(fP~|Q585[*MB8x%fCJ>QoI5\Mily&U * OPS7wAy.4'm?RIp G~-cSү-,"h[΃ɞ;=ars_Kzl_ڋq_Z}$/Roz;o|9o4ӼZE̟`QխvGڜ;$i++R:֑نiu Lw`G0:|XndL]yo*Ӄd IEr3I4BʊܬN< I#Gľ% m/}2$e`yH?C֠L4-20{,ed#n  ׮5;-B+˘$pH`6t@ qZZڣaL.ڮxK8KaAqoG8`fڌ@G rր:+u#VvˋK{gf807#5ϦZ31fhPNI8f((`>rɧ$'|lda^*1't0jw7o1eIP9Q\40/{"g%bV=9 fue1bdmaduW]^x R]_J-hߪ4i+ ]\jjv35H-u<ٕNϿseIq @UQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@C֫쿏/4he4T^*ǫF;y#Z=WQ.k_+4kҲrLx#*+g5CSZop\ZDA\Fc9;[])n:4W7[Vh&$`\($@+Kf4бD  ?m}Ryv-+y>'Mh2 ..{]#; I asސKwֱxIjw -G8='QB֖i4~Z$JŬ7̣',sm&kR GpT? ʰ𶥧_"[ΗU}ȩ0FH>I q䷁L,l yQUVybEv?qD~H!iCAςsCBi ٭щ$tVTd2sa݇/&ART`4K&sNPIq#F@xQn@z5IkqytY  C[B„z TPEPEP zM_kSL~Va$0F9i@j$C?>byƽpa]va בiJkv9ܨܜ3c> ;F7,o8E-Gɝq3P3PQH4QJC4Q(%Rf3@%Hh%f4SZ?r?VMj'r?PGGTTEQTP^O_O_r6[HbY7qcιy^.jj|ֹs8Ѩǫ?~y?~1ꗰO]J#dez.ìv"=18 Џ50@К3$Z{m ŠMytRf ZxoS i2=0yj|-R{; &&pDLwy8f/#Z3>'6{Afi 4ha1.=0YjڔzmW?Xzu]7\B%z*o_*0O8N{R1;]Ju,`M 0+T s 4?^=z5yۘ7B4aaԜ֡c[^| Ad;P3:R̟:;! *y OBAZ>iG*Zd`@@¢;u]e}i&ٜܠ=պ-Rh I( )(J(4f J3I@HiRPi-%VO$G(((~!tdk.@@=XTj+T-g*i_$;XoYK=W]}(`(~tW#{ `dѼo0Rƽϴ[ o{-fojm̍[*[nO*xs]F§oh<>uGv<^sK;o ǦީM+s rsT$9e*~(O͘g'Rm kmFSZC+W$TbRzk_Emj%եi۸_)B1@ӵyƕfmpK.{_Mu:i|sH'-@o}_MrapsF1Ҳ4jڇIm?j2sr;Ԗ6Vڞo>:}4$~C2:>r<M:izWy|gNqXIk{8&3%rsI@5\TZj4V k ;0|e֙nP$1ϥyVI- ˢa}RoGD1",W*h45&OLL+AvpyI^+}H$8s*sFzs$w|c ma:e1RS7B{ "6.bt&O5.`2y<("o'nnFN~̧G<-<t\n*9l=*]7^Pɭ/-.R100' 1;4fMSējw:>C,}pr'9&H|[y{kke=C*`YKdLwDR #|ɠ7"޲@CYTr:.D FBiⶍͪ@R>tS&3$P{0j ymbU U0 *Kۨ,./% c&HPIǿ {_CO$*orzG${IUQFK3'6Epǭmpgy4~T3"?_Q3 yuH춋gk+yf1,Q۱}$oCW.4qWr魯ufEGWQ 9zTWi}'nspV)tE:2D)1-3NM?`Mn+}FéAxb=j]j7EѴdָ m7RLl+#d^M[ۆىĎWv$\q}7JѮOQ2.ܑr2:4Wg[[ԅf` ƌ̪IRWП|Vv 7LcH'PT#N<=i_= ),KIDd 8GhW>m#iġ>M'Nr:uX.5{65 sӡzM5<-y%ݵM7RI`݉:4#my;"cA8_cu|_Gwz/;b{SA2[:D3^wMnѴ[m?ŴLv0`0p䓌8wriU@h(̇\\|9wI/s==?6؍%g QF S_XS S"ϛ.;68= XmO_կ4^dᳶO6D%Brzs@:N.DDP{aV'UZ+(xfS{2@#w=s^'Ki8n.U?TP:xKX"7(e;L6z=@\ حn`-T3ep3gwJ֢%5ͥݶ5ʨu %H8= k? M糿Nkyb|o?(=k–MidDC[ '6qހ:( ( ( ( ( ( ( ( ( ( ( ( ( o?ήU;t`/ tv|Q\nIX^A>T9.5U;˿.h $6LS/mrGNĩX [t=ĭi;: 6xRkbwAb@} tT wqR54_E%Xf<o--,lZI"^D2rC6@cڽI42!ܠ XIDv lp2{|)oZ&% 2"(,lHN~l q~k{eyᶆ]9 `gqzvm[_b<$3']^# kxa|-#q}ַ꽭>MPEvDW'RQ@ E%- (%!IFi(%QI@J`!%.kW+";! ?b#*J (SnƸ6GqtSYA*r2:^WȳꐅCPih `5M$I?> g9uH(K{X#H@KEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE2hbH'%E*V (ƒ<+ɍd@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@SW*~:6s$$x%ŹYzc {laX 1(0ͻ$ $c|7V6wV<,W#qۏN:z)(0P)(JbJCL&i))(9!?b_ß({:vU}%Q@4Wۭdgm>BcO5?گoR1jU;%GsdI 4c޺5eu H ^C)q{E9ŭ́ &3ԱYbQ梵<j+wQdQ^)R9m°;[~tK,pD" pz@Iu6)c&D)2:0*#LXH㌓zhHDgU(HLh e3RX4eE,GRN-RPpX%b)(b=8?,q:ڻ7A@ӭ he@ ⳣg&"SQo#%&6(K,pD"pԩ"H GV9QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE4?pj8dC!RP]hvm!†ǘwɅ> `U~37:2OEt\Fi3Edl.ii:NJ]6Hnb 4gO={sֵH-q+], <O!:V;s Z 3-9c|nFF\!^nlRnWhRY%m$"aےGBaB~R@is\?k\͙Hg5[H'!g w&ewN rrc'*}' }=pA㟛#Biݢ;=RN-wZ,謇%^6Ѹ,dž#'r?V5lxkBR 7?JV[V(( >t<}pMrWح߉ m;?Z >/u MT6A&.L-'ݿd9UWdC̓~gW:v%9PXZʃdoz k՝wSt9}VC#TloMԧUclp23>tUMR-NxR=2 WfĒ2~}k-HWi>"{nupܘ +  Դiha˶OJAm s}.uӤ/l|A5Yw^ǽ ó8,y4-Zu{wLFݘ Hp:5?49`;3JJ5*[[:W3$휻!xį5 v8m&a=գ4wKQK9}3DK=-YYV+I7f8~R{ekŒY{ ,6̲wEkO5,v2²4HoO޺ڢu!@(UʅpEKj7Wz_P\[[tL dyGtxF0Nz]?ͮIZ촆+f(Y|\ cqdh {MWO #a:}:c =FV`[!PrV,ڔpGhi+[010$8z/"M;=;|Q?iQ bÈ?BS&ͥ:9!03lEO[WQcR#~R^lH%>xxJ}j+Gu-4QY6Hg9<>U;J\E 3 %S:@.fOZadMX<FB$r  4yZeh!{nm1hyrlAp>͇mLMۧip~bI$ `NyWφm{K1|76-AbI;'NNflvZGiiX3lcvVKf_]d,Q*0:t|(g[[yst ԊFne_&&IzӴ cO$뚬Wy%I$H11#sT/?onWV.yɋi|(@j!˖MFi?t/'tCHu٥JmwP~A5M;Qk'XVXEL^XBTs<Њ.k.i+5H>ܓr6 ~4x7ri-Tk7}|nfa:{Lgm=[[[OD zh\x[LE1I/ل#| p񃑎9_ Xn\KrMy]Um' m}FKp8;\9ʌc^–QH4Jh~vYF3MI<@fişYO*M[f\7d@$dc=ElA V4 1@QEQE'Ǘ}NIDNH*T:bOk,`.-zn$߶b㲒Qr* zM_kSL~Va$UPI>es4NA-d'( 9g9=sZa`_]nhp@fMRS};+n!doբ$$֖ť-'(h}T~lC@ JOc[1MV$dQ?~QR?~y?~,.#Icz,W8@~@|<~O_O_{LXzJ%y85꺜ֹ}>Sߓ$VEHd~gҽkk˥5,=g"E_@:(+tkk|JQj ;Yddq- sR1G,HK1@/9; \[Y$/dՀSPPJo1`Ak'y$1OK+n$Uk[{8D6A9 Q φ%'q?V) JO{ nuQEAaEP%ke)Z5qm1m5|Cm\ zy駇\r̕~HяTxU#,O+t-vf۴w(?yQ^P$y#֧PIG28`c^6Z>]b=jȬ{gT%#Q&\J+[[OT1B-o[RФxUdѢmo}lrl ` @eZ(3GO5L`{.OBյ(}"R]ᵅu(HE67F3Ө(((((((((((((((((((((((((((((((((((((((((((((((((ռEoGcNybHNvEbxQkFIHLlV9NH8xjXk{V($Eb pMlQY#V-mò؀Ilj`p  *N:Ps6OHmPSm1WbXHi $QI=~l@TV-no>%-SA튇L-tCbhki%@s #^8 Ecj^&.巖+|RC䶌 =>V铀N1W/H4]-f0O=8 W?s(,nobFS` pIqU+ZT6vL*6v ' :( ( ( ( ( ( ( ( ( ( ( ( ( (<vLmsJ)>7k`ђQV!NK@ť2 k.(>OcB9Ye$ۥ1o?դ>aplFy@.HEPpx+]a!2+l;PXdpEsWoķuȍ>ʻ' Kg}Gyo0j+4lobAni {6 ~Us+xìSSP"3`''i] Q4gwUTdT\)mx.m5+uAK E+ШjĖv/8dU(TdJ}çQ*}uM3gӊIZIkt/s 3HȒ2@E]FL6w ԡy€:kk]B-c3b1UҬ]9&hgy~((ޫc­9헃͘M/)| OsLECA ڕ}aS }N:Rxd)yus|d_% Gsb+rrIJȍn7nVe@Ufu \OZĭ NO(Š(>fݓe+Ѵ(`A0U^~)>ٴdk\4h6uX@f{+T>_(,UU7~Q䱷]Dd "2cctZMbXm'd^]sU]SUI6=۶@Wt(UW6'OF{_SY!0qx"8U5tSUԫTGW=ϴ[;X>4є p~o !V'lW5\Zټo%GO0vZ}o{ K{Xs..}p*ihH]AV q×S[^GjۼK[؀ʌF<,?5ލ^\\ُ,%e}XzWPzm:KcEm##"L'uZ͓ˮiw3@t቞8@qg~IAQ@&aq~-i29Ӣd8{($Õ'H~+((((((((((((((((((((((((((((((((((((((((((((((((+G6YǟJQO 3܊쨠Ȭ.[Z 'Dl!e<141;;TO61I`Sf F rO <ѵVWwv^Mwvr)%&md#q@/kweikZO3,\^$C:Qmbһ $XSeƻqyIaqjҰiʑ#`uf[:,wRhM/O\I )$dzlbH$h&7P cހ8?Vmcލ6XK(~gfw>I>.LҖ^A뜵u ~,JQszi$2g 6n{B2<1nq휳2I=;rMN(GUd(pkB mVZgc,Yi򣑷cHކ\iSy[_3]A6>̊ @2s+Ш"-N*&-:SFteCScZf}~zW#Sڃz%n6WbGȼ& ѳHQj/to&]æ>;I`e2]c#< euk.v c"\N@!Ps;Tzuyn6y \JKzd+ۤZE%؅XL#OWn0r8>cAm6#iErLxH,_k:w|Od nSXLqF2*,88WWM}d/r/`Ye+lٮ}OO{;mV)VJ @s޶)hk:֐)OD>y`2~h]{뮢 ( ( ( ( ( ( ( ( ( ( ( ( ( (<`ύm?2J) gC )(QYj-%B-=quj%#xU%| ̏]"/㷽yD,,lcd2y\HgKEWln.&⍝c^@r]%:eqۗޠ]'mWZ1Ͻ.{x/m,I{Է1 0x#Revʌ O*t,kUIN:KKo"?7cՁk;h,-vIx$L[PNҧׯ_ZӮCn. 9zziB<5\tiE_y]G UV_[pM}q4jrc]Uef"I$ѫ+e`FA }=*I WZqNޥUZ+=BVEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEU-QUh TUZ(VEG?Z<#Hό?9(AgdI(D=ފJ)--% }n X@Xx?JIErMGsmE=Ͷx| s-{Q[j6Crm, :9?6J5H\,o"ȑTw]Č.^B%oA8׭oQ@ +R7iaj&hKy"E%29G85F? H[rM%U&O,sZ3K@b \-G+qH9I zTTͬI qjm#Kl 9lI>讆M3V:ֆ 9pۦ~'<=:X5ZFC`RrӞ+J{kȰ=1dgOǃK7MOj-flm/ҹ,%e[c9ںJ IcG絆w)m~;27c=㚀x~NEq/ y % ’2KqX^E孽SNVq rov= ^3i(t<}pMrWد%h_S3&mty\dxcNqks kBx9Ɍrd:ӵ -wʃkU^}V{Xt={ȢtVf˥񵲣\Eo8$w軛 |qY#vuGVd8`Jgӂ)%8!y#gw`@I=qSh^)#Dx`!kCxCYou{my`FExZ9Q7~^\W+Bkr=C=Ko2XT*8BC/@CQ ԴqyVyQG@OOOwwiښEƢj5]mcc'v;L l,-5[xHMKy~e@A zXXZ};GIDfpI{A i}KR2ρG3p*Q\xQc4fIB-Bσ$s5Ɲyme6~yl$A r3(((((((((((((((((((((((((((((((((((((((((((((((+sI}mG|BcoQ'Gyݐ'Q\=IjxL!1pыyyb^(Skd)eil0 P@U7<o,z;{$S<{Y9}j N.' Vms UV̥9>^kxH;&v#y,a2U;j"YӼ7m \-[Y7X@$|ꨮNasY/om[ ogE%ś/pB6 @;+*^ZFaZ,Q%6dGEref$'I#yH$2rI d@|/w=ޓ"mwqkWXdR}}ֻ#NXDpAhB{TkHPoW;1,HsF=FG?j[OqvvGȁrqC8K-3ˋwCdjry0#Dv,72YsǦ^mK7͂ɏ[i2hkoeqgEjVp' $ Z_xYuHl[T_tqvWkPxMKH=22n|ugzkzqY8rZ$h0_Ay:q{Q\MkfH-mQi~d Wn<_%Ss 0a3mFW dg#9@M:ukm;eťwFveqtg-4(I'$ EPEPEs|^>zL!y2cty`m'ʕ51>ڝ'_DJgXf.o1ۥA R0%8eG<rW֡ikw6&MB `(+׵Uu{kk9_ˎ\H!9v}\i4)xaqR:t5}gYYcb89M[=VpڕM# Bo7u%q##N*RWދɚѵ2oNX{d?rУI"6@ dqZzMŬfWEEG]1 M:{=.koڻ>rspzmcTk LM+cnq[xv/oE/>R5# svqRjمU}=<MJ{r;P:^\ b_JekQ+;~mǾ{m&KI|i JAwɀsWGkKKgxmؽf(IʌgN2N3^Nhga,N?3c4&ZO?*W~,4wtld`#'#z]jln2w(Fc6iu Zj-w@ Aqh+KN[B2@3\*[q,FzwVb츺^3l/r6!V_FAẺi4$`d8q⢸u Rш%H߻V  ETf5ݲ5oݟ*a\q/<=}Mej60ZG!c_| T)חrAʱ ^dN/ar21ޯޥΊP}aS eV[$rGmh?5iikm N|NȪ?J[~KX-!IЖ-εUjp*AaEP!ͧ'TD':E1 f~Q8֏ċ .-XޯK1;pk0ߐ}fiZ|:N*q??t1c:ޫ~%B~`#Z/o¤SHi~WthBԴI*W[Z{_PAq,)$Şbs?j*)s\yZ#-MH vtT=RrU|$Z}̖['Iڣ&rIǜq랢C2mOZI W l(` lq׌d>N]cU.$G,I k0't;FC扣@A{w̖`f;nvtnF1@>sI $?2 6p8#J/XiP]^5M eb2A k0υl;inmi;nd$Cn<2..o$C;H T܇1lNs:q[P8g>QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEx\jz:ƥk41H"F+zŗs^&`C͝J$LvW%WEsZoM3y.ӤFkzbJmuWk$ !\ǂI6)}v-w+ֵGtmL"a"mۆ A ."Қ5- w u*b{TP\uo4sG7FGQXm.?$4JT1ҳ}72 ŨQMޝ1hDIQeuԬX;Iە#%^:\{1j#¾868M,ɧgدmq1Hz 4UBhgRx㿊8[o&g42>]y$12.TîkJTAy-D*JZUF2O֖[^u$́H0{z=3Cy^@n.a5,y'j'$!7r`^M"rb2=+?Uuss-c絸++ȪW*3"0'qyō'S[FI UY#]>f`##TK,mb,NUr28~-on8,noHcC⨦:^6bC3ǀPe<FRtL:6BِG/㧵QO[쑮,-Ymd\Ȋ2vg#\}Z]=I /sLnh.y$Sd(ۓwjō#\3FrHgP]k{BѴ{G9iluk]F=Heq-˦OQMem$p"GH@:m91RPn@z{Vt^ D8KEێy'-Esclab˻Z䞜~4}6N& @bH9xs}jcknL3z5_ٻ_<vۏjB o~!vvUE ,UXrߓ^i/@J9*Hic9{.m [0?.af: 'Ϝ`y뻎zj[ٲ~2sVXRpNjKI$ǖb=W{>m6{KKkk/9.@!\6L~4(e.uo*dv2͎xJFGJ6o tk 둌CQDAo*$ 8W)CHii(.s3M>e,r~okog*+JO0mg EfH8LAhi. QniVX̑`ن7qe95R-9ڄ7]\!.2'n~44 !ǧu[qz#l':o'v-%va;߲5$*La;qEdxb8Ii=D$38'y$['wg=^ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ռEoGcNybHNv\ou7=M'P׷V2̦<R~fOaPljmd$Q$c^dӇ톩h!"JV 7ugzdV-D"EmO]UoƝAkVћNӰ)#PO9'tV-mò؀Ilj`p  *N:W/ckZn.-c,RK4LۘFぎ*L_@$fYf%2Huc.om7,M\Itα[Ce,$FH㸫:m2]I cǜUPG5ĮszK:>Yppx8_0M7K!)HEwjԴ5^G\@T΋G-~RK5F&Fdp=0pGq\晬ɪxmNGeOXN@Q@$՝ o/mmu짶H}E[x~Uc/l@zck7lU h[/"b2#ݜ1K/_^oW(;oq$`Gpɝs@$fl/ZI4Qgsy"U޹9њOiZ ^;>[a(3_&LX8U5{yZTR]׻PU+G=1@7Ekg '?;MM&#=Ig,OɐwG=k/Vug}6e0If⺕A.Rwm'${[ ӨxX ]Q@?6ps0\O9 7X̲R\0ڪ^a$zϙ4d[Vt*GC\eȀa_m=VѰA QGΊdpp#tF V/-ylYJᕕ؆I{u䡌pDҰQ 8|!ouk`dQ^A-OCWuúQ# PUMSR4F谂6zmQi͞ev${lɢk{p18ċ>FiZ5:c#S%v;3@[1\f=VSlkzZxљI*JQϾTVZՐiKq<GSYVӴmsP"u2ƒdqڤ@R6rFDQ.$ 7' g@e][EqT:2*[KZo&0-bc d8$ץChĺs]${Y2*(Ҩ05{ -F[W[e-` mpzGNkZ$Z^A,{йr9ϗq~=̾|yd vgՊŲIm u SxDBɜdcfwm1Ie"'ĮWk0[O[זدW VMT ^1Shz7VX˧  `y MwħAYeEm@qn"(=+~*֭V<3) D܌9k/Zsr%[Mx**F(~{SsP|kiÓi%/ƨOة8̈́2J*Һ"RIQLbIKH e־$fZjRR9=Vĉbs(msT٢$T ѷ{MwOI}KK  ^ټ:yqO4ffc *| @[TZ\3h/4f$r=]Vhv?$2]\]d:UkVCp6ˆ=@O {qqZO= qg<7&N.FNӌU9{klnO0ȭ:WQJp%>Vۼ`dVzԺ{>ZT ;P}Et,md`U A6pDs\ `8$7ϡV662{4-[)$(z6lyEA׭$3귢WEG$6ٴdkэ 4܉shR?>k5wx"J:usm?#?+a+ʴ#궜|p[5ŚZFT|8tyo'vNgatS(>%]ĎaAiz|+q-β VFKA>n΃@ѭfk}&fexZ@z rO.a:DXHKF%!$;G yɫTPlRʍ9B4&ISpmA<f41\DMIp?Pi̪ AKEG4(YEp>,MH.kz(ᑦ9 P#go o!_hMʠTPwhDi*=2}j >Lqypy6'v.$bEVtQmgwn~gbrNyf'4Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ ` IaJn=E>(,1"Fc{8=PEPEPEPEPEPEPEPEPEPEPEPk >Lqypy6'v.$bEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE? ;#IE7Gl>>=(LwtVo,-ly\할]ݎ?V` ѧ(7 y XAp;s3쒽|e,fH& P13^kvP$[Dq0Ǭv:՗bO8cb6u%p zmo[.nU4搼/&f,B8׎M2σukD42Es*̘ߝq\:W[4]5\>}GfQ^; p{m *$ExL3ǥ=S0_:)U$VP iAREPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP*8~ԔG2J)Ÿ#* ZJ(h.iQE (((4Q@PE( J(J( (|)!YЖs hcG`p*Yo~Z(b[ٽkܖl>M~%'n=nq+Z_O0Ւ"R:8 J? }n7;4lW*zՉrˑq]|Y~!ћ\.|!R)RJ38<⾝l|M2XNaUf HAxeA@Q&뗗1xE0a7h%41dkɮv-%A.qp%(=XGa<%u}z(Y/9B^Y?#(G=2x1 {m2ko: f)ꃶy_]]6 ZKthe w\Dy&B:|1hbҭ-/$m3pTȷ*@wȣ\ 9!҉<#[Uy2*L:FѠb[hy$"hUvW/,4` 'v~aq=,n͟,BnV56PHmnѻFC؏p(:Egꗒ'Mhr쮇J+cP ^:%-w*LZN W#9>V-J ng5,r~]co!o&5ݼtRY>Q#09-")5]NN1)c+W 2ZxF+Q DRZ34`dmep[8_[Y_Eqsspg}FXAU#1<_b6[-Ψ`rx9j[gldAF %ˌIM\4q2ݚIodZcofapNCxQ<-xe=3Pawle0B8j<x|v-1[\iufvuldP $ 2oUIJ)V-<_4m(aqxK81 0J\g9j΋('$ArgY#d'q$NKÞx-S̆;FL(,94o\M̚OgqvI7>hvrK.3=+źY,ܱKs$P HU䏘gÞx-S̆;FL(,95z?ئ"y r,e0bxے71lyǠ M2{: Sk$/=2p92;2zպX&eK#j3 8jQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE9$o#!bMӓ<Z+TuOJhc1Đ4E=H=3P-IjxL!1pыyyb^(Skd)eil0 PZbks$,hwGڽ0GqGKDFDW `s7<o,z;{$S<{Y9}j N.' Vms UV̥9>h[=i4#N[ /NIW,IkgHRA 9%ֵ,ҹ|5M:026I$냹uk]y˼8p~o3%kYmw0~s r:ڝhK3DBEj֨ ؓ݉__(6o >TN4 QQyh`tM%WIG:ciP@^Tc=;|i.v9dyv!0; U?A䬗R1ǐJ@9'eY$~s5uV6'8l5=B[#mLs p$tx+;7fڧ|ˍarm}xgy СR\ȅ]F'$`붺_5PCk)^݄> sVVM(}ޡA=lw$NsHsxf9꺰/&F93&O<~eSn%)nmm緷*MH T~-cSү-,"h[΃ɞ;=ars_Kzl_ڋq_Z}$/Roh& }}GO*r0GPr  YJcfiܳ#˰G8qRz Ż5GU9mz ;_,m9/g6^h%dʭ5moߵ0ngrp9Q`ګR)[ Zx-OC#n( g(M2P+8iGcFHQ/uH @;vG{a[C1.d}q-6>pNFOg IuCyqqJfB`pIh."'pL,Oq>Q@UWgVȚ(G8uMϏ!8&vт@Oj-P[ۅC,O6r88bIx,<.lo/.Y6C'nl`L ߣNd+9d~uO_O_e:_JS_QWb(+>lkk|Jb1]ލŪ.~?=:~[93QGysepDrJ`U=^GQHr)/hZ| Qqs7:#"Ymڍ:noQ^W]j7T1y8o:wʎ^2Fӵ"ݥBl&hIۑsϥuW7McTLUQ,ZMJz 0ǨA*uifVF^ezxM>drK*6Ty#<8h;>6G?%wg*v9{U'ſںݾ0bK[w[nR#OEs-"y#ӤN$B&1S9nqqX-n 4:\-1<~c $EsxC`v^!T3 zUk{+95ԱĶ0͜deT18ݠ *+y-/흠{|r 7Q>RsҦuulͭݥqUaKFI u Z+Eo,xdlO 88i-|Mcw-%¬I 7 E49$mn8 +/WGBc}qGrƃ$>*=ElCRv轠j{{PZX[y `f'R@[ev1ٳ 22q97E r0ϽG{u䡌pDҰQ 8'|gCiz󍾥,Q@FFq9[p\u0M&ȸ;s@QEQET5+}#Jn-i_h[Xj^mmoRKY/32%I^J7B}T/GMhX)+ȱ?oc"x4dX}ƋZ|'!YЖĂ${?Mm 4Yܴ\,u4QEHŠ(}?k2;>+Hu9ɽGLU߈ϲ}7'Z.tЎnwrS@ ԽώOQi]Ǵ' grpn$Uqʬ#ޭ 7%9 e\I<Igĺn7O_&{\\( 6Ӭ LfBTevEtQ$Q4pPp<|Kˠ`n)&^Q= @Tdg>8m#M5oo%؇;C+)AQũ]e32KmX;mYT} &/ ړ;gz`ᱞ@UUQ ;My]34/yoska*xX3.F@X#i4HSky֕PO|'+>1Id0N}VGgϦ2$Y #FzJ1M] m ĥo%w^ w*up:H09b֍ 8R\6;ʱVr+h5yZi3tZ?[8ـY;-[kilSJF: ح((((((((((((((((((((((((((((((((((((((((((((7:q~k۫{fS)E?3t'r+8;;"ok.!-oxǴjUiVztZͶy)۵QC0rIZ(^4HO6$yjQs 0yxn4 bKIekآS$gV9 9mBvWc41\$Ē"xC+z}pQiqi.{[:bSb6wAϣYLqii%(7vߞ˜ƒ<+ɍd'L|G>}0} a#ؠ(=H>V֯5+ FxqCqe,IN(Rcv (S񍽵ΛEaO>b}v'y<1XX_4&%m\pDo&U1o1xs]Yx^ 2=^0n[!bCNjbzP}sf(/mL qu@9Et+}ZE.D ]IˌpN8sm5Voud\HA‚x ס@t{mX"pG*қG${IUQFK(1Ωleo5qF2*9v9ϤjƝ|n57]55nȠ$ g;O\b "ѭ/OҮabӮ 4nFT0=?>8i_z0"9xu9]> WQ@kmQi͞ev${lɢk{p18ċ>FiZ5:c#S%v;3@[1\f><=i_= ),KIDd 8UcZ 6c1]yC ֻ(ϖS%71R/٣xW1B2;"Ӯ’[ieB5 扉h Ies^EyՖw$@fNBq>^!ѴiR-OTr,*5Ep _Dmj\b3-;pF>ԝ/1}ss/}*^ _"{|>+ 8=IQ@UО}ܭ&a1J$. s4XyIZڲm%jޣdHw6B֊^>Bzښ-ƇyHI :xK43&K\5ۈ#qٮ}OO{;mV)VJ @s޶)hk:֐)OD>y`2~h]{뮢 ( ( ( ( ( ( ( ( ( ( ( ( ( (<,Ṇj+Kq6`Gگgu?ٯ.VE f 'NմɼmByqLXg?nɇv?ٟgՑŻ^V3s{פp׍m"]mB*d@*+=v[t"[)}5j<{Gn[?ΐFR2Fd}oB\,D1961St ^8y>Ѝ$k d"8Y]e LQ2A NTGLruv}gh'1!b8gZ_IE;hv # r=(Uv$yZ ZP2~^:+v,湐1d WWj1cy7po `O /0"m?â)bk1/n1K}HKyܲ!S2wviV%VFӈU؁:]-QEQEQEQEQEQEQEQEQEQQ$[C/!,Cm>=hJF6OVdWZܩlmaz?wZv?1z7h(M;XoyY8B=gy?}+M[k˱n~F*8 .O]eY\]}"6 XBx 'R\ӂo"!>G 9U,+F;&4OXE1>IWcmkA(Qϸբ]CǺuDsk'?lHdRH $M3ǖ:g:[F )mRF*mQ\猥濺1dP2Nٮr((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ϼr%ME]K5+ `rh.KEcg}al\*FLӭ6o:O\c|P>Vn6 RuxYATX~e= lywZ^ޭ1чPi24L.~P'Sjoq[/7Rg4e!Tw1'~ZAo¶qFFm p;qVl8G(#݄.DgKrt%>l!`S?P76wsrԯ(Nrjoq[/7P6:%}quKٲ6,I?Ҵ(ղ\joq“V{ve= ?8@h__oZ{ve= ?8@h__oZ{vc= ?8@h_?o^{vc= ?8@h_?o^{vc= ?,?8@;?qUb"b}n4(ܴEQEx[Ver4'+#7q2sҨ\:Vȗ}s}uHFJFdkh KC-,0ع;vg_`xIVKqx!Y >B01zb xmg+ǎIMyiw:\H"7.R3dNN9❤s%i|L$V߮k+延g}B73ڣѴxgg# Hxt^$/"~+騈nKOw6޾[.tkևJ9eJZ"=A5@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Ts4,q 3@\k"~-ErrڀQ+˩+ʹwZ) endstream endobj 47 0 obj <> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?MEh]] FRAԦA `wۡ‘"*`d斀 ( ( ( ( ( ( ( ( ( mMAVWW~Tm_AR@ }F-W~Tm_AR@ }F-W~Tm_AR@ }F-W~Tm_AR@ }F-W~Tm_AR@ }AU# #Ǒ#O,m[)>LAU;A?g엗B$^RfvG#t XEZ啔w3!$g ]cLeem ǹ%s9ӵK} L[Bqhmoyd1UnAnb66 O6p‹$*c"1?0dnt4 ir-g2yB#2߀v98"xJ4{ZѶI<ps/H(WwpIk+s_?ZӬn|U{o9YpcT*Fwd@4XsQՈVʡ0#5m~kb/9w:g9kz^mtISա C n0GYq:VVdcּèQlhۧ-8(]BZ/-FJ+КRZwn1 ȮAn672جIm3Kı$PTܻD|)sa?e1*vc׻[#zm//ў V >r,o..i"\=RD*Ǔ=u}cqiXN7K^j -*:+/9wsE: S˗MUY9zmJ[Y>j}Gbe =9##$FXʢm/[Xֵ+]b`Vy#h''#4[7Y X~8^%k73ZB vПQYPCy/{4aeN.RWy3%N\gW)x=+Nծf}:mZ%R`#'yừ<+h ]#ÍNc~gU\Ww6ztwMֶs1<m9;xK8"|F@f0Fxr3c=^_ m{.o-юpD=ᕁHpr3]=֋o2i,78T.1ϝ01;qysn}c9ʰ\:n"{kY'ig$s[Ǵzb$q] QEQEQEQEc^yp^tXN7lV5um2EJ(Š((( mV>Yy+>UU0KEQEPC&$r@ǩ8}J((((E`* 8-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@P5FH381 :g9cEC%NU(((((((((((((((((((AEPEPEPEPEPEPEPEPF(R@ E-&(QEQEQEQEQEQEQEQEQEQEQEQEQKEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE ( 1ERP0)())hEP(Š(((((((($PEW7D"ԜR}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ia {6h2MEw7(UO?֏?z-U>L>LETo3Ǩo3ǨS tUO?z?z-U>L>LETo3Ǩo3ǨS tUO?z?z-U>L>LETo3Ǩo3ǨSepGQ@QEQEQEQHHU,I=k Gibǘ`[Uڦc*W\,aG,a]٘W}߃#$}){BTE =Ac[ ZHҪrQ\EPEPEPEPEPEPEPEPEPEPEPEPh@Q@7Z {u &gb8#Bﹺ_èf.QmŴ6ݑT㞸Z(#B,!TV)P/0 # QEQ\Ǐ&ա6-'[` X*Y?Yu0x\'R1:bLH=A (*g?MPdz΀#(e##avVB^Ļ? 1^f347u}XyU2gAEPndIn0H^/ko KE"XxةA{b#feRqfx{kIFi]GPUIp2 *?oCrv.Kq |$˜rkrΊ/|Uep<{mZI[)b5p`N7'9'ˌkK/#İZ$b#l^Eq6zZ.W{hItF`2ގr$ 㠭ewz`E7*Q,W%H#f: +OlA$$vO0&˷$v<2Fm--G=?9c8u4U='RXmup+E :R7sYY6uF~}ʏ snǷyoo]DJ8Oz'(W!t|;FxvtWK藘.J2HZ3F`rm{M]Zea˪oKM[*.N(8qh_R6Q$m+q ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (iz9->Y?Ҁ)$MS4=JT >֣[gKҮ9fd=%1 |ҬM֚Ȑ<(^ B  ?ER.l/&@o2Gjv2|H@ ñMa%q~f{f} pa8}F*yipa_1 b2_ 1Y\eX02ygT Jl"y~\+>[\gϽ]d//z6;}:} m:wұ7hܙfgBv'<P ?oHU*|T* <@U@T7TQEQEQEQEQEU?گV,hQ@V4I}O.Tz4Eyش/no>>y{nlvP$2YEo3q/dd> }xf88Ȏ ҟ0sp<^ם_y?1+|=>OK^3ogꎬ#+O (;Eۼ=_חZv3)ܸ;+qٷ2ֶME"b`77\SYi :8?+ Zϣ9,]n[{xFU*O\%711xۼրfo1~эrj&y+ Xf v;c=vޭջO N#!xS8byeuHgcu$׍&ci|Wox 8xpN0A ITOU8þFyV ( a Rp0 @Q@UԬ"t:r 0VR5j((*ɓ̉ր)]Agv9׭o4/GJ`0sՍۿћSTW6BL}4um>K)EB`è=XGJލQMY#9S42ݝ:O,u)i*ʨo ۯ Y^^^O$/7"q-@haC8 Gҏ:?~hm52 _ʼnT!|F9]"*LQ%kayHF>S$n:?~y{ 6wWK7[#bHѓ9ia=2Kɣ۴Ld `\:?~y{[Y> y/e8m@P;>+yRJz1F;tR/ Ө S^4$@fc Բqiola 3߭j@h&`$R슼`@~^yټۖźc+|+~>GKk/) (j'XkVlܮJӘЕh1}.Tҧ+PF"N c bXxCQZ9Km=9aL3M$iEs8hLvE?:hz\jEsg$MIT#KEd^iS\3Or$/=/hRXim}or>P1A?EnQ@Z7 u+#e1#{cߴ GZClg/(d4x3)*pz<( 'IJV?9#TQVPEPW64PE<-ʁdz((5A>->x?..ClScgzs3Gc}r'1Ǐ^{ulSEQt:vdKh.7lPWE(o֗hSQzGuKSEⷚݝZGE|(8cM֓\"p3\tϤ."Hd":fGngȢ貰2q00)iH4v'\eA8׵z6VW#8=E-ynoZ[X- ܽ} @WqW-{ټR.7k氺NU.YD6TryzVY_0C3'FNsk,?55(.Q]m}lɞ;FAӜ5lC\}fYT=;Pu+WoDI 2d2:pumDu8vNkpf3Χ%C6{𥟟>^ ܒ]޲%2gGB3('ӬZm/L.ntۨm$hLav>c>"MCѵ++۝D@߼Qcu'i=:| a%*,{XPwb<PEPEPEPEPEPUtM.P80VP>5 uy2 *O#cV/먼3\,_`c"0lh(z(WFq&q(/r@Cր,QEQEUR{) >rvH zP( ( ( ( (ϨZXdMRq /4f˶yiaumx+Cl|PĎ`#rp84h̐ۓ8&6HD mI :ODƏIusj,,ftY@X+UF sjbkV8́#!FﳻT7ߊn|DмVhmy6郟u{zV5"-o /'N ep:9~zn7[[]GI0Y }Ut>J/*'5][^m'$c,@EY @gxߏCT%mo2躜2}[icLn?itKǺ`LCxˀ2vE<7 Wdbq(ί${ x\3#V|qʜ ҴgBE,-_̔]0B 2Few$d>һ]f #ڞ29-|6ۣ \&ic$CO|ƻXZn1- q<2VR26dӏm@tbP.cP-gLz(((((((((Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Ptu6K9P*㪰jQ^Ǚ?ÍT9 ufس0?Ms͏Mz}kb{Ρٞa X?\c}^E؞g¹?&W:oקG':uα?6?5QΡpG٠mJ6 ÓĐ0+bjb%zcQV+\ZH =hQ\e~fhQ@a>/5Eg_'h Oօ2y?3ZPe~fX~fP=Pt&3: O (?2y?3Ge~f( aТ3/4}_'kB O (?2y?3VBIjZ(4Ph QEQEyWY̹2>5y/5khlbV?)S$XAwH:7Bg  fl$& LfTY@y1vGX<{suo$z{A cb C3Yv6o =򥼗7Rwnn/U ˈvq9+c>{y#]5,uܰmY Ì0¶A.MKJ֦ҔM}-ėٮ'fo/iL(|-4V졎1(XІmHǐ9{1m滹 &%fV3N3m v.o,-xz MkEQEQEQEQEQEQEQEQEQEb\265j@f(LQ-õ<n>=jr2(oQ 4[M"G zi7f(LQxm[v2G c`:Z@}oVk aֻy[f23\}jX/-fCn9T ȃߴQ o7p 2 =v5b&Lij9n!J_ˌ3N'k3}oE4SC"H#r0# B)1D+bpAg3}o&Lih 7AGf*(oQ 3 A튆ԍĈwTVh(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((4Ph QEQEfEyQ6ߌ}\D 1}iDvkxsR7ݘ|#cWW-p#ͥ撋[[hdBFHq.瑉ªI ;&Wx\n`6t=9/W^Rx Hmp(A$c!r=y o[C%ZA%淹7ǖ. UFEyg{ϊSdd>Ғ;R,2G4XG5 4oRY6F1Vb8^ "t˺km(p-u.a)Gɭ Z+e!l'IǞ}yFgԊ%oeX^kWlp9'k48SJVgQصG*)`PB]1[ >DPʽx[Ȫ\+2q=s@%ύ;4^(m2*z;=Nexz~QA÷\($&Aanzڽ;i Up3<!EP::R29[-Fk"xUIUV%bT( I%0Z^kX噐]lXG5r2`a3|Uo7}+0vrx5EPEPEPEPEPEPEPϹ?hV}|7 &څ֛%6{&#kŸ 9#ԼU,ZTwdo5Ebq%zlcO(n g(e_iqr6I$*orGOju9/lK  " HqO5w^G5IE;(dV$Gˌf4>Gi34CI#)^Kstsf8mu'hjZwXEoy->3(-x$i3bI60tޠaЏC@hwIa=I-ʹ34d2Ə!c~z>{q^*ٞ\ZA3DbPJq*D)8cId;@O|d9O _1}63[=yݻ1Ca2y%r}ӄj 7#ܻFӧxm-c~?hK 8- [y0n9 ;X[&eSɭPZ`sycyT΢fՈ <(~#9'l,#ER) snz}iKѺ6h,á3d墳nQDv W*0>u;$nu[MЉ2bwu`xSX^%۽@dX*T9$I944m䷒D]$chu}Bm:+.!V$Cuxqx)zĺ5l5wO &uBXym-V `E`*46h)tG\RRyϭMg:]Y[FbKݻ5``t ( h >Q剷5 ׺L(#M5;vKe`V;y en@SעѶuKQ[Ǻ?Υ(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((A+EQE|+OGo4 .n-%\mqx[KHd9t$zdg5~Yp؜p uO)5&w_qn/,絗>\Ѵm0q?'t4kXª9pXntW)a>֥yUJ e ďO]>Lqv̲yBV$8] I.i"[]6pJ6;8$qؚQf>|ehgyp&Bn1l@֋MM4C0)hf^۟z2Q#EUbIbpzazEPư ,Wgldte7t5{X,Z4tA/%f? :J(zbXZ;i{\ǯ5~((((((((Ugm*i>7?QZP٦(4EhQ@fsEgo~MVi>7?QZP٦(4EhQ@fsEgo~MVi>7?QZP٦(4EhQ@fsEgo~k)<=ZPcAQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@EW((..H 9[mճq5WS2S&:$Թ1|VeX=bЦvmC0wGDNRYJf Y2swS^Eygyexv kO+[9$E*I dص h~^L ʉ%fcMS:^>>%[iL}Fw}sZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@*܌d)(W>>T JKY31e,p @~.SV7ڣD< e2DppI@%>ڬ/>_h8B 5{h#eՊGYNq?3ހ7hU-RʩrZL>X%UIn\q?YAѢeRf N/̾Y7n<џjh+ jgXcXLw+c&Aַ((((((((((((((((U*pkQr+zUi?JFyX85{ix^MmbvH j)dd`TQPwS$Ewr?3hf˪ry g3(j( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( |r4l?QL52IF$:ɠNt&A}vq(灹qzK[sht멍ڪB2v#s>@VUGow-̓<"hu`A i~(Pf;@Um v'둌i3g[]BQydF45|+sMGIc\By̖ŢVwgA$*ջb:+Z^ m9Rf9&VH3.HV` OoQ[4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE5pEhVd_ZtQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEUԯҴFpXy URj((((A+EQEEdۏ?ujBrUH# NzNo k`ym?jxw@5{inU=ۛSHf,/#cɥ>T_bYٵm-|jmx$`ؚk]qɯ_9ܠaD'?2rI8EfXʒ3Jk?iZ[D/m&gv& x 05=>Tmɧ&H2q٭kxL22FPmr-䓟ZtC1C,I!F܅ziidz.%{ odۄXGi`;r|Jjru7uDYbxO[gۈ41\DOKuGP&/ȠCז_ifHumeG8PtjƭInvTՍQA@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@5ya0QvH8T`=KeinfytDdɭ*(8ԧ+N/) ?ƹhAw-9l}߹O⺙?ƙ@eysw6{-'DEy} |IrzTZs*x.%#g"+"™hA8{:+kq/sqs<E R$ס{VtvfaKFӦ5xT8PFs>4PyNzܛ|p7GPzVj0HgfyZk;dBpTW_EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEik4Q^ $s:!r:k@t4i8if':v*Pdֵd\h`KIV;1%ƞNeoW:n|;!u]I`I[rB+9mOHlK-N3<Ԗ9HS$qBoO 5$YLR吋-08> QVS@=Fh-4iF+%95-&{[Sorkv,;I'=>HvȊp##+JBe׀P?uW[Lqd1sa]%2(8B P8S(((((((((((((((JbEG1̥OO5gnKOM[hK4ލN'i#ʝwEp0|NQe-H/\CU+(`vZ'ڕV&Im̸&I<#pzzSf{ \{.ˍla (Q!:n@ןd sqr"$#;N8霎GS@&g[F^$,E% 㨫QmF8t]-@wqe@2'z騮.-ψND%ٳIPϡ*> P)6`:PQEPEPEPEPEPEPEPEPkujj(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(٨ldzcj( mFޣ4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4h4jS__tPOOvOv@>M?>M?ڷET4i*0fmPUWbsV( bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V bb[*}}n/i/iպ(ؿ;Gؿ;V"Xq;j(((((((((((((((((((((((((((4Ph QEQEq|?Cj,@<?֮]'P^ p^rZqxGaz975v;idW;z+k+6򾯥i\ 1`è=W^55yfk7&D57WFVSG4}0oCjք[;bA9$Z|gٍfEjo0S((((((((Q@Kq*ŘeFvd*kmd1GB=k>u;NOa;5IPCF݊iYMTNҴVKh ,I=^ItN-o>;UQ^'Iuwnush%;!\K\3v8S]g[r4gk.#rp<84ZD,%1G ̫#F9編"\ON㉦w؈%WJ{{yW)4MeB2N9f%4qͧa$R!~ULd,S$q@DKs$b_40:$g֮$x"/ڄrck#żfܧWw@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@OS:uOt* j.Oc' kH!캭J30WL}垛;pFyBN>=Y}#@\j[^Yo:踍@ $Gf~/5=R<`WmBg8It[_ں޶UY!W)A(lr}n+4Q[8[KT8Do12Bp2@95i6V~2H "k{61-n>gS}} >2)P|hA<*S{Zά;[i* l'ƞkfɭߪtyVO0*<(qh,tiaM-\YN';"|\z-s Cc-97}ٴ țdg#GUVT+AE+Լ'S]?vYIqwui 8adR?GKGkKF{nog_'&\¨U 6>,MV5u].Q f8##k Zovڞn\iH+cJ>Zkڇ"{l#'; F (f-o",  [ O ^|f[Rso4ǮkW ږu$˧Id$8$ 6@QEQEQEQEQEQEQEQEQEQEQEQEQE ^((+}iDvkxsR7ݘ|#cWL|+OGo4 .n-%\mqz]6 I2[5ݵFƳ“yyͿ>m 1Q2(OA#wcF[]"+276[Af֫i-rxیcZvuMlb-VFiNA8͖]--dֵX~:23u $ztK6 %|7u;_sm Ol6lPtD!*ILcHB""UP0@Q@Q@Q@Q@Q@Q@Q@(˻p`  0!A(oiY]Z$9fPpEdK)t=CHg[_) 7+3688 H.l,"}k?:ρr/"HM.Q.UQؓLS[dXL!xۂuʊEPҴvfGy]'*PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPL !y}BHԡ @vF-Ƥ LMrT<#=B\ բ9Aeixb;Vr:yt8'5,_KWϊQ+E`'#8qۢ9Nf6 73Ȼ ;U ^ǜf9LLV(qwi-XW̅t^b~LF1{ע2b0D&lnM&i̍ 6IPgҭCw+?uQ8$m2qW( <7-k˙({L4nxQ8a𥍵\Pھ6`Bڷhpx'J[:#H5k̠>fp <{Z޵KKhum ,#1$rjZ((((LH$A3@eY{Odsd42X)W`Vl42[3yq<P\\ӻHO&6sW hj % rʽqh.5k}P#D|995~˺#m5Z7 TDݜ1{թEQEQEQEQEQEQEQEQEQEQEQEQEQE(4Pz( (9oKiw  .L61#^BB{1 *+ Vׅn&]GVqO׳[/`&y4_VweV]t5fGXuvjvaw1*nh7ZݖF^&YЌD_w͖^AX$I* }J Ip\kwKo-YD)Jƌ~٭QilQ*(P~qZꨢ((((((((((Pr⦨$Xh_ҙE8dPmOV EsFtky|ףG)=3J1Wi\n!Ya^6V^A%/k[A$Ulz{]BC8j#<( +h\E9=S& <,gүdbԯod=z_J/. <78 UʺgרŴW#It_s:9}$'=O"i_˅*69s¹?E?L ?J[5}>_C Tb6ʳ(GC K "&̗pZ<:V1-#|PTO=+#Ne=ܯ]Cv%;m 'Fv  sU͍)Ĭ\Y`0S~wc\gawk_wp_h4Ӗ  b@=֎mCO êʛUp}($_(2fl|̣g Ue|Ikeq,ZW6%͔vZcT.gg Lk{źvwi[]Dٓxzt(xk]ԧkk]RPK)%[,ȡrf`w,>S횯XX1|:rֆcs M-OL-Ȣr@Q!c8l+3D5`C4siVgO1b~u²gHgPf~XԞ2eDx`_mڼxEKv _K0lơ"A>j>s1ɬImcUU-XmQ4*vL٘lx׋@#"}&6O!1Hm嶺WIe e€H*ޗ{4ڝpQ__{^o:ΓX[;lʢHo2=c>Om ď-P4Q992ۀz^mVhp@cfU"ÀXg>դ ғRk;xC_Cr>\ڰ.|1fɪ&$zɀ1`u$,jm>Rcݜڀ76tԸtUF*,q hVjzM0tRre21bNzq@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@W7`k@{g'b՟I4FKrGaĺ 0Vؼsޭz֣`Z=>  hh9'85Jڬ:}!N3Ën F(N@ZB'x:5տ٬-l Kplf;QdT6C\O$``nb^֡ zD!1<[e7)7~Ryb{9SiecH.v9AC@mՍ]k6$7t+w"RѶOu`ut^}B;n4R ԣ׎V`ahӤӠxƒ#0 .MQy.Kuo0">nO4.O-"%G.)nJ;{x4j;P5 f '8ぎĂ&hs-Z[nb d:u8aTҕMbZJ!HYFQw z8$(#&`MA/q `ReA$ONOcIQ821ӽdZxm q9!$<$?:Tz^)ckm.3 TXx9A],KeajK'shc+>A{NyoJ>cH`|g:s@USlfQ:֣{MѤ'dr5c7 ¨|;kj[]FK+q8#+CB/ ZPko2X nX2@n(( _WF$}Ʋ }u^ϭUF}Hմu\`cנYajWteivnV _ZugLDV;< snY>~d/"4Meij-MGj,2V$Ey8di5bK"wP>V4Y|×9WqGwK=-c-$qrTHpN2Oaqx<-.Mj xQ6 POq@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@` EEj>!ӭx%|rwGчTJ5Š^k V{/?aje|?]89hy750<6YWֵ<;jP)tg,-[}ϽUz %$އim&Ms×t2ṛc8;C 3.jWs]GNwj;0F󌃎 V{/?aje|?^N'gm[Quk6̬A8֥y,-[}ϽU8gQ^k V{/?aje|?GN'FTWտ*XZQѡzUo>_Td{/?hyEy,-[}ϽU8gQ^k V{/?aje|?GN'FTWտ*XZQѡzUo>_Td{/?hyEy,-[}ϽU8gQ^k V{/?aje|?GN'FTWտ*XZQѡzUmU]]3*yrvs=a_^y5V\z OP1r@΢:)[~yoWA>԰ ?foN.4$VI#1ӎ{ Aw~X6'' ^_V7Ltf3NO$Vn%Ny]1vO_ ?_ ?zO;;^OCY6!^Z rt#rp+]2+;u%*o-?ZUeZ%kJ4۽QOOֹQOOրE?O֏-?Ze-?ZqPçm 9𭎙W1|m 4W4տCOEs#BJ+KH#f &/4n";,oad.sАsϵ^>!_0[򟗯 Fx=-yIk?g qibAܨ'f;\|YDD`D)S.JMGVI! @X'I$tX&j!c~TƜUOaoD7/rG4O߅ ??e~+)\(ܾ6_G4O߅ ?a/<{r7/aoDM__(eܾ+?e~(oDE_frcǷ/rFOY߅ _FOX߅ ?a/9&0C/PGzҜ-fys望=Ʋ֖f6nGFuʔNnQNjAN;0?8 zW֦\ip2A u q~uVzwykirf;Sګ65o ˻KG s{Ќw>(2I]*$tT6PYwnšR~4s\MEgH ̣:2?1@EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE2iD4gvM>5;a}llyvqDw7[ɍ*z`ECqwmijW7CnsK#E{-FYz -.;k&x[lF[_޼kwmb1Cpx4b5,ɿO2W1#.X(8#]F &_pECuwmc+xWE\j6V%)16UPYxmm丸8aK<0UUI'*qgY\A!GX ~4~.-gx_' #O:ZKfSl(dsm99hQU%+k'* bр<X;ynf'Xh}è3֙c8C 弞ciU;sF$`v 4QEQEUdl$Or@~u$0C,1K4i$ĬHI ;x(Z*,)%78W ! l'<6H*QQGsM sFBʊ$?KEPEPEAsykePҸPI2{OYZv1Q Vs蠞 E#0U,$ &[;.bxd2;dPUaؘ`^[pdc)'-"[h"Fu#*F(B((((((((((((((A+{K ͬ͌6?:E4wBi=6"+b!A/&@/OF07?ݝWC2H8B#ノJkSOdrsTNabcM=7>0ZiQmfJaȮJpy>*K; dKLf$2zgF#uH{.DYn#G~=ـ4{ZpCö6d\DۦWq=74Vm.t[YI#E!6ӍV5AD $`Vq%HP.fkuk} "sS#F+H#ot ?ܐ5-cRUӌ)k4#+hwYj`$ƌvq2xSxެC̘9 r<>xSB`~f$79$5k4=['6Rvvo` ߏ; mcmi=GKDpʪ8-5eB HT#xj!ŭԺtikb'.@  Pܓ xRFe} mN) s]~t{\o-2 ThWG>=5׎-,oQHW˅\`(<^z晠]\Af5(+Ky̞n*{g#&yiYgQMf4+ˤ$E]$`g'ɯEh!ygXAel~U^IltH%xUo}p(_z~[?IPjSR6 g'ͨxZ}M=x^}fsvg?VKcKAGql|[0 +-9Wgocii$[Z ۥh d>GShZsDEmhKX#T:0`[@?ri:ًͬ5o g=Bfov8g>Ⱥ\ Ɵ*Z& M G F1Ik\ CG Iql6lN95/Fi[(H\ģڀ2u+}&n1olvrooҤ[{Vv3Il 1FEz=ե x!nʁ}6EA>dJYg#€9 [Su{};[OKk|6Jv|c.iZ p4%tv>서8,+ c%>c>ؽ fSg0cǵyjf<%_Y֥\I%Vݷ9coj`Ps 3B9k6vs[gJG)A =%_-a/Glgb&~٩=3@-Nh+:> 5+3Al@BӌtiBŤq%$}IJAncOA@((BҦ0B}V|5w$ZbZLQyi.l2t[Ieiq($P07(8- Khm"8P"q@i࿶Zhwȉ#M+[ɸ6m.i$_"+tnF$AFT&;Sgox2E.`r -mniErORX Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@EW(Q*P0vmPJn:)V Ry&~]UܹUŒ9O}]:\3Z{n t  g8&MEOmLƅG!Daw2k[Z?Vm}pvn<@Vo؋Ks#.Fř$U/9O-ƉaCc-/?ι~!L\i_l"dŕf+õ(p_8;ޟ| 'dHQfpU?(OgFV5.rH'_QƗ}"ͬs2@YA ~uTHQE #wDN46nVuOVژ7>]g0 pbQ G8]Mx)`ͰHofӖ\G) N3Җ]y,z[H"u,778h.o}@j8\nAR.gysqtOhz\@,&v6]@Ϧq5w[u-XC7qyS[I#cq뻚("G*ـ<0WX =kcqt{\a0uc]G+r H4Ctʯ / dduR39VzFMёhou"Ϗ t(WeOɟueg''5hZEq>c(vKtm͒rr99f?%Ӭ^5jLcy3@|=h,<7٫[GaP3VUcHtxEH00­UQZ(iJb#62@($GAQizݖ%>V7yֲ8zP-f%ųNDo*y(8zzg2'7at<(J+Ey%LeǟgO?.|z/Og@6sF?9`sh:zڼ{`x=+| e%:sY5P4VpZImϘvmV鴚J+ΡcݥD/aW`y$G5nZgo'WtiXD*~nQU7C..9 p=k_xh䳒i䕣eR!ZhiZȖ;)hd{QFLKI!ǽlQU/O0C+B)z4pf D1f?_lQ\~-KfNQ`T Z6VϨGs-*Duszߊݢz-m6=$ᄌ Bm;z\=Dl 4b+qmn}(u}F ?O}2NJdujƋW᳎[-f)) lF?:+lsxZ]GM}EV7 qאA犬&@kycvۋpyUض?Z #"H g ++FTY?vkk(cjlIeHٕJͱcҀ:+ωme3[уܞ7PEsQxvޤqY۬CHR:q_)loV{ۋs,DaPA 0 ¶s[wG'#EGZ6֓F-hՎ܃{ ݢ((|O_iO,K'Yx>=WGmkHI %r=0~lPjGbHGmU { 3I_˨<^鷋ObPy J+|]q -EHEJ¤9' U-;Ʒ:PwNZg Ƌ#/r *?(;( ( ( ( ( ( ( ( ( ( ( ( (S= ^_[i D]S{p2{ a>#ݵcxی<@g.˲LFܠ׭jU=&-!a#QGSQE-09_/"d1sck [/ɩ/a[bK[vHc=Ш]$Vw5<^T!ǖ^ֺ/[k^ 7wFoco=ÿү9S])hέ8tk_K:]] |ɐ rxg蚬ͥ]kPT;75bTPn=JEӖxI$B eݜ€4;CA-9sIo郜mE07,GV:L[?S2Xzs^Y]h64G3\jAI;2hXb]p 9ϵd[T5%tˍAy\ʷC#R o' * :Ba R&x|-F!qԁm@ y _YʹaLY`}PAEq7wWNNs l"2:L5FYKq-ռZ`K1ۅ-:(YCɤ$2ll6 ִo[{Nnwm0 oqtPF]yci˨I "8 H9 s*O ]&igMܨBX4\Xۚ( X4GKӴ,(33S݂ zVdd.%[an! f9s@jz_kh` #eĐ'E5kOHw97 lx\'r@]5h~-n6ai21|x]MP-xGZiqu ˰dk$l8awj ^%Փ,\:fϗ0(} k8<7?.|4$ dmm.13Z&VHK*ݸ29X p947s[%_.IZ/3z29q39֞k5RôJAF͸+c.*6Sjw:d_%& 9'\>9[tǶy-7HVX t\SLf-@^0r8>tmZmD{TGXgo#g?ax=wFs6N?-axPpW9>Tޗ5:!8d`C+)V5׆5MSI<@ 3FY7E V@ 1cqU .ݬl$H1DG㗕yc9ּs&lol$"Mw+n*{pEib/ ϝkxM9]FpT3UoEn,̐np@ǮES}$R 0g2R;rW?{j0qn?yvVnϑ6v}}P="Ҧvti ̀@̵%Cj>?~W*G\4OImZj2j7ٮK&q[8E/?ٱxu>c,+v+vwwZ=W3ku ԏ$J< gl廋I|E&6p ~lqJσ"YEqyC$mFwqq>.L!/i"0 I~66 ڗƣl՜vPKp%[i <Rwn精{WVbDd򃁏4 +h&{/fƍ7BkŮua`ng픱@qi#8$'@fA}$XGDi±]G^m:^kan1 K}bQEQE_ͪWaQR3?]8o;%Y(?zLj<;wkx]@7E`F^SeFr}6hEv]hJHD A6\?/ V|?kmq׭.]xz k/ifiSltnϷZڮ?nnO<k_)u9ʥO[xS=+kSNt^ݮ4{U :8?: _k lm+;$I6I涴Kkk}| >L1_Rsʒq:NV.-m.RYGp~B;Ua]m𡼟9wg 9{LWŷfԳ1!$y{\iմZ?i.#|{XV`H\|g,m^k%#g9ܽFVl;m`yPy$ҮTzc<5 b]:4 | mnIMi ǠѤ4!@єyRۜ6lrvwYKe !N{ޓH8#8'Igʱ`:%ʖpN6)>^gt4[{'-s޶[/a.qųl$pX'xb1NF@ݵ{^J~&qZG7IUCO5_{~_3q7|g^3kh}K~%°˰%H#`Q26(g..%cR$UGRI ii7\YWCHn?8rvG `x]䓜BTu;Z OE+",K`Hp8$+k{t)~VH2Ў*Z>tuk /M\`rY{e]c @pL*DqI7cU#/Iai]K,lȬJΌw 䟼:Poogy,[]jy׃5T}ś6XF2DnPZ;ynf'Xh}è3֙c8C 弞ciU;sF$`vTH񖳪أ-;`|DžL,}ҙ&[|𮵩@C(fuETr@Ep Lx#Y,$Ż;~ݞ}:V֭6ۭ1<Prs{_Ep Rm?KծnŭR[jFߛ-8ԯ(ũ%if5&e,$c @j6Rjiw^FH ^r?:K!4bV$f@$&,|)kzmս1M HNAZҵs@LԬdmY '٦]i8e=ȭ[%QWldC\D pG*m?KD .~y,ǧ͎/z6[ΥA*͌H![ PVKnښxW^KNT6IǚTEiwqJbp1OSWi()i)h(h()h ( JZ((bZJ(*_ͪA18PƋQEH̿xgS?'k<5ye ),ĕ{JCK߳ω݌#'o>"G*ـ<0WTΝYݞ&gJ dW׋uX-m(}'jƧi iK,;W >8wOAI+]5Q+y=287/ VZ|OGYL/+N3NPwW> CYJ8{(i`WcRFI5n 閑4f1T.@8Q ++XZOu4Q,q;*y=*9kB;P%炒,-\24 i=j.ZDɌ]7c|ҟWy7(+®TPJ/RkRh^iiwV|l38> 6Aem!cT~^ӚETҴEWiTqiֺ}- NId%Ebz4P+}L(mHLndO.]F  Ո-FKxcYFAbrIry& ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( {ag[kH.$RGC1V( }FH|ylh4mA!K" ],1OJѢ*ǦXC:+d*P,J"s+[gguf݌(U p8gƭQ@[ZI4ְB6Z8™j,Q[EoZ4Pm]R\j>w0PKd;dqɩ46_O2B@B%(vݼWX@G>cs_ 2%o,VdGy-wtݙ,vQ⣏LɬKF%s+U(ZmC6Vo4Bުy6R]AgoĿ%H] EUllKK+ky&9P$jPi1$,duP 02{D}::Y۫#̌"PVGpqr}jIm-Mct64z70Էvzk(VhÀ}@#X*ͦ\ [K4?ꤒ%fG+R^۵ 9K EWk 7&rŧCĤ aϠ,6/iYvm1V(M\rRPnE8jbI|gn.miK[Y+>YZbccn$WO4ZYة[;H-ձ a*QEQEKPѴ_l|idٜgp?*/<;>;@Ҥ"oQ z(b#P!+x#(cP(UU:;S &;Sgox2E.`r -mniErORX օQEQEQEQEQEQEQEQEQEQEQEQEQEU;gJT(ᢃEW((xR4IM%v$t`] slfk3Cq7,đP&'8J-6HWrZڄmRj{ C¶WKA)p;XPFG?lxUF (9E&IH7gsU9'-҇=aL$Vr0xKuHgv8 d\J6X<5[Kۻ,zi6ޏ@өxkL(#76Pܠ?iD?@)hSS4}:[LVc{fHH*/^u*y:4r&zeVGGX ; $jV\ꚭLܵ,&4,#:U8ʀzumRFӞ99w?Vz[ / dvA{-u,Ş4*6_Ե|eq ԡoc[(f4KY7e۲(pCp4rs`XuSzS $9$ב#ϣizվ{v/YuYn- /F+ḿI'kOQ'鼽)YwcymA#BC4-{y)$-c 4zh!˞$7dK( H 9y}gĖ1qc}Ĥ=x_dOm4_M!-#Hª(P:; Uj ((hQJ(_XZJXG>naz}*iK-JQAKc7NsnXpA z[Iuzo=I|V'QWawuo~m [V 9pnlN09axoA׬|4WZ Q$P|6C9#rO57L V۴X5Wʍs?ܩ; v^#m//d[XX+eԫ).8Ӛ̽>7:ŴVHph>g|;jMYXZzǶ;Ֆ sNU>:ߋc_\P,bn%` |3_3wix "9XU@;I gk_ۖ6vh( g",0'ۭ[YEMkdmq|9 ur>wW>Юgm>K;kYྚHZ-%v~Fqϭlk:ryfiʥ<n\Cim%ĩ1)wTu$;7O ']B6!89ՍSMu*{+YmЏ,gBN'Ŗz+4iX%,m+ps.+_Wv3xVƼ0/`xcUn5;hŹi2HwFGqR]gPZQ{Tm:.Rz;gV lѵ3ww;60v:dZ˧Ǝ<H4,g\nZUzMjm}0QlR] j_:mKb#nX3ꆀ(۫tmHn45V E@lh:w2^i2@[)KG99ǯQiAI@ÅF`6xy]h,\%-0%`Eym–s@Ϭ־msmY' $d%DRhݓT/#`VXn8sGGwP6M& I%@~Pca`w=x&kzݛtStl@q9|(GWn༱˰y(,p$G\hU}8bȤc;AkcWӮ缱4ve$䄑a@$9'.I &Tiey!,bF.d`,xMq𴺎㦋6n Y{py4 NJ/AIepHlaz$N1daavg rTd|94֟>i6pπMCK[VͫKSk$W%vJAc9 RJKhdF==_0j?`zFڮq-F99%ZYi|5+ ʿ1f*p1J/̾HۼJRTXob;( Lm :%[6"LF AwY>W9:zv<Ɍ3*tg-m ohq1gހ2-f9 TV*)GVݳnqnhqȨ{kQ=h9 rOaTb>I%yc||giz{5,r,BmS gTEPEP/+#Ie[>cY:w֢:vmci!c$Uq͒z _Xjڦ2;&ZG۵Նg8 ǽPjw,m,Z܊$HW8'(Ym$14|5SRQ{9"[Kh^1"C3q93޴jM/THcE:M#Ún+#KmmR2TP0\xk+KQjn?;Qa$*(w3'KNΣ2]1 8_gli#F˂q~ m]ΙplvPkźGyg2 ;z袊((((((((((((|E,RTWpA+%9xfdԫ#ҟEei>Xyde$24Q=z~d֭P.aswAoZY6E=y`85afD36>f->[׊K67/mw q/W`KY RH2phf} lvgw,Y7gY xoc\*J $Ơae8ʀc0*W_KYOol 1y$eEy+I)o7Nմ-KR=vXT)R7W%z”V_oN\^ BB74TM$:|Cl~,-9 m)h*1طS@Ey5Өi2Z_O6"!{SPXɐN2Ս޶k\]i&J,r"="Bu-/~sE}p|ᙙ*3t:]|+zvS+U7u Ì@=:|GZ.o}bY" o TOz񖣫]x=F -$W8},G4j+ԵuLY湋l-n@f 'cڹ*}NZZ\i$67 #4ear6#袊(CE) 4U9'sjU$Rڜ8ͩ1-?Zhi?68ApalcJڢ(( JZJb bq6\ړ/QEA{p-,g+ʍg$Tz]qߟw^J68P r=:fV2Þ$M^!gyw?S㝡)"oI2w})VgG1Niɹ#٨_ k/ifiSltnϷZگNT-F + ǥZjiڮ۵ƛ{ow C$PGPYngXjw),n#E?q?ϡ.hQxPOˌvq~mq=dpL;=͟z =B/$UҪRL|PIEQEmrмo#=:r2:PEU5ta<ܺ {y&l{C}6dȼ '*X7w)8EUkFu!ۻ*[[0BMȡU/};5  # mFzw .Y%Wtl$Q:h 31u$4I,Nee9 B :(((((((((((((((((((((((((((((((((((((((((((()M-,$q;rhUQce_/'-LlIUU*kKWtY*($n"2ΐGc } MEAo}iw$]A3eX P0֒K['hlC UdFMy1!wV*ApAT6P]i5w2P*k w[½d(M2Q).o-I'hko%ı jY䑂I<U-5&k=R : TVVqkjT1s5w<2Į FFpqȵo-M0y## Y((&e&&p5hĀȪ{#$aYI&%bF` HQa@UdI)(ʸY a9@\Vh;&hc7TV AhZ( ( * [(ėW0‚OAޒ}Bxi"IEP*lnoീ A=963@Ȋ '\gk[X ŤAi#cHSK7pe59'LFml vk=8YSq,TPI$8MtVuK}]P48o<4BenO#88יizXO宗q{2,@f3޳q&ܜv]+ 𬑶1ap}^tAk^^[]"1s۷AbO}_jng+`êOn6z]]_Emn$A=95Y|Eܷ&cA96|L? _TޓMhd󤸷I8pyTp>$MJauDp$lmb< P८^֚g[6%чʰGd"_'ڧK]n^o.+ bYFq` cרQ\<#XŽ H0LwsuMgCYF T1Z(K?t xi$h<1gjΓ7WukH^F+J'([XEgMݽ[$r3-j񮘷Z5˟-82G=E0::+s-my $jKee=*燼@"7qiְ ot#NH;vlQERRPIKILAPN/=A18RcE(xwQ?וCrn1YֽKć;I ^YA=vH%xK1%x?M烚[Om..-f98^P4_=5D1[h8qwZmdžZD`K-%ON+ՒxhNտ_.1ggW;/ME?õq_ vt+]|b=.ݕ[{9vӲH$n `( nkkDֿϻ3e Y :;ԭxVte w8o',l,OY5UXv?QV|Մic5fU&^wpyGK:|Lm<4>OvImm钐 P{[[˒@tWvZhO Gw(ZkiFcj%I>ۛ5a|+=vך}pwI+6Z-nhUOpQNpF~Ҁ9_k_m@GP_(.O^^-fl[]' #gvprz]#M`YtI<*|axcҖ]/O[-d§M~[뚆,r&X[ݴNZC b諍`縢[4[,!sewn$s1Nx;ibW(}FG :Ql¤t%I%6I9ɠ 4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEGXM%g%vT{^R6]@H&)b8+x6iWQz%1a %Xu^\5ԒjpiPCBz&I=]eZMioXCm>iLEEӭxWZԠ!3:F *9GH!Y%Hd#\ߎ*%^9da>w0;OS@KxSbk4K51nߺwgNsuEAͮGkc`yjTÞ6\ilnۂ`SFZPc*pu!|QMzصKr-Q#{#Ssޥ_Eص$M,Ƥ̥d\qdkM粶hI$J? W줽K׳kVsQxo훔_I-uoN_oz,etM+Vy\07ݐѵqZ@cbӡbR@pPrXl;F {}61 WL焫76ԫ$x*۶#_mQJcA#&s]YZbccn$WO4ZYة[;H-ձ a(Q@Q@ǏRݼ/+LI1*|Wӯa}"m[UD%y V K-6ϗMqgSó *O-&8JnF)CJ$vਕ7TNGvAvРB8Xܠ`$0opAE j#E `@jCDMeqi\MݷH{m~`PvדOa%:[cv1&'8 2Oα6ڛ;/)v+@Ao{mtO+zg&4(((((((((((((*\wpA+EQEP6 cs(UYQ*k_q'%,fi0J8$dsj#^0@G M,WKo j$Qҩ$5䱒{+u{H 5Y3=EmkM̑3} 8qTtY7> ҧ6pJ1_+~imdY.Q6P,@Ȯ-y4*1mٜD~Ic99wE*uR0GG7h{csO$|~nfnc[pQ#PBЇ> ۼwuwܮ{Xx): yanɯIHb& eC IkYa6g#Fەq+PZi\MypAq$_ftyF@lZx_Q:|?K1{ə Jy%B]φt{&&;Y]*:TB^v, yf}kֶ:A|fQ`.`KV 0,#Z7GRh^[epFIXg)33K "5ڣGA{Ve]Z\DIhd?`J#dvдuՖ7|Ǚ(8U$hLG$f[ d,i\0fMcʖv +M&ݎK66mvvqyV"dd穠 QEJSILAPN/=Tp6ƍ**1J<) fgYoqדß#QlM+֌KUb Y5`:jpc0z:I?׉?ozg$wEW![VXeWUkY2:)|=#&hn?fye2Ð{_ҼzeVngJiAB;"z*1J<+3Bz*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z*1J<(z,S#p ⡿ ^((OJJ׼iCw" Ei # 2}M6V}[G  ^Nt]?G~oy fڣq;TdFk[{ so *чPʤK->S^d7dl_ctY20!Ak+W|+cVҚ qB3hB%*zcPk Ȁ߽E'hOzO'h}?}>\?}>\_`fG?@h_`fG?@h_`fG?@h_`fG?4v'hߓ4ERߓ4}?}]4O~O'hXi*#٣~OpoPG7XcvnO|U 6;&ȱ\,%QHe-]פ *xL4+LWDBo-7y1_c?ν/e&?]Պ]ut[/h.;'}}݀'ŌWVgI&IӚ7=r ⹁&*ýI\qH]M|"*}ԣQ + /g-o<8.}O6@sԜoYx{8uvwV`ӟ*7&$V&SEq^#m//d[XX+eԫ).8Ӛ̽>7:ŴVHph>g|;jMQ\u {N/!}G+ >[i =krN$ҧH., .F@c8uvQ6yeW@l CP( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (Z, pHb xv>,Q\~%\gNuYvۜm^nSֱֵOo,4F@ 9 .{FhV23}$ێ$POEd:~ ]v8%eՍg[rStR'q1uW3q𴺎㦋6n Y[#ѫ$EPEPEbR4Y%5O~}j/zisf6y2KW z`' *gKo P;U _ꚕ4V[BNbAEr_]ixnpu=DΌI;cʞ; }Sf=^QK:r9}OA._Ub)c#v9-`\P}-ಋJf r@>|d |3]x8|1jvֳ}5R&[0JsZhS^5 =*I,Zܻ[br8V /U4y. InD%2@.Sx=k8 jƣ>5]Xm- FI@N7H=K _j3jЭE'[/aNk9KJ5/;R 0M*FfןB2A'Z]Xhwf|xFxS Ҵ(((((((((((((((((((((((((((((((((((((((((((((((՟I4FKrGaТ9 Oj :.^U.[y9TwK ķ3lhB1 㳢9 g\nZUzMjm}0QlR] j_:mKb#nX3ꆺ (-tt;8Pd`Faךe։2%[nXD_瑎)lާ5@LjwP6M& I%@~Pca`w=x&kzݛtStl@q9|+25}:{COh>f]BNHIdASYRxb4K8nFW)bHBnjYEbj:^fEY60TG ҳ&֧{%q-O% u,ĀNK18㞶t}#SOD.[G {a,X$9 )q/qJ4A5m,ڴe6ErYWn$F9㑊ݢ8D K->O&c79W,"]2N5׆5 EٗעIw2 ^#8n|3{5s[uH }yD+gzUwφZS >cIQ821ӽnQ@^{ohpKXZRI:FGJϵx^5Ӟ{o[|=#3r]ErxbڤQ<1fw g3д=CV̿9[!6Ʃ3z*ۢ ( (0|YajWteivnV CWQXjr(pr "#^`dRo~ʠƾGX}vGL*/itY[kh⑐€gg5Er_gli#F˂q~ m]ΙplvPkźGyg2 ;z(((((((((((((TW,E,@ (Q@Q@}7JXQ-,Ą7,{Mkwm}l6\A KVQ袊((((((((((P1r@⦢ O֧+ 厓mrpU{p-,g+ʍg$Tz]J5曢Ëa4+t?_ ?_ ?sjߋG&lw3q?¶KJrC{tP0%\1mKgLY?57]:M"˱2,cP ۷|zg"5י4vQd"2Z(((((((((((EPEP~qߟw^J68P xwQ?וCrn1Yֽʌm*Њ0)Д7z;xm..-f98^P4_=Z\y$۩~_ZҦc-n\æݢܟx? aھSN413sJ0?8 zW֦\ip2A u q~u><kqog.48 WvIm݁9mh׺z@}`]bg烕$5tUa]Z\ٸ#8>vĺ;k͢ FCy>r?.3:s\闋oe4* H9FfbCsH \I۫h~e*\Ggk&˝CX>KG s{Ќw>(2I]*$fxj!ŭԺtikb'.@  Pܓ xRFe} mN) s]miKaUh^v`瞝j9+֯VKFմ:Mn^)ƥ6nMw\RMN :(i9`T޸爵=ZT1%FBN@+d4Q\h:Aj\s[qaCq {m䑚nO5T*c`pwxբ((((((((((((((((((((((((((((((((((((((((((((((M4VHƃ,=ɧb]6LWp Sycހ$x6X<8}N;hdU[=0IpmүX, nKb6 A@K*V-/4?DkK{$.ҡ* 4*Mz3Ҁ;{kKWtY*($n"2ΐGc } ywtBhY^~h<)mb`9;Ys,ݲe=؊LoIS_t zPIo}iw$]A3eX P0֒K['hlC3}-x{pɓv9[Z~8L`V8[,s3kzYnd bG]ՊPqG4qZMyo̿yT;95}Go6f,WQ& ri4"r<7|jЛ肃u6Ҍ<1A'#u=VY%p?Ll#KxRbm$'|I=/외Ǜv~[Z]JmaXo$L(y3  1gF $T4y,K+![$7ƹ[[Su{};[OK,# "o7( z-Cݼ3[GqS,JagLQ!O14*ιܣ0r;`OuxYlQ[rc¦>iLEEӭxWZԠ!3:F *9K< wrirkbt>+BVZG][m֏xfǘ99a=F67Kkb֩-ȵDv XyL#ozzRjW}bԒ42A~ Yq1y5)5 4#@n$ESܯP9I% RI1+3d B; BlE7/$ߺdl=Nz2&OpKY.YNhI莡dI)(ʸY a9@\Vkt%U4y*;*8pI(8G GW{}Tk҄kHɜyC@EM41 *+Pv 5b&~٩=3@-Nh+:> 5+3Al@Bӌt((.o-l]\ W =OzI +Y89$ }X=Kv2D]&\ $g_#NٵmV! (AVn.N=X(my(''OYBc 9d OqOk7IibMd;d TJRd qJ@ʻT{  幊w F=x<J]//^U V+gR:bl5Y\G-W8q?7m(lqߘ-E:]Ie#%DVݺI#=(((((((((((((*\wpA+MJ;,:tHdr>?zdr:U( Vկ"&]2, &1;lwnD2D 2+3ޝEy}.|9V-]q¸dH_jkGy,ws,_5+2!T[JZpgV#<ҦԴ+]WMO[&$rT mb9*{(-o&nw ǘNb#6]; ȭ{zgqe?1UvrDVzO}avmb( `[UŎ} qMlHbHCq=3@>gYRSifQ9 z +"OYh,# Y :#8zVQEQEQEQEQEQEQEQEQEQEQEX((/ć;I ^YA=vH%xK1%xRhc8kψʨ` &U(*3V|gҬB8Y5D1[h8qwZmdžZD`K-%ON)?]G+r yWMTJ{^4c6!Q7L@6Р J&IxZ*ZZu"+Pdg8t4]>'$C=IU(Z6u&72'#F}Z%1¬#@9$,F(+#s8c9>f6rͦٱn O=}j[ =B5 Vܫ4a>֬Q@fl.gy쭥RIG#•)/R՜ĥzEY+qZ@cbӡbR@pPrXl;F {}6P&ٮbkxL)H(7"dǵ1t$k峷l6%-@Ɵd,Z@-\h|[+rI'Ԛ-l,TȆ0`tb((h^iiwV|l38UžgiRyh7vGzVvVBEcrTi1D*Pokf4my@)eԿMg"jVv:}ɻ$!d u[*/Mݜ3F=#_ҏ1J ⹁&*ýI^;VўicyS(1cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(cyS(Q?L?G2c(Dv.͍v3/Gk4e ]eVqujA xh0@48RydȮ9thPV2؆Ym0v qUK5JK9c>ms@MkZkǧ6RYo 6FPWo@\'wzKu],j1Z.w)6Y]A:PuE`C}XiW[c{Ƃ"CGx]jW{-{{" s PMEr?&ͦOu4peʠڵ:\I2[-Ԯ!#n(RuGpc ӟPsϖqFG_G)HU|mf |H@EoY:}*iaX^('HL02TYxÓIi%m18(=7c=.k5eMX}fj d-|8gu 4TqA4ѻdU`Jgv8 ԔQU5-B +Ov ,NTw$5WF<{Oj6yfVVܧuv Z(,T5=A'@ (q_`ʼ'yiɌ60uLJNyَ@TrI{*|'/3c^(:da7`0Igc=>}l%x( QiuxYoU8 GV-yWMˢx$9>A8GnnO<kvڌK`g3^g8[_2J|&Jgn}С^&O ë++v"e"(N6߻h jk*PKH ovI{d2>c ּ֦[ɣ]~_vI>P=q}^ͶY6p2ٞ5moW-ΥWʀ[@]21*YqU?szu͆*BI1Uc.7EiG%o]$rp6ۈr[JMbnˢ?i| 'fpEi -ynoTP*g=+sJcE%Zi9<5당` ܘⲎ՚4'M22y"v>|ecyo$R Xt mKa]H* 2c Pl}*'<$ }jGͶRZowmw)$5(?jb;E*Bg?6zuMq7dQ(&B:C p}t{2iic"_<ʭ_9Y~Y/\soQzTmݸ797WwVc-fo +A\d`+kEYS-$X$OvRāظNkj/~.մԎ+;[u (iGN3֫{7z-Zw{qn`刌9 $9';T&GP̻KFrFÁ~]Y<ê^>ls B`n&5)|E1[#I,7N 6\D)Ѣ9Z암,.q.J#Ɠz2BMizkqjYi~md䲮ۃ)PH#s#j_QImõ,H8K& G^"oXU01''"D K->O&c79W,"]2N5׆5 EٗעIw2 ^#8 R^j=V1¶Sbɂ8M8^~aPd}ktC)6WUhءS( :7Xj5TPN'B1qץ^ex-0M&4\s#;E/-6@ڱZbC]C}i?lKg,k wgU^#]Qͬm"]$,dʮ0OAK [TҾæGdc+H3{vTlNōPKWDI'kR?q{g8W%T9sQRIm.IJI;Y$b8*YFVSՇbVA*=L/JMeij-MGj,2V$Ey8di5bK"wP>V4Y|×9WqGwK=-c-$qrTHpN2Oaqx<-.Mj xQ6 POq@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@` EEPEP~KbrnQ2awetdȊ@u9K*KY.l-Tf }ܹOJ먠=OQXio}fnDKuc&U)pA5fMkصKuĒ8Ȳ26I9 @ҮM3 c.5!- ! ;Kon%@.}6eRwb#S\ꨠD}Q;km%@Qs3c,yV5 _]( ^BYXgŠDͯψl/.E&܏-Ag8g+;}o<)pdw=jvC$E h6T=%PI,N}LeRn 3C('c֩h.ZqPJ8*ddgҺ((A'S.\8o忧@^!sR$N;X[YZ9$Xc;N:z5& #cd.?z~>X3jb'ӕ/9G @F|5= h$+FHk^XxVGK ?uoyɃr]^.C`#.rF-\32pu[}CS[{>h? A !Ha$xx\]]qq46ȋvs'buxu| %gwox]o=֎A7>ɰ0v zϴ̗صmA\2N bq|#\;nPhMƤмX_3ߴgԡD>4:߲%y1?{<溫O XXã$\*R w.B0` c x+MI by7s;sڀ)Amw}S[y5k$6ZK*%0+wtO :Σ{ KlpUX@xPiyd*m'س07py8VX𭎥u\Krb*@En܊Y--GX8д'i]쨜RO㋫1rot7o_&ݜ+siߴhz'ϪK3N7!_P Da#YYK~rZ4lFƈg?@:]0$wX|dR |y/n`O^s0%XE# Tc.>t'^ ?ZMLwzI$o8s o o}Kp/!"D(@}ݯ@l!1};֞wٻ+cGk0"Ðjr^Z_AIy- <VT){||&'OUEfZ}%k-VuoTeUM&mZ32] @2uR֧׼?kh}K1XT_XhZ-&˜\ttQEX(( ہic=]Tlp=P £'JӍӻ*UƒWaԄ5}O5U8O t~"j.6x8+͋> IzH!#vN<9zsNMEbcY}kK3Je#p}|Jr7 nR5 QU/5,n,ɼ3lgӄ4kVkDQb71 d3PYb>!7t]%9 IϘZQQEsMSG#%U`J68 "((((((((((((((((((((((((((((((((((((((((((((((((()M-,$q;rhUQce_/'-LlIUU*kKWtY*($n"2ΐGc } MEAo}iw$]A3eX P0֖+y"XYQv5,ɿO2W1#.X(8#K{GVp 5#'NNMhQP]Z[6ޒgOwvp[ZiO@ =hP0 KFMoI_˪Y%bFXݜg {ET];Vӯo#Ce)%IEw%7\3$)*O@hZ]l)>V([)%pZ0wp3Eyo'lN\Q9hQ@Q@Y5)5 4#@n$ESܯP9I% RI1+3d B; u %IM帎9D.UH :䊳@TQ4CѼ% Gl@EQEQP\ZF$,4z \_ZZI w7PB6ؖI(z)Ph˿bxXنT1:ޣgݵƮx>LY 2#״xö́wm# my~uj[h' "iH@'+u.n|qn#&HgР#\@Dvb;zvb)pp{f4bw\NO8<V[S{"'*jdd~b*+Xi,TvD(#±Aހ=6MJ( c1\Hw  u&_Kdf(I sӝǵq:<6 #}]qJ܅@z$p?MN/-ڳB/]C`Syy4CuJM1i8 ޥ(ֵ&SKw'{ CeFjZcOso >Ife]91WBq4\[>| Ҭ1AU=rWCXYۣ(Y" |ʷmX}?~oqZKo<ٟ9,c=Vh+ 70ۥڋ/vBw [/{rNbƘ=0x:$5"Rt&mw!$}MtZ$wͦ= F!ʈ#cڬizwmD#zu#_]xj6;%&wi{l#pݎ+m! pn"b8q^8``- t8h#$*GUpIEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPTut}2[_pbWi*NAqzUl,+cmib0HpF(m֕}ugkr]=mb:YUZiy%Z[I&w PL9T'n[5֖E6͆;dTE恣j ^)fGEp:zPO,% ^ΨB& `2Ϫ],0I Zs݈E1I=Et1N,J6m0rr=趰]Yc B\WAvwrsy$sywrxL!w4z3pq IJzO֡m/2F 7r}Go6f,WQ& ri Xu?X[x c)r g#:U}T($l rjai[yFh$P8sa@_>|q[I=z7uX.%Na$F( TsdsYiZvEZFr+ SQai/BŢ 9%W$(#[\p[MBBq1RӊZٙ3 aT\JzvWK6cr,v#\IyҠд}:9ҬmcfX-4M{Ρ2\MȥA1ExF$n4x@1vC a]) x@Av F@+t՚yh%R8wHcGր9>%4Ć-: _b‹,\[c=mf{D.>ѧ}J`Vy=֑-gUGyn Zv X3ÆM-N]kRP9'$ҽ 9d"ER rO~8OGGK;uxyJ Î9=NOq- Lx#Y,$Ż;~ݞ}:V֭6ۭ1<Prs{D6rͦٱn O=}j[ =B5 Vܫ4a>ր8F67Kkb֩-ȵDv XyL#ozzRjW}bԒ42A~ Yq1ym6xgYU$+48)_O/^ݮmYJ]GldPj7e)nQ$V85%;e}AsM4Z{]pߺv;@vBNWFoMiN1I1ACY`l^)1یcڀkagbl VD1 QEQEs=Kv2D]&\ $gmOQ1ΉKfm=lvWah^iiwV|l38(tcONm\CP& 5 Y4W2a+C=)k/eKhkږ^ $AtRY'A 0x$[0Ut2_-n;dDWm-Otw Qumy4YCjf7nn s*zk\\I 8܌Fqi[Q]gH6 ;Q'd2"G*ـ<0WX =kcqt{\a0uc]G+r H4Ctʯ / dduR39VzFMёhou"dyh|l d椢9?/chd$&Tڪ_kscYi-}{6EoEC%mC ƽV|O-4˂ 8BP88 0=j7:6kE=x^Jr2 tWFeu&%,F; ҳtxAkk}CY^GhRA'^Eyw?ZCciYvAo"c> Biegi=yF JoC೤^Z=ϕ3(F@P߆5MmIKym-^`rȪccxBӼ3 RK9nGN@h.u RX[y ~btY,DXӮ-9,3EmI`9|wwZ}FVc1m^AU5a~+)ƟXrcJƎz785ukp[}un%ZAגyQ^EpzjŝI8RĤ(WjMA*ׄuݦm=Ϥ87ɹy+hPDF !B$<].^W.Q-b `v'k85M>n,ˀE8zqPAjW#;5#ě |JǭwcHh:vn¶s[wG'#EGZ6֓F-hՎ܃{ ݢ((|O_iO,K'Yx>kZ lWko1v +7^Նy]Q.r{ 7BQyoy 6ġ@W!YZZSpڋ I!QFsN=@Zwo.uH$πF_0@U~QwQEQEQEQEQEQEQEQEQEQEQEQEQE>?#X*z (Q@Q@6!6[HtxG}ģyIIbuxC+)`zjKbrnQ2awe#_ZʡyhxJoEK{'21CʏnJmż:$h,QYe## sS=/lu[x$1hR[ Qހ3ε]ʺ|637[Vr־⛍+_lⷉX# H8A'9 jo$ն-ԎD+6aҚ㾴Fs$Sƶu*8]Av9djzV V9| ءzzqY4nG0ڽ@2YCnu"ѼIO5Մd_oy}p 8:uuj:f%"Hd\~}ONhWL/fwv%dH˹~ >AfKGu$q/(1y5|i>%MV.,㷽Vq,DB;sQCXV_Evzm̷1$:GZu/5hxcEqsyn;T+{Wkk3-oJ0Br ֨JMEšjs$ yxU8-inq |pxkXJg K$ƻeQQT=+:յ7ͨƐ4qЬe Hzt䊖Atƶyd2\J|8QҀ6WQD]"tW&ΰFآѲ;VzGyn YTg;y==+F>ͮ\a)݇9EcX;;tBģ'9@q34rBѰ`p{T[#%1ĬơAbrO5%TԵ4>[ˍ#*8UQܒ@]WPm>Ũۑ[yVY[rFqګxJԒ=U&b#?42y8F=j멯Ũo Ĭ/kFFyn=((A'S4(3j' N8gk䓕^O5,O٤ӵ.I%He3㧡s_CSLf㐷GrTӴ=cKt6.-Z $G!b#1Nv,4ŦH;1XLa; 9ڡZB'xCYq4[E-Z_L-|r+cγh-R&37Hr a,S[ZY5BUbI$ryO}TKy:j~MR,ۉ 23]xLu Hd`Ē[pd 6sׁcLu N5I ! ~wadpq]%;Rռ;uih^&V@F26dqw6~WTGxTz̓2 SEbiZAeׂ#GIaqkú}zo" 3ۦi$c@jz_kh` #|ŀbHCqzk& mC g XluP9Z :ѭ-9́ej029uKkAiSCzm#kV_>)#6r3;@QX~%&; ( @AA!6!DLx3e[LM$TWmD)!_2Gx7%:`m$#[)@N f;+ּ_Ek=&ݤ6k(a * $QҼOmnѢxcfX`I@6w$ڊ`Xm=0}h\8_#;J+e$0B&Y-5n9ہ؜}kֳa]æiYďY6px=#iaoVw팟<__s@Emp<4}En$7ُ3F{5y'"]ߘNMp $ 9*ܞVqdXisYrLh&~~@jRi/Zz3տQ'zU4+ѵkPmvP'OAJZݢ(BO֏-?Zbb+k;:MIV,+B_H1g"k~j$Qa`rvn_m9}k'_sm_W(!B,bvH`湻zl/%ٓvJĂ87|hzRD6 2*nbUڣ8'aB-@Cm0T1O8 KY3ַ[k\3F^9/#VR;NA 7 uh&9@nRx8K\Os*j HPN1ʞN:zVxmܦsA}j][,$4c@27@\O4׃PMIMۥAeR=~f sꍺkH܅@@?[ҢKwSDpJM߅M56EtP~ _|!&aZ" ݸq6eWZ%ΗyjI9 " RA:֙A`Tw0$m0FӦ@zwN"ZZ!9pKcBxV-`lQ:˓!v|>X޴Tm.=LƖRF,r:ǚt#N&c]l`9 T~I)''K6pa!2r~APEyo=Ŭrno?.N> P#\"2۶T]C6I/|)ʤq$ x`GJܖX#E,@}(X&HPrAҀ1o|)a}*Fͨlm߅߈aN4Bw. d>VZGy@RcxH#a hs}>-i"}3ZqxB Ȧ[ƷᮡiG gH&wMIspTy1vjQEd P]>Սx%,nZ/,4-CnaL}.QB: (,QEQECZ80^a){tA?ʼB.Mlew>^>by2XތߟHpxRFQкH!sozo{vqZI5Z޹`GF#<Z)E9~=VI(w;jϫYW[Ѹ>kY*bOj|sVym,ig834FRLu8N3^822LeE\ZGXi%#WjkȣkKi7%l{dcI a#Ab5%–ޥ=Mrkq+mFF6Ud=+sz wko-q!Icg;D*#T4..'v$vHef;`z U̔u5 s#!PPd9{Cڴ0V&<Ђs?STQcmU P7|9E>:`ۂ6Ake{;cip%>qȜ`NsO57W+4rhs\" s@,.彊Ii S~^~b28a? 1u8Kpe]nlaJ?78ǭr׀|<UMzm98'gJ erqrxՍoK"2o̕H V 3⦗Q+I-㹗\/*'&zO^HoŚ73T7M3Ɵd].GO-Z}PnQ##$`]]Xg$GiqIsyo L@T9=$X ?7q6u;|ֹ[ vKԷRZ+ Zؤr6#o"=&yᵷX5,HUTu$o5Şeq?dcx+kjnokzZx vI9$.GSY/"H~zѢ"ϸ$V QIABKYd+}Eh'NVY%\AF}1\߇%g[k;?L7¤GA+v5R9bvԲF̊$7p^Iåvw mb1C֬Wx1Xѡ%GYea$Ma;Eckh"y e\7:=ij630[;F%S;`FGl5Ik:;pbӶWxT8m)2hhu Z2gTY9 G tW ǁ.B5M=RbL[#ӥh\jkPhkm8ZU'9l0uW(&ZmlZ%o)mRSMJ/ZYcRfR2O.824ئe&&p5hĀȪ{#󥽿my('8/,oSlܢIlp>k|6Jv|z\&$R%6^Y[x劮{~:kKMB\Y\s $2S*z) ۽򽺖B7ϖsr@'/,w>BUF eN h8V5f{R%Tq23oCSPm4Z+~BJEؔ ^hҨ6W/D =2Nm2`*0 rq֟-OWѴE*IHGtcw=oT#߰wkgۈ8jzy[fnr%\MJ e+kk]Z+nVۖ!mO"f]KGfy W@ą Qy z;K=6]k g"zrx_MՑM-/2i@'$6'C<($vV/V [S:RvUڣPlL0L/-SH_]1zTyzvڭHJ\#:# eևy9mҹżnC`ێj.&K(u-R"LbMNpeA@EPEPEPEPEPEPEPEPEPEPEPEPEPEPh@i((R_5p80Iz{٪QM! 1[OWgKh|{ɸt;FP-SX:o7KG%1e{$w7Z/ ӊN-4FGC~ns00-KĞ"|l#(ܵBdp;R"[KE֛gIVoEWqAȯD4 ӭ'0y+lLC.vy]\^JoF1dpҀ9hVl);mI'9_ŸE.`Ju7z6qqeGn:rFx:=9.6;I%<62ǎQ좀.QEQEۋkmEܞT6LGE'qsۜO fi.,fX{[Y2Ƞl޼tw^Ƒ6r+ S.q O|- *(,QEQEx{Q?חrbmVD9˷p{׬jnӮm7mhzdc5m-uX0^V>GRocrz F _Oe0$?=>_Y,f On+]s(e*#'Mi-ãM$F^[Xcn|faR513fL~bf[_-ڔؓf\ QxfHRI0/0G$ޢ1-Y^wf4ckNμ.I߂IZº}ֳky-Ilmn>gG ; lEbjPm -Zu%7< l1߭h]i\,G1bW ی*YZG$R%+$1Q2Dp8TQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@WԭP xé#PuZMioXCm>,F(+#s8c9>f6rͦٱn O=}j[ =B5 Vܫ4a>֬Q@fl.gy쭥RIG#•)/R՜ĥzEY+qZ@cbӡbR@pRȆUc+Jҧ#naxg%$PЃ֠:^{Xڛ,c |[![Kdm[#e=H?W]JX#]2% \K(Öo^$7Ck%٦:Ό'f:w[ T* : Z((ޟe*-흽ʡܢhŸQWLjEܪ-F $9<{֍ '#ܠ` 1[FCHBڟEVα6ڛ;/)v+@Ao{mtO+zg&(((((((((((((((A+QERQڀ8;S'"ibG՛v)`q 9[[};[xt$RW$pםzީk :j"2BP>XՄw5վYWU@t[h2*rsGu*.c}CNBMlWb>P8(ey,V*TdQ01N=zbq] Yb^6V^A,/Z.. Vھ ')f Z8FA_ҡh!IbpʰOQ;#M;1cyS(C(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P(Q@_ҙE?_ҏ1Je1J<)P\x EKIE-E QۢX@1LZ[Ò\\ U(uZ&YeϠ ( ( ( ( ( ( ( ( ( ( ( (,5=A'@ (qޟ`ʼشAoGפgt{e-^B#ᶞq׏ՊGdXz}?m$«^YXeI"|ܧEM l~ªi=Vш-% cܚ⟳5Y&;9m*|q9"v0ߠ+!ݤ\sR:H~^>S2VMyWwy`fA"pC4U8 Vƣ2BX$"Y/6 ՓRId7F6!ip Z6P-^ךE Ku9p'`߻elF0\WIexT{Ĺ-4sh$2g #9+[}zh-kiHdAH z2ӿ,脳ujnd~*CD.czUё-1if#7ӏzұΡ\iw2= 34a,=^(v[ӵ"L.*7O)F hW5^hW2n $Vrx vVin 1MHcdR1@Ǡ5sX{A2rBH0 BzʓIYw*41M#2F0z;Kmmmb:1e8m `A9fMNKB[5KvY) bcq s ,o 3+[hl\ISg#o٭,\\G%#fU++68OJ:}!ux&=(cb1$⣺uZF(lkMGs $:P~5޵ys-,vKrw0`T?{}⺪?^jW aS0}4p>oy9NVR8mxfp s)8Zzlmk=Źc"0( 88Rj^ֿ{rC2-nڣ׉udK{xY% xy=<El$`U8$60=pMxFkVR03X*2>nkO O˟ 48gKaxV ūifե)+ʻwn A r1P|CE%kG#H JQ/t5Ixqc#kmW8–#-,m>{d߆x_|t8FT^%|sf_^$mr|̀c%xl)KmyK,LvX LC&sa6yw5fGL+%]U&UqT|3{5s[uH }yD+gzUsG]쭮b%.'%eVe~lP3@XxmF-b{LؼJn-lP' ִmAݵtdhR]̤z9-,J.{bb 8cɽs]Cqiq{Ga*<p2MoxHk{xd(+a=m<:,7+|rD мbESfp sghX ~ϰ;8JtG4:VF8d$p1ah>.V$~v%aRHTQlgOP*[˝F(d'yu -?ncE9swqWtϣ}=BG%I q$W WڻY2tF׋u`e)@wEQEQEQEQEQEQEQEQEQEQEQEQE `4Ph J(h4f (QEQEQEQEQEQEQEQEQEQEQEQ( U OP1r@#忧GfkP;&OO5 $g 7N&&#r+--;:d<1\$N3c-iaʝXc2&8 ׭)2@\?uY~c?n Sm죴a"z*|kءOP}UʪVE?O֏-?ZE?O֏-?Ze-?ZԻ8zG]4t޲&eGz.yx|F亦zUm8*Hc\nH$ gѫHlŠ~)h+ƒE}dU |:; 4]~\o"*(!R;8z J+WG}%BA!Eu@C F?g-=?ঝp)!XFCGEcoK5r4y6rtUW7Wiq?l3 -e1uO1WsEE"t :Qgk5ÈIU$B%4I,mC): ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (M4VHƃ,=ɠUF`l>/35|z(S+y1Y%UVLps@ޓy_:  :q?:KhFJ\:L0r܌cۜP*z{nf$OU>V:j-?Zҵc u;;7iM靤8?^ oK"2o̕H V 3⧗P;In㹗\/ EMXμ,|\d\jV7pC*<V's@(m$2RjΗP|p9 ~4zaXjkCl2[ʲ(nʒ3jSKk ݼ$Ƞ Tm*\1H#6pq*z(ckh"y e\7:=ij630[;F%S;`FGlEPEPEVMFMBM=.k;ۉT+GK{g-`,POAq@(iב+lgdPpJ#tQETTLmInSJ3NzP+1[VvֱZM$mXz@QEQU5 -=n푎i =iV62LqI*O?Z4["G &kwm{n\BvH:PUaؘ`^[pdc)'-"[h"Fu#*F(B((((((((((((((A(fX(ɦy5G@f>w?w?M\w?w?O3Pyy>hA5G@??O3Pyy>hA5K٣5G@f>??M3Py6hC٣5NIqѢ((q_`ʼDX;ݢ$p8S^x{P?ד(h2Cq̏/gRv^4 ]O#}ȩs_F%* N?[}r]|6=yծͧgvpEu7Fr8r[vr+]u4}FWOGR-%q]ND9 tGlZZ[PJ.זe&P[޹UT=OpyuZy\dto5{9cAf w*=pβp0)inK촛B(O@2Y>Q.x Y]ZCiE|` c2 ]la]EonZk[jU`"tZk[w$xcu2g=ōMxls\C,mJ66ɼacUojo:~o隄"4q->9B[iݟ+6bދX>Wan늉t}1SNQtnG>ŵ\N,l*L3'`=8" bė]ACmgnI*yAܣqٳ#޻ O Vgcvp9)hz|hӒ $U`HyQJN/P3s1vRw.mo4F-nN$ҺݴSIsosu۴e2I{r89_Vuݳ\ǧbxwQۜb9/<":`XMjma p=:# L\iE&w$fw [m'; zP5YR/~H1,æZE$&ۭ6ymNAÓpm{.ϛ{x70]U^OJJw[op^1q+T*Tx<׫GtţkKםy"`N.2`$͢6L~I>PYF.zq^}.Zw4$6zm؊AS~>u&ݧoV9#A/CG1PvƢM64V p W|c%pAI\&5Be򽵟bFߍ($WJ>ΚulECF0aRGD-vv-G#Ҁ,EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEUb]6LWp SycޯU{ =J_Au ::u_]FYܗOlmwXUb;3֬Z^iz~sp֖RI]BT iU 蛁$guki5a a$xyhڂBE@bYcr8>ovNc:.ӥy7VOۚc Fyץt#B Uźm9Q9犚;k˕wi.YPWߖ4ɦ Vrx'R/Zv ڵwICg<2,h$bʱ)~yuiP6v񛃙D>UBӾ6[@ pcQ]Ճ 9?ri:ًͬ5o g=Bfov8Q3[vnڍ=袍ާn'o=P\[$$MC79q oRU]gt=_iew\dgP^GoLO@O>J,uym d oO4.x`Z/]+U GLdGMqg]I[(K69\`s@eT5JFta%ڧdry\2=k*m{LԵ+X.eڪ.ˆDTla2YJ[2Y:>CEdQhܴ ?U(/fMZTqFm_(1p,A*?Zmaj4<4q\dz=>(`;;t ,@,gTcTƁkt.=2+$c\ > TV$OFU#8޺ͱ?G&*Tol=28Vy=֑-gUGyn Zv X3ÆM-N]kRP9'$ҽ 9d"ER rO~8OGGK;uxyJ Î9=NOq- Lx#Y,$Ż;~ݞ}:V֭6ۭ1<Prs{D6rͦٱn O=}j[ =B5 Vܫ4a>ր8F67Kkb֩-ȵDv XyL#ozzRjW}bԒ42A~ Yq1ym6xgYU$+48)_O/^ݮmYJ]GldPj7e)nQ$V85%;e}Asx9Tq,Q+62Gr? ⸉ 1ܱi1) Xc8s)ZuՂ\XZf o$*ьt‘(5}[Es^]$W*w`P O﮵/7b+[(.>Gv_h.=kд1iV67aH3wڹJ{h[mٲ[¦"#rgA䭼o䵒64Yww5lPֹlM6V )|zni%m%EC1,݌$rN Ops@RDL6*ٗl|ߜ q\5| u8)A'ंF|ΙjڱM[[s)I#ΙnNyg$΀!_˶~ÿomߏ+۳گE`x#ݠJR0F>"?{ 8 f0,z\f5@*'mbd^yYj{gorrh>"}2XeH<+$qP;6}8Qq ?eHV<~3,qKwbUnВO@9wҥ PPFwmO{[sMLok## 5nS`ikU4*O8#z TQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE  5Cop!V*Ðțh&ֵGxc%N1l^XOyt),lA61$b 1nNN95Fr_βʆhw͸$GL*(9WWEs:φo5;s$jex$py;I:~] $KmEN~z(P-P y|9иqǥ&od>W;aLӡ-@Ɓ!Uin<Ǽ0+ MtQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ ďkz~hb(( Zwl< q19 gG<;㲃D[aMqṰ!|*DOpxoj{:T/!:AV#}UOi1鍧iy9 Z Li`; J63Xs?l iuI'Ѝ64Dc\#č9ܚl-tEE BNI9$$Ig=xY%j.-?Y,׽]o \j7xQ,d*'WF$NsAD{˯4"&Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@zQTu;gcОP4n3K`> /Font <> /XObject <>>> /Type /Page>> endobj 45 0 obj <> stream xUj0}/vnA)-䡭!@ )>kyJ-d)3gfF;:}ETlA]WHN}q67?o3{NGЌbBH6_o>jDžIm&=>_bz|Q5Z48^O{~ڎP#&~L9Gm|5k C0ȣBw#+C]IHE  K_ƊX*")57'7y5B0:;4 G> stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ? M&W$385c6=dAO>i>߱SEY}}`bϬTQvDaX?أ6=]ح?(~OEadA+O>i>߱SEY}}abZϬ?TQvDbXأV=]ح?(~OEadA+O>i>߱SEYni`$oc+>ϓ-kNVNs!nTa}OJ4Îev@` EK.TS&[ cq֫VP|??T*]يPϠGz))i) (QE ( (@ EPEPIKI@4QEQE1 ESQEQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (;|AA\HFmC2ۇ8ҼWw] TpI#)I/Pź/fo=/I"ʬ8$ &#妥i$NeS ڽ2$; *O#o,Ly=$ϐVCrq߰vŗl4$[8̷f5o.0r?+OىFҥeq1$Jrq֝o[˫?&m$l77zѼс?3ҸΣM``L'SK?p5t]c'=:)I.mZc3ۃQz[QiQ3,VrŁտ _v҉$()(,Up3ހ6h((((ºhSC5%g]Gp/s_Zr-q8-Ԝp3qq.Ze&!`枻Nt6*H`7c3p.9`mA&: Nj{IJ/4,qF(I+:0MLZґu@g'Kc$e򡵞Sԗh#g%mn+;eë`"uS&q.簡0袊9BQE (@QEQE!Q@ EPEPIKI@ )QEJ(4SQEZJ(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE,ȕV'k=##8?箴o6i?fɇ~zO'~ѵhO7 a1뒫S*v}\*Ѳ^ÚMQE(t y^Yt $rY퐖''iekaxR}p;@袊O:SLG5[!'n1.NOzzӧt&SKm $X|0K?w4HF[7yEV+&޶k'@ѿ,bI&y|،>QMkS[ QE2NH #RI"Q:wՄs[nXY) zdԛGK"EHH]ܱ+*UѰ bào\qxW((3(((((((((+fPÑЏQ]qVZLq;jKXIrzSYI^\0BKN0PKk.;֠y^CoUƝbQEQE%Q@Š(EPEP0)RRPEPi(4SPh(EP 4 (1F( RbJ)hJ)hQKE%PbRHbLQZ(1F)hbLQZ(1F)hbLQZ(1F)hbLQZ(1F)hbJ)hJ)hJ)hJ)hJ)hbJ)hJ)h`%PQKE%R(Z(1F)hR@ E-R@ E-J)hLQZ)PbR@ 1KE&(-PbR@ 1KE&(-PbR@ 1KE&(-PbR@ 1KE&(-PbR@ 1KE%PQKE0Z((Z((Z((Z((Z((Z((Z((;(((((((( dQU'䈰'ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}O??@hix}O*d?ixf??Gd?Y}OTJFAA@pV7<&Gր-QU>L>LETo3Ǩo3ǨS tUO?z?z-U>L>LETo3Ǩo3ǨS tUO?z?z-U>L>LETo3Ǩo3ǨS tT\O@Q@Q@Q@5Ѯͤp5}Yɟ»)Rt~,!.YK_\,aG,cWO?_7}VЫG4x"cp{خ:NN3Vh8EQPPQEQEQEQEQEQEQEQEQEQEQEQEQEQ\x'-Λ෱mR >fv#4 `A]:h6\[IgN9P( )4,RN]Eb I lB 0ZEPExmZ ߝ.CmyV빏=q@=h%Sbu#$qР(8 {?>~R29FnEd%ɜ\sc3JxJwW襇X&tU 6I$J԰K$R+ŇG6"8J6fU 'nQ\׋Ԕi,upOp.\d LVQ@WgE麌5w$Nf - N2 ^: _qyiw { [Sr2r\9h[$Ϋd^-A Gd`|rIc+hty s,q*a=sPSESu(u&Q "dP@#.w5\Ch[/gܨ"8@{Z^/UI4HRqؠ+ޟEy2GN;@.o7,gPgEyΉyBn4-$4a WզV FtռҢy7-pu-jj ym5<2F܂8N ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((>h?Z=uZ7?߇cė3ZS<w4u<Ǹ O@$toE]Nw9FMO6)c#  `ԞfH췴]U}hfp߯>[3u;eLu*2 F.T0H W/c+@cXygq!9О5)afg zd֒$Ԛ2TV;#|"IlofohvQIpy'mE6ܣ{֐_2:߇Ap3[CJ?0ףi^_3D{w>aEWw jyOe`lI&PppǠ\fƫmF#œ @$o$pJus7tڣ>wfPo;O>ns@h48x)ʊY I'+ŖwĺF"C,b{ikzͼMB'\YfwQȓD !A)uo(fB3ϧ=:?uNBBg X\2P0G*}Z:Ưif̭#EkvB98=h)MZ)@T`pAPkmN)99Ŭ%ڌĥ~ Aguq$y%6.9=Ɩ#ѿ$=D.@@^GJ嵟 蚐Q!Y.&]ڤSj\Oq7$̌F.sksΏߥt(xFe{'.ҏ1%TH(11*Y<9 ?2ő|V l2xo:?~y{?*{ YܟZ}GGҏ:?~AmyGG.=X>-GGҏ:?~%fjZ A6#=usm 嬶ʅ2"eMfT]'X>EV<=~IOiMfe[ IbYr~SzdQ@eOau7F tn9Gx֭rH`92?1_aP&2X6X`sӭm@hb w7Ǡi`E[is2vP{Jmݰd3HTn$uEQEQEQEQEQEQE'´>6yT9&((>/6uޭCMۘll"ƻܙ1%ŠmRQomghUV6 I&StE::}n}K=wHShG9B=pN<֗t?\coq+J$gfZ\hsSܽFU f-q nf $* 36E-L:?0ףi^\" )hh w,K}_ EWv (-6fu0Vǚ<i.eVoi0.#%+@P7WA먠, 6C EpRwem M>Uw nV98GZԢ8[wzs,Α4nJaN 0"/tkXH5m, .cV%oQ!+93z P7ʺ6Z G2| lN1ҢԾu>KǍ;TM#fz(;\WѮpA"DҮB;)P{VׇIyaiiqC %E">}9 [\ѠbI N:S] Ky@< e+oQ@Q@Q@[AgmpBv8*KEW3 QcS$}݃-̍ 8OVS種奇2떚f>m5/4My]1nu4KVno q}t.($.qyZբ ( +/uȟj?"_֫r'& :OӯDQ7(?H"'Zg 特EaENG$_?{9o\,aG,aGf+?a?~?f#& ?f#& ?1_wW 33Q 33QO}߃#YɟYɟW~+<8?u6kXRFFUuǚlibU|wfQ\E^+VRetl20"Ri_Iss$JUI0qֵSL!toWXp$s}*G;ȭLO*3`򫌜!hmt45 ܉QՔdRt )ך423n$QaSdww͌h[{oǑV2髨l|qsZ.-n|"nO )tʰRKc|txXn>6|ȕq#8G3Jw(l0Gu\AyInۧh$dЊ@wcqVant IiqȿgjU}O4s Ŕ&FN޸]X>|w {&@>x}9iSML ]5T%U-ӵ OA}wizЙ5&e<8#$c޵[c. dFsO=exz͋HOK]nJpsB3.aj1^wck.a=$Vl zw2ؗ:]a7l-e,7q|c ٽ IwG Z5Vf8'k E-Rb (AcwAW,`~j++CŪ](\b ?eZ= I;#GAB(oEW!QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEP Ԉ8"_׈Wk/j/kk_s_>Eu $ij4D@ಯm> B4;=+VnR!G&~zJ#[o ԚOc&;mb[\At#O& 8n|/&@:٠zoTP'=*x/R]i3؎ f٥*p_8\1^3xv;d}Bm`%b2n`Ud<ȢH y•9kiaLc-Y~!gK\(@>BWi{VK-ǧH$4 @8sXQhmOnZImdAkEUQB@pqzw ~~!ݎ% 9Y3cӚWmuqmMնcGs+ǡ\yos k8ijEmd0p:;W"/lacuf.mSh !v1#e*H=S,D˅3rN2I1OJˢMlT{ZoHe1on̓BI =,!H-eHGZȢaONl3ixlBG~+2~=F]JdTq#*I=S]?/ 8$nt 3rWb0ڹJ!pk}Vx/_(SN"(Ô(ͤr`yeq]Ʈivqk>EvKRQqijsG rk1rh܀rBG$d 2y*zE+G8iE\Hî+щNM1e,sJqLM״}CQ涳f,H`6 gᏟЦ5_>M.'ٕ[U,0{Wuú5S27cT񎟅_ԼZDv]GoNDAL0f$d}txC,UA 0iu9Eso|!.\ "X23643g|Cgh6 l^6yB)SZQdM5.!Iv \$UQE1(P1EbQE&(-Rъ%QL((ڊ(C(M_Z=ZOb*iUR $sj8uQigwa=ŻL9\%[Q\h/~$jBK>pB 5I&-%)%(YI hj<]}ۻy `1MB uťO]-c9 4b08rH$vΊ?5{.63<CUa]-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQETSyh(w1=Ih$ 2>)XGvcrI\+|C_'IhlKqRVh3 7cufj,-:rX]%Vp$(dgYR%O&'ogI«(zV>!Ӽ;{tZUUUBq{dGkim,rI9K$i,m2Â{ZAԎYT4qbbWq8 9Oi.vVBnUC|@A^C$KFR9>"3Hf PInNHϦ@8 X7Id]A3n3A֠[{;hm\(ϠTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEq]JW"dC- gh'zU.-/^HI\9L8z()AZl-=. *78qϵZd N~Sid`ӄ~{>v-b,N͂K!5?xz-tmvMs\6Bd+ FZb1[B{ y^xI!Qp3 ~QBA;x(I5y75z]:S$;#MRK];Uy]X|B,ms}y,QH` `do"/-#[08rgSPhx{i.!Ke}CUX@UPzqfٳxd0Qϰ\iWbQsgo0vJ$[zG ȇׄ?lq1$Q"h(P:; uQEQEQEEsʥ #d9)xT}Q$7ռrCp!H 0W91MC M6c/}=Ĭ"027<wC*V(5$@ S(((՟B5-5"3Ԟ}h~ ao5NUN ɪ^~"$>ufj,-:rX]%Vp$(dgYR%O&'ogI«(zSHgf 6gY񀨌YlVYZm RHck9l$x5fC *qN 1˥|Aib2 mchK X!=7 ?Z#=:וM5+q/ oOԍո|Gw8˱ _3Q}MOQJ;"`e݉U'Vx~bQ5IUU>NpMX^m PZ1gxp(A$c8Ϩ4PmiarlH+Gq^l==2iԼ/=d p3wPKFO闷gʋX,Fݱ p:3ןO[ḭhle,"C8&Mlxt5Yf8Xxf<ŰA0OO* :W|wʘxc>mc7q^Eqn,o%  |sȮB k&%ӯ^p, "*$%݅$ҭ-T\jKbӾhb7'^l4yGhi2]#%1CWGKsJ}&yjorKF63WmwmiyeamTaFw76Of)V9?tYZZpeo%\GTF"( Iɣ@ }N\1dУ V…[Бn2yk>qⶩQEQEQEQEQEZLS>,a1,7yjzں |]{]",Jckʍ.hnٿ 33Q 33WE{٘__G/;YɟYɟ¸[` nPQ, "Y!;s~ gB?ggB?g^U2'Vl4-ӱQ̫noGLGLQUO/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLGLQGf?aG~ gB?ggB?g?0?_w????pQO}߂#P(P+/~/,aG,a\fa_7f#& ?f#& (3 0#G 33Q 33WE٘__/;YɟYɟ¸ ('>oGLHz6jd!QCy($A3ʰ-(0ۿw:-??E{?d|G!1KEuEPo ,Wq *Xd1tՕ̊X=%fadI}î KimPcWBѧԌAUpo#Km+dy.nbEDTn@Xz?j? AЕooWRvFͨJppHjq(}\%\1bh.e$l^Au,Z}7^$ \!$AA*XE/1os;IB tY:Hkxhѓf⤖e!(KLalݤh՝1,ŎO~IbJ)qF((R@ \QE&( E-&((!Ezyo-k&;Q͚z袊S 59kIJ9 k-њ4C,v,+`Vu,yџ'<|(lir[\Kk0d fҵb-,Z\!+qǠc۷8%*KݟޚKzǚ~et=~xX,T 9Tg4r =$Ys~GTg䌞;qK;ܻ--2)ou6e?Ͷז7jfMM ]I#*.IKTPͰp6??fkou{Xc PygzG!~Y£Tӽk05&խ_=@ ql-C*9!F2IZ~n0ۇLY~6YZ;IeMR)r>tnZuǿKmw,] ִFKNw^~qT޿Jv@B2D9aqМRɠC=S4M!=7`q^u X ȯn$󈜂8'j8n.^n|1z HB?{*c/C+PҮ4ؒgP2}>JuY-\4=PU[w?(۷tϦ+;?Qe?WE*s_¥''bc?Qe?Q2+OmOGfWg? g ?_3ڟ/=2c?Qe?Q2(}ٕ/G Ge>̯EX}gj? g =?_x{)ez*v@;Qe?Q2O+V?_3ڏ/mO}^2(}{j̾SUG Gv@SefWgj? g ?_3ڟ/=2c;Qe?Q2(}ٕ詤wax恀UasU_ eдQEQ!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP[d:}-dڼte$8^zmXE =NYiLfSh2]ՊnZ?;aY1W;YؽrCr1֢^Z@.m5E~-m4-r[-.rqd\:em?RwGk:Ud-u<3=V.t({EH%jL\䎧94 i<p[ x~BWX_$}MH3q9RPkJywOaҽk#jVW1_{q+le` }ȬkҮ.UH$3YfoWݠY -su`nD5P#XgFii{mOe)Q᥯fUgFeb9*k+m5pJlһFA#݅S)AkmPc}-CRn*͌p=jPzuMISi[,^Yai0۴RK$۷tOVРt3YdB9g#N+>Mjiֶ|k'qn%I/"&;Zra/(9>ՓѾzuܮjVw˧;KG52%ͨVu'֦:tEƕS/񢶞"s&E*1M[t]zi}>zt-XQȒx(Vw*g[rk {?d=urTR]g0P47w*/2ygu\4F W穮~mT#ouƮiZ}-(\Hl wMU<$HIc2*Y0 =1XxsVR+ͬEBlr:~\s6r6Wy=>(\Hl >Ƴ[ABcd| # ͛ÚڕԶQ\Nmdb,fd =N`5GP6-٘. g+D.( Q(P!(Z1@ EQ@RZ(R@A'S5t(+ĎcAŽ= # />Mkʱm$NJ /`#3ttoՒ BC+nVSwZj8={{ybm`kZ9R"+X4Ir(N;6=J"2:sqp?iPKil"%[o1S{|yLyI$Dij` p +tkk$REG&;$aOr%As*2_E /#"@* hӦ?5O\׆( o2Yf؆wlc)q@((PE.(P( ( 1EV}tn]ůt=-EaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEZj|_]^Ʊ?t!r wA#@,]5-yg%|"*( 0حVF[;k .57xY*0:t|#AVYTcW[e&7\.p>S[k.dXfD 6:;+sI}mG|BcoQ'Gyݐ'kP-~ghl/$x q#y +o4dI;sh^5lڪ]DFU:Xaxz hVկ_ {PMoe#(ʿf蚵mijSͧM4!f>oyfrjV0-]bXO;OG5NSR^Q+cH!<Ȥ7ˠP{EsZ̲Gsu0vHBW +rkW x%RKWT؋F20#zECCKOke6y1Q?BM4;{kÐD:=uWkbŗOri-1Nb¬YO8,c'v:]tz֭=i*spCUI$uNj䴵o{m:C4m?c]&8޼b:+_ܴvZr]^o\qh$s+}j[>ne-BnL8U9~] '}Ereֵ|4iX[f1_]wֲ>GO|Jj+֯>:;9;>o}ls ./e[k?28oiBEfn2Nב5Jz Oi%ŭ̩7.=9za@Q@TWwK$[7p4-i"43,{Zy#ixBq=:*7<;('ݢ -l>Q ueB ~xc^m4zҀ=m:ZضӠkt(2AُgO7 rۣ4ȥCђx@袊((((((((((((((((=tZDz~?΋i:襢a( Es)=G"f+B..r ]s),1QH֢_$x^\ƫ,LC.dQGֲ! [6+VW_o ^3Ajn]Ί2q (Vl7k&"ުŰ>VPW;BAq fL ﰑX6z\3Y}=7=>Mgt,9 -*|Vn&FHV w|rq9:.:j+vR L̓ܬL`lcaa<8 7@Zn=ڭܳZ9y0' nlfB0=\v!QLAEQ@bQ@RR((\PQKE%PQKE%PU[UwJO`E(((((((((((((((((((((((((((((((((((((((((((((z_ 5Hn%UBT]ђN5@j-K%Wܸ "}㹺9'}‹\:I#K4W&YY2F=t4Pkb{"1ˆUv *:tSkMz+wom!ZfV FT{(r_lmo^Ւ*6F~`@c܌zp*嶑:^J^9qw }Gvܷ?hy}@xp鹹<- kQ˫h^,G` gyJ( ֗bvI mI&h ( v&n{n:ȉ@7oIZ^H㹺5@ϠAvMD.P 2fEQEb)bPQKE% QEQEi::i姧N"Ɇ&Be ?|Q09oҽ<89ɽyX| (uy!ڠ>֟xuf=Mw>@N3>׭bJyΗy&:_or *@̸Va&+9%{%'T.);@nA#kB,gkhx`U5Qojbԟ\kx$ҭ$eDy{yʇ1 ^P|EK$4!l.ќDKKS}YkO]t%}GöVvVjG-N& 23Os+:G}LWP$%FP~ [YKGOAaΛqK-n8 n#yDР$kxh:`қL"6ىó NAI;blY7o'g=8 Y2iLf[b >ͪ]I1 fa ")e!$60tϮGe74i*[)XnѲrrH' j:E$r[ۛXd*DJzc*P/Zj:…k7Ȫьo,sxϥsgįO:~ɟ^+4&G6a-G3*($'c' ?;fs׶qۧi{iؚ\Vat=67E1**pFON*h<[{{ws4@eodV3]3G^:{h%.COܼ#e p(4SDݴc;#mq^xWzUXݘN$ݹ}+{ eWV~\Ы$MձPIxjؠ(KyR jZ{eg%E&7ySnú]VGn>t)Pk F8P=Y #K{IDL_d%p\?7j׮ f:>b1;?nU'?eAY="ec:,R qL?c>,W YN1six*uVKV 0FFnbH έ~LgCdbĮ è9k=tZDz~?΋i:娢9(Ćvqn>du]9•9kyaL#/QYK$m(B"'e>+D7L[ iFw|lFrx=ѽqkd7#A*˴2985zJ)hJ)hJ)h0\QJ(Q(($XjzOQEdx_W WŧoOk^wq K1px+ro7][V's ʖ-6$/ԩӐ+xa$i02{Va;U䁸Wf&*qzpgRE8_y5-7MK+6 ay^iH$ d9<;OE#LnjXv\F+yhmɃ Ԛ4l W)'{ Imq> FWd\29OJKuY wfoWa¹!H,dd r m? \W,A4̵̱~q HU8'A֩Š)iVNYBIBl.YH=EkR|!mxu,1H2t+v{ hpZJh 0;DѯC!3ע3tӷٹl;siVWbDsHFy,hxf89$ }'–{-9ӹHB?NW3DD۶D#`ny\zUu$66EdpQ_>5oi-.Db)$w]Tma8Sq2i^#ִVsEm %H*Eq]n[P==ͼPB]ч7b[xaxqƎ &sè_CEZA9"6q/mxB}^<^2;}beK)}I?|'QWyx͸gڀ=YI$E8 &NsexCj-}Jsnl%^P!+]MB)&KtxI*fpy>f[]F/1IV<> cw{S [bLȭQwJ騮 oZ‹PRAIU lwO0(##f}[Yd,-$!t$T1d@߻2AmUu +9d}27+]l-mOrn&¸ov:`YR:j--hsI|Wza߁9+m-h1䑂ROA<+pݷ8ݎ?ºi~a--ޞ#$4`l@\֦Fa{|P\CuOo,sC"I#`%p^iB}&RLF?25apv;˃ץImkh!-K2ɏ\{#f3G`$TiWWWqas6b"8\svOk(#<2YtHY.6d[SK|fFEDdT7M[ӎy=&8o|Y^jK0m,K DD{qdq*>r?mF('լ"UorΧAW7*օ VۤȬ͂0Zt,qImiԑ0@=^((((((((((((((((( .E{'g?쟏^+TܴvFȧPԬpխ J:+x-4[|ĿO>f%'h/?RQ@ ĿO|@ ĿOe'jJ(=}?4|@ ĿO|1EG6OԔP{eh/?RQ@|}?5%%'j- { vFut=QHaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPYxoBk]:[mgFr}w֝\Xً}Eyc{/=HдtkaDe= i@[HC$`QcW9cfKLgQl yn Y67@Sx_ )i6ڣѬ`!$MXmЪ@:Ji6:g#%} keicx) a'S@x{Emw|vȲd 󓟭XLX Ƒ*9TPW4."{fR4JY'/}~;f=7c5f&e-Yۼy"h9$`>espOgo,1R9"VU#b@FDG3D,`rp*J(}ɔZ[fE)0BG#$xK\]Es5R$dU(KV[XVݷFĖ$Rj#b,NhlKs cU(eͼv68YS0~ɏT1FंT{ 8#qaw1N:z/O'>kmgn1ךEWx #60[xK5EUy8 djJ(Vk1I$pOS;պ(({khnm`d ΋,ZEXGm#nxR1钸5mlSm py`zTPq[JfW$(ol>[@K"yQ{5-x^MNwq3Z1z PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP=t]dz΋OO1^]^+y-.^ T޾(j^f)/Ԋ(h#H4[313 C#%&7wg¶:dR\W4h4ӻ\V ?EΦ֒Əd (;[i*G@.#dkxU2ѻלyuBA>\E)zxߊjNw?'ѫN1ѾYk{ Tܮ>l1$Z"G[M"@~u=V l$TgBAA'~jZ0fBbāq5?WW[ԯoL(0(((((((((٫6Gmq; )Pb_ՃM_C(B( בRJ HHOa\'Qσjx_ W N>}tSrc+QMCVhkG6l3ݷ溨IcY#et` =ExXW^ŧHpX؞=GCMϒ^QEaj*m6{[$ʍ~gʹv[; xn\=j,"{s.ZmwciCsݬc*F6\Cl軟nOMt_Ʃ+: cL!N-n+h%Wdp}+F ZJZb ûM6v>%bd$nWtMZFumw[G`K\F[ k)gi:ou$@ Q1e+ܞsOmKWjs^sxi+tafŲP" x LJ5i&uTNX̘V猌cZՠд{N%xm{@#8$[KZ|7!E )V`pHsȮ^{=R࠴bahH`1Tt-vz/fW2~)j8'`G#kZZJЙ~y w~+JFJ3[tX4%#gvlg5-׌aM Umda%K4Ŷ`AJ[mJ\]6>db9e#U~KainjNu0,Y;1`_9@)·u+A4PԴ5崎He@Y;񞛇[CՖ)m:Ȥ ,㝧UV2^5{Y$Mx&PHP]Oᖱf,.`I09' uϽEm>YHTbv@vjOX Ưo´u/YI]R]V .yPcV|;ȷ@/^xm'/H9l8T1,>g)mwqrkF ( ( ( uUuP(Š((((((((((((((((((((((((((((((((((((((((((((+1CG9\kmFB'4Eg&I6jnWi*KStZ[^,:t܄yG"46ŅyܬA jW'NV_iV44j& 19rv$1lQY-uvRդ,l(?(s֤nmrIPv$/_R(oM"xK` Dde8 sڧu{^93m###p@}K iݸ`v668Kfζ7BRYw)ː7) +"]<bHm&*!I( =Oj]KTl`]T۱aR0 iQYcA H4p ON'`ycK5{iD)GkLX&HPrAҫ]jVWVW3/Y FqǮ(.Au}j_fEFc6px<kKQEQTn{++[Csu*8w8ʹ s@w&%EٌsJOk_ϴ~+[LgEW-$-#DJR2U(j2Iw6&:՚((K$Qǰ h,6Scq!'*q' ;kDe* ;b'{LեxnWUYw.qIrRϭXŀSrYOɸdv#>hBĻfew[Q ZL4l cIIbuxC+)`zhQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQECw:-??E{?ds8ǟR Φi'Mt9:Yڲ"% ʗ.|mfhK<Ę?LVܡ'X$HV#!Oc^}>=vm[3X6­QGvṴoGeߥ E}rGW2|mٖm~*{IaQXem~*.(')Kv5GYEWQEaxO k#XO2@î ^ヷځO k4 ;L7iVH~DD3+ᛂ#Gg/ivE5N'Az9X{ Q|°ˋ[Y))$J=jiKVk)9+ѝ:|Y S}CA yZYG8=K8HCAuYs;y/֑`UHff,#<7 ;Y [[n# #pBaoqj Z]AiW~Zo (7$ _ ~ =O[l2O*x'\].t崉l' ٌmb9}K3@u߸-æYiSZX[Go#1rO=4&)Z )i)iQEQEQE-QE (bQ@(QE%U_*T_(v(((((((((((((((((((((((((((((((((((((((((((((_E%ԭ-+@hLbIMwTPXS5ԯ)y'N%|I-ޏX5եֺe/!QAdǦ+h3ЛPֹawIv rŸc9X,>uGҤ+ӗWq9]}ݤЈn|aM/Op8{~QN];eXO6haLxԗU3v o9$IA*{H-Lf!;g`@N}TuOhEH#;xO,6:H%$|W|fwo@.SHLTzkQ}M7ZN~!h!KX>Te2"q ;Mgݴo,runV$|^EqCUCO"4G+'ڠ;\^K=&Zwii@2QQ1z(+WJ fl}1a@?5k)oum˺.mqcF}}Ί[Z ;14RG!/z۸}JX#]:9h)e˃kw3d1d\>_H:^1c^?$~UPjtFOӡ])mIQ4X7c r0zsR˭^ܭ힓ip1V#r.@7({(4)Y١/ ɻC",asc+袀 ( {}iw}sx,W'$*Ze'%|QKi?YpR,znt~^4onM XÎ228ꨠ?2K "+]R=BJ2;STI '/$y"v?uRC4\ַGNeg>rT{*OQ|YJ)m* &աhdܩ3t pyWq3xWJ0m G G"(((((((((((((((((nE{'g?쟏jZ+UB )jm۱ 1GVYuVGY '}K6ň\מ2ۋk&+0 $`uK[_2Y[}K^J$D#0aFGaZZtX/m[4,.:SCMӡŴwrŘܒMWlnnr6xf(a.xAaKyKMeŤٔVf|czi o$i% 0eڹ<拠-/"[ǒ8 /( J:`Vs:o ΦZ]\[xv AyYcbp Џ+EYMIm-n[IFo6~zV* 1EJ)hJ)hS1K(((1F(,R)j ?:I/F֖ڍwJWk.q\+=??57O+.zp?rϼ0kO;<$duwZg{M\<W^gX ˏƷ 4~uF"} l!I5.[L >T`g tWu: [{f^I X؝wʌ5]Q+(KTl܎27nk-:K>J_ܬ1Ʌ-qU2xkGľ"m,ڤi4L1gz9=z_6]0Uºڿ)Fӌp}Gft{[մK {]Jc|r` ;K\xTLt)Ǹ  `lo!xW8EG{ri$^q$cӱgEP0((([uc,6oi1(px#GqV]_LRDg)JC@RqiJT_@ۗrx~uZYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿPѹ?:EYܿP=t]dz΋OO7E-,}JV#l#[qh<4sZ\RRhnux[u&lSir^`D %3N:㮦mAqrwܼ±h( ml0! ?JU[kx #of#ˁ8k\E[>6V{P9+b/sۜkG' &ӭ$ۛ| rrNNG\?M]-ѵ 8y3K9:&:fWо> As?5xv[|HXl2 osvZcmB*`SI3[ď .ƍ+GLS  $nJܨᵁ 8aA5 IMŠ( (Q@(P1F(Q\PQK1@ E-R@ E-5=A'\H(1Rvn/+̈́8Fqkן۩Òyp%kv~}U|mlB_Ywv5fk-RdAq FBx=ygof9Oލ=3W%*?HB@wUTugÜ4c:Uw_j)O5oc&VTH H!2@y םJnX0,$ ecm>~_Xh1$fy{Aݍ ۦsP1Igy5tk9 ܳ/8_TymgzR<`#ɿ'y6GA¶Q_i/~E&;o*;)>OncPˌ%^h=_RůC9{y"dl+`Okj %֫_y„wP hLZQIKLVD.onni7ʒDBs)iϿ!^Ap^Es'~\m9#1<H/=uHs3CE}{ռ< 5 8fUcN 0;gSEb [ Z[//y38ey9iE mBQJEg,{v02N+fQE((((ZdpTHzbjhHXED@UFEJmuv_Ҁ%(((((((((((((((((((((((((((((((((((((((((((((߭L}BF99$ FxkĶlvu;-Ġ2@3@Qx]HY\i;T)r6l|M-mu6Mb" Qn^Jb6:;Y\gy%ϖ|y]J1ޫs֒u-:A-ntB-+w1U@REtlu=kQƚ'F[pœARxtZ\$8{4q6ǐ.;3-cچ,ft#tK]ۛC5B kf 4 FAA T}GWua\Fsg$~L TVpof89<8s6[]Ҽo ߆n8_Fqtbeq~ LӼ1%֭6E9<> ,=jlܚ~eŊb_o$6ۿcn|^uJfo#M{)LM<?2M]|k:gOWRǙ3w}vϧ4fZj$q]D"`22<պ5(sΝ{{n,cK2!C S4MFm[P3&h-(3/bCmހ:ˍN}^Mtw#6?^9{Wt&"?C|zQE1&.?Aޥ/ndy1N8Uf'HazBۄlrv8Naj6lcăkFHea؂?JtqrE} -$I6r&V L[!K{6b[8#fR0?^UR-eUtˏJҢ(((((((((((((((((Ki:.E{'.-a\"]8 =>)DDJ*!k{yYv+c%<wOdy4. -7Cy!3I# H\G A4XG^x_Uĩr'**&Hǭnxa`\\qj^)2u&%N--)̫<sU5 HOp6 ~a.FI:cةԽ odՓI$n/ſovlX~R:)- _ ]\h1Ky9q,ѴUKptXX|?"vy6HXdGV5/p`&(Z()hQ@Q@Z((\QE($XjzыtOOր9xF'6!Nӧ`3qN}_{\֋s4K$N66"? OU{ } ]*o[#`VRZXVm>O=Xe\pkBQmKƖwD^#+Vl'i+skF/rňdd_3Vg<wFVN3go$3 VbҮyo忧_4} E3F9BG*q=8$~4yQ^Vf8Ǧ=*o-?Z{j,# "f7x|Q##lRnJ]7 d햿h[6vǼn8ָP>%ͼiW&+,E$O,F pGlZiskG.44C}`yc-{4+(J)hP1EQEQEQE(PQKEtQEsEPf&0 ƣh=2Hָ_K_]wN ޟdּd/pl(8s,өIQO_<ֽZua&zZs*(8NmK5}x SGx W6:Q<A3e94ajB-Gt(Ǟ4<5ZYڗI*vr6r d/$y돝> +ԵOY't O1X9!d|k,xYy^kͧ5 #hbys!?jW5[P[HVYIj&dm$fi1ii)h]j*}Aa&ص@*D#DV۝O^;פ ZPɶ7x:im PE`ܬ8fI^ U hli$ 6ZmIҴ˭2L]F>Gc09L,jVWH9k޸ަSN B a%rI|. =m\#0\z'( ( ( ( uUuP( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (-C^ocF˧vjqiU!rIڀ7hv/\iIe ؐ&ALpsE:.zM56d%c۸8 8X{AEs~-ωmEC Do}аkp}M1|Qw+,vhS<7Y*8<\}ώ7XAAa06)`UspxBhM>Fq$cE1bhF5-庴-gr:H}~j/ |K=v>Le&T(|nXv-/i#۹\p>a来xIl9I-dH 7 +dhV|Q%Ƣ!2yv6^⧟>F*}vS]m+PNx'ο֬tˋ"ΰ$,\1k%fl(3#bs89":*}_ڱh.bYc$`a3Zz.Xn^=JfͿ"ۑr̫u +5}v;dƓn4\B# 9lϥn˨Y[m=ݼWPffBռ.NO< 3)t]~/}gn6[Gs=PJ@WU\$>M3zOb,-q<7!JGA$žhu|EHmsn%Pcm;sK ]$^Ky_mVǮߛ@a[OiAa5)"[8XǟJ&eȶ'$(PĜc9C@hSKGk$-2NҼS0{>Ry;mPEPE_CԼPy|%(`e4 Rl Y ,Df(jt͵ֱC2A#7(&,q|6IqŘl: g=VT^;{ ˋ,H˦BlYmę v8;*+'F%fvn*/r[<AֵQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQECw:-??E{?dsQEt[KLyK,Žô~Q''k*˒N m4$OpR˄'cp,_Ρ[P"ӮzT pj[mv;eTrɝhC2@.N=-i:-Ɵ}v<ds%'i'QF#O1'f nq^Y>#&i!0I2cz`b$RVn 2yW^$llPщr,g`8>;lO2Bvv llT bb\QJ)qF(R@ E-P0(QEQF)q@Ģ騢7 (0|fvJ'^u᷇̑c^pGh$SI+-]>{(edr qixgɿ7=1Įm޸ғfX?4\c= ZC 5 Ѕr6grIrj׳j~4pZYnO+ҩBqRLViEYp8񽮢&F{pmmo)U%rѐ7dmף_}侵/m͵@wWP43ɮm+ qw^I|M&Qlq׹@gm [J!YaӞs@#/%O3nvހ$+RhdI"q]aRRk: ˲8c訅eMǦHޖ[`h:KEV,ğceW}0(((((ůtjůtr(((((((((((((((((((((((((((((((((((((((((((((Q@&E4W48!W< ry+8MT[=16ZHFiP.r4x晥.5GR XQ>˜ss^EqxT:vp$UC6;rr:tװFv +iV` v%]h>&:uҺJs=y+(*gDzۗt8]=3P3ZV7KXWRGka2o)Jskw~-kWp,\@址qv5Q@nKSol^;iͼ`s$ YמKHvj?*G tC9 iiǦαu;dBH*0 ((((((((((((((((((i:.E{'J)h[!l7j\AiR-#Tr :k$z~kv]-!D< V֋kyp-c"+^8UpSYii/t>YzocjL< j6d/qo$ ̤Bq'2K൅o Z?#.?]L-Zrym*QnBg-i'mco Tb={Sxnbĩ"'VVbd^$PQhэ+a1t+˖*/h&/hgpڌ g5MUd?ͿZO^]Z[-uXoPWQF0p0 cyOJMD*rÕ2GO)m!(8^~8E^OO%t$;i: 8Դ6ˆp>kӫ<%y}w<7г_FKwm?w /'$nN/gSC4G,#[,J3.qm^8t}Pߔ:t=r3@G;PaiD&n#U : ݎ* c4vd3s$LG d0TRxM58Kf-'iY]e?*!P_ p^-kz X\&2AzWUIK@Q@-%((((^0Y ҭ+P9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG&/h 4yj(o9} r_CG$g?쟏=tZDz~?΀9QKEnb%P$/i4"Xtd29(@slfޓꍨ. em(0B1o>pe&A#9[Q9?#HsTW&PA V=͗-aI,R)5 Y)Ii.>;_ҦE?8xW}Gt{}:nlzy׏5~[HzZ6*f2@-Ezi^n"<2 pw|Ǡ>ċ-=Rh P׌ B( TߜqVhcoj;ynJLW7 8AiqxMYy!b%lMn$q7#jw9MPdMu kc5lXp+W&y/! ۼ#8#v1]r>RiHo.^+'#?6y>W hLTy} b$/?K CJ*<Q feg(̿OIE32q?f_'/?K >̿O} (f_'ŒPf_'ŒPS3/?Feg(S3/?Feg(S3/?Feg(S3/?Feg(Jmuk2q?Nqp{PQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEꚶy i]lc3y~xs[#Lw .}>="Ddb00_'4o^uor`˺,dJ9= FF9=*XZXR͍Ť1]c( ?xJ×\nf:eЁ5˰FCHNFI71j7餛d),52@8$W4k{K6f+ٕ*'+C1ngw3XOi5̈́wm5唳$m,ccOp7$ gS5.AGO6b:1k6CwK+Jb`cHDĀ'wa@sakI;UV'M{.׶jm#1:vZu 5[=~ EX|\C9ЯyJ]LUL`u'9={NtZ_wS#_˙$ "Ĉr.I>kCXn4&YHެڬ\kd|g( c8'#KzޱeqK.7hY8% o 9 I #sdžeYʹLVB `уr2FZпlكqp'6ÂIG@rI'bYxUlt%tnӍ>b up1l]h#Xx]kie3 Xہ2=cZFZf 巌|D<[wJ=cGԡRB*U3}X X{Kɚ; |s뜃ǥki|ZVi+ ԅ?\Nwmm9!x_3v a2# =.%Tv `傜 pr4}E4.?қh(ʹ@!s$f^?S HK!,RLs[V^kW5yń.# Z$O*)G+nٷ8qrr8S% by;DOy/cc8没^[MFK&6&gp?uǑSd:_]a5FbFNt8 iV[5KD'geN722H<>}mdkg[In<\*dwr#.C-5o/ѭgQeR 9G̀IxZ]*N?hI %vgy_ [Kx]-%LOvH]gxTٿiy߳_Z>.[7gqmGF`@>k}o-Lo}sEK\Eo{qjhb2Iv9c:!nʫyj+g 1=Y.qB-[ߺ\:MZIgͥƙoa>hXc@Ŗ$Ӭ4R{kgI n3fҌpNdqֺJXMWG{'M}7F=Mu4QEVW5+AE{(6eW%W9ҵj68`q8%Xw?JFmJY'%(~m$_d<8Sm+;7QX[<0##hӴZGMIu)Kx%$T4fԵ84VLu!kx:D1I 9@(yG=e!e-ʓs-&NՆ:/vpMpc}@k²:k2Oko3ȆrH񂯎UAx"mmFTHkg#A~D@¶~Wgn*\61HUf́TSMbݙTQ^c u0LEBUϷ@1[|gQ^e sY-{XyeTHP 'sۗf8jIƦçwn! rJ$!RWn.i <2uVnC2>n vИJZ-qpj^?Zۛcf1 !^6{i@gWRl'b KJ@j\.8u mZ:uU0!ڨ8`_9FJSoy (b6mwq7ZvKmNԙel[<рoC>=̉姚)@$sp9⴩RREP1hZ)-QEZ( ( ( uUuP( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (k\մ;;m( @s,s0:wm5RY6CcyKb- cbтA8vYYzteV@T&sa$zSEq5"kYoyoi;Fc 1ddTvWƩ5mMݼڗч!@!.wLf;Z+.=7hDa JBu$E;~,x/B$HQd}7I}n5[/Y\)zVM4B B㝤bIx46%ljRi/!URM2hxP $Dw6,,1'xĭq[vZ [ĴH9[ O&0mQt9vtW9MNwIdrm1$dm/@UOz<돛*q9(^|INUD T|JCpV"98=kŚχfv 0Ek" ͵s@Es>ԤԠ[eE["r wDžpv(((((((((((((((((i:.E{' 1Z(u4.C4wM_N_T<1e.[YAt_MH{GѢh39Cvp9YCh.Oc.HfѶW[o (Gg=h`ŒgKB6A*N>s$0^xj*JD o["o}I\rF)l)@ByQ֫Zx:k=oNS[JMˍ>fmÀw 0&e_b!Y],8\A^Eq5*1Deͬ?nG 01+!RSBaKIK@E(((b))h(((((_i]?Z_i]?\(((((((((((((((((((((((((((((((((((((((((((((bú̾(:kv|-/!H|݈+9|,`R^hb>Av!p9=x[Iηasy5Lj` M?dBmc#( =I5@u)co82:7gԴ)..maq mD$Sq s[TP7qiHդҾʶ]avRGRC{bl:Rda03dpkf4 LAX궗lC]({խšo5;-O)B*fn@oMծ/Kƺ}ӓb+y*xIi֬nkug}G&-&᰹f[t HFB1ؼ]mc7pws%‡.ÓzTy:+W (AsWuS᥷labiV {qϭOX.3[I0sC_촙RH h#gliL>0iwweX#h T/,,tInn<]N_qC lrFsrxڀ6IKE((aEPEQE(@QE ( ( (4 j ?aEPON]eЅq$q< wKTvj/+Ҧ,hʥ)B_8E5weJf_ܓ^E2lXc<νVJң#p!+$[v-wR|)so]Aq]B,G弱Fz`f3s<Zq>0n>bH'avԭaIDӏ.-hD*ssdr1'I%̓yiO2I]i>X%_l?hoc~EI9$w5xo@jbS2Gcn`G9Rz5oobcEGciw$Tay??Ɨ{'114ooT{y??ƍ<@QL14oo@IE3{'ѽchO{'2J3{'ѽchJ)y??ƍ<@ch14Z{'o}<FOL14oo}Q]i]?[15N-*W=(z( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( -5]>[MF񯮯cX팟yA; WQ\kk J(9ػ%A<h&Xkۂךr[B 򢀽 yn䁓_+}NHe-f%1A@L>$-e]WɇIs2w1$Ng9<V爵Bז&]/LRR{FXy; 1GOqgۋF[W@:FN'j֗åq8oqpdZO= ;XvFƃT P^iVp\O1LP q݋_=E9 Ke<ౌEuwZv: Lk B:\6zi"a  9Zm=Wv+2̠XNp8# s˃Mb.X$Melx-4FOO~RI>]q--nEƛ^NOî!o 3їL9.9w$r ٘㎜I Z]O k"0]9]q_ܴvZr]^o\qh$s+}j[>ne-BnL8U9~] 'JmV[x!<ۘ6A-N;bvkexͬvWfV$r4OT|%Ԗ^b2f< *98 Muo.~=X# @w^&;BLJK]8VĘU8L|ϯjYy+#B1c6O~h^6`v2l/#d|aܜ9g[[-HK|I cq$f[VX[sks~!Rc9#$R?2:7B1 *A$ʐpY [6- ZI}2j48ESjs@n*(|( c+!oy|۔DINpqֱ5-:d׬8n04Q('=(N;_X%7өe-%Þ@{98>Ac 3YrʜOqLmhZO-B6- ;}s=+Bz+`EGq xEc|G CRwhGu=14w9p#ېz^^]s–R^QJDA%#f <P\L53̪nOr!ӿu VA-щ->RBÐzm[ZEo3*Begb<;Lu9$@[ZxѬ^}Im|AtŌYzƽ{5M%%icI)g/?+3ǥuv>PK׻(( ԡld/999]GJmʛ&< ڱl ˷qs=( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ($g?쟏=tZDz~?΀0(tٴkŷF+eY!#y(Xr6S}O Y[]B߼aݰsmE}/l6d(ŒWix.|?|. eH^EYP:U$85Stu/- hvil3ώc5"2:V*FAZ[<K[` \zb;MK;Tm-HK[X"'Aaz/ҳ<# i}Ȼ"'7ܝ;pW${]ͭuS [|Z^Z4ia-dM+2x~^+kxX$AU\79'<zQL4Si( (%(((f(EfQ@P3Eڤ|IF:Ө((Sm_W W^—?52Hs7,\ `9R^Gf/X՘x5cvM:PfOd^z]Rv܂GZ׶Yx>(8?0^`-x:(RIZ7ƝH# 19>W|L-8L w2$U`;~cLmoZed0I6  qm5VVH0s\  _{5#佸[{::FePd\w6Sycqu}QmKPX:aq*ˆR15]?W}6% P\ s{qtlCz\jC~\]10[G|o,/ks]PۿwFF.kTw8rFhɏ^Wbj sauIdepn-4;y2Ec g4ZΉ[|#̤þp1]>psE{%vw':¡ɩ`8aOhWq@E )E%((((( :ӛGCbSc1sco Atu|Wy~:5Ȯ0dAih_k]?[_k]?](((((((((((((((((((((((((((((((((((((((((((((5>ѼC-Q<ٚt#Oʌݽ nMuW 3Y,T.-g}/y?쪢;m^mcd虊Hu Ya@ē$F;iLIcEr> o_ 2MW],5&6drN۸< [M.;BXBRM>mBHYJ꛰l@1&dm'[b}KNMN+M {KP! 9f/j\[\YK{T%BР֊}TwO,$8UܖҺ ,CKTZ}Q3 4r# |VɠH-E2vHw<~*g>[[[{ 'F4\:c 7/ 1wTWE6~+ҵ̂xK:eyLN8:֎cf|S+uq0v-xVB@}\^>yiqy91E4pz#V Hn-.QJ縈Jѡhz?*aoi55pr>:3Ahh*;Byt/|PoEy=Ƣs lw1\A1K$ȟt uj%vZ}[2HJ+8Pg99@Wof].+ַkm>:osCI#ͪ]I1 fa ")e!$60tϮ@\Vi SKG"F1ϞI>͟1g[+k.!x]N G/Y.G{:\HUКtUk MN^Gq ]#ZVT/{2-_* o0Nr084ygVT3RX3ܞwm,w<3#?^Y[YfcWPlo 67>]ůt9F\ou8 QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE7YiFo'*e;~ހn@o[$ F `2}jt ;ryl42^$NۑOQ޺(O eqohAؓHf0-, FݜUOE_[^veH88zEf_{J%GXUV(6p#r>SRii-Sw.pB*P]4. Ep\d1?(.㸎[$-DFdS *)@0yϠi:[ ⴨ Jm3lFf'd, 9q$x튄xgH[/-,~g]gH_d q 1OmonARJcvAikA,vzuFՈ(o..DR2&77Aޭ@Ѵ˸,VHvc*8ձEQEU{(u 9-n )1ʙnxd!NƬQ@0x7B[(f#uќcbۓW"4#;(-wKCsִ ;^;OgkS ϵ?ڼ?]ͺP;"@h:q\0_! %{:3Xc'gmAVi1DD (((((((((((((((((((jJ*Ţ5 avu 'ޑEK*ug#x}O <}A;5ma݆}=u <uIxG⼺2lnr;G`2OM:X5KPgCP"(S~s} Or\(׬\XC9tkZ}b[)6vX4*{cր2"Mb *{)t^[j]7Fx<|sxtnoΒ5$쎌˻ ${s͍ѹ:cA=6҈ܸ+~?t,"(.I%$I&ba}q$r}EiIԡ'oVkO,{mL)RxV¶ )޶i0P+[.Li) JCvq:-gK-JHmȓXq@jc|Bq(Ur"tx+9"rHTbpI b(aϭǤéq${ђt*v݀|cV-3j6Vkp>\ݣ!>(EUS+'UtU (P(h()i((( R$MiJTZO@o΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IE.?%sxtPo΍Q@ :77IU/VɮL7,APY?{hf0.H + l̳E弲Bq*G(c?:77Yޓڥop)8I-1"`-+`N2~wsxtnoΫ]_Yآ5L }ztpA-rH9!w6ݽ€'?׶ku 6*)m#CIk}g|wP\*60)8h?W{⹆IǛ8,78=} 6Pi;dHa8-ou[kS\";p0 ZBYqvFyzh?AuykcxgԬmcKhRbm$h?%E%ռ3ēNH6pL sxtnoΪɨYE$yn %V<'Ҭo΍Q@ :77IUu +9d}27(?Eqq \07<0UQIQ>:??gn4.?E7P$42(d6 =ARPo΍/qwۼѬ҆h,8\dsxtnoΒ]ѹ:JkD:(f8P7Fjv b/lȸ2)c%)He]AsxugiZj77 hGuSV<0e3h?fͯSJQY >րsxtnoΒ]ѹ:J(w7F(?󤢀sxtnoΒ]ѹ:J(w7F(?󤢀sxtnoΒ]ѹ:J(w7F(?󤢀sxtnoΒ]ѹ:J(w7F(?󤢀sxtnoΒEP1~rZ7fHO1rn*̋;ORItx?׉";!`@e=ihg^A=kmh!/4B%8䌌u~!{K 3Na!T\E-58"S A̍y9{mF;)qnܱSP1qH.y 픷S Z)b+62v8'J? BL->o2Ruۙ*]}o5Mf"{ UmKzW( cBG!Th\JWT1fl'𮦲B&dN 6c9FqJԦ!h LњLIE1 Fi(44 3E&h4f I3@ŢC4Q3Ha ?QEӷ?5;8΅%ߘEH<U3;|%~}CZ/W\O笰HR2ޠgz,K_#NWJO\ #Iٔڴ(ӵkPK,~'qG;b0߇bͭ坕͕"]EHd2?Cջ+[T*pF{X˨i XEdQ81/-j[NQ)"Eskf iU]e͍Z9Gai-,bH3f~(y kâygŒO/ +l|;tچm͌>Zm^8ocif-,YQ ORqkeicx) a'@Ձψ(x9bB xK./.lUu ȤǕ! S[[߶|Wy-l&NA;99ZuNZƓL 'oƀ9 tچeC=0A#q;.J0X8GQ֟6aԠrq+m2"k+f1)dqR:ދgnn_0ƿ /u59c-UcBBkX-4 ZiAguA%p%Cv7r}wB/%ޕcp@[=d8@#"8PNN8ۋ=.JfaHr_h?+ۆk%WJr;{hcԎ5 =CA4ʂ\,x*b!fѣ@!U*H#5'㰆8Vآ:+n%,TmҷLS4b)Lހ9'&]>+下\O#"85k -B]I&Vh% зL~rd7$@P!]4? `O06 tTS}|d#o0e=>J{k+[ ]Qǩ8}??d ٷO7ݻws^hJ6:d6 ݻ>\*KmsX{A m|lDJ]u>Lz=mͅVNѴ(}FG"\$1*`''S@6q=ߊ5uq~3mV-#;}7dֻکZfRI/*n ( tHY.6d*;[g):FAGl!Yrvj׃.72O n*O6J#h|HfbiH۞@zd0M[[;Tv[B@", X#oh UɻˏtAJa [q6yk\M\ItKIg/:G£#+m>{K;xS(KSH-Y%a[A)+/o֯GSz|SG侱0WGAwO}7SK'X:n2rkJ/[Y8ٰv|c꣑%dр*r#yj/Ќ>QRWk+V( U(.$'w<ß;{Z(?ֿ?:_ZtRt~T+~k=uOήIQ'GHe/Zt~k=Rt~TyIP?t}OήyIQ'G@>k=?:'GGT}OΏtߔRt~TW?:>i? կ)??*<~t}~^Rt~TyIP_~tYOʀ+hZ~thZYOʀ+hZV| GG՟)??*<Gi?ugOʏ)??*u @3W חK.°7 >'GO@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*VEUQ@hTPZ*QEfsFj+t2YEç-}jHƲF2B(5b\ΐFs',  \ؓp̒h$h\Lc+wHP;{+KX.#lCޖ;Xn"9TWSȮ~\ijv!cfW 2Ldp)d hH07~~/GYZ0 x{p eT?\g_{O]^ $“7cugMTM/e8ߟ|qӽ nhz]SUo-h켫2$;Cm]rsץgXOu<n!wx,U {Qp$WZӦ"[EU|HO?QC 7W;`഍c,#Xt5*@56dbIA 2zIULwh q#p# 򞞔*+}eF ,!{ۖ1?sdpJ,$WKR |mf q䁑EmYK$WwB&F_(.B䁁 Wź-y{Ln 8>\ۢ&״}DXKtpYWnB}[}ioC/O?gnYxey9Q3X;4fEYIu6v TeܓWҵ9/|.{lU`d_PGjC4uQHaEP/ ^eyg޽3Gok^i|=,I]"pU?J {͕ՉǥtWi{i*6'Qy6$;O\785a^ :WmxSMuf(SZ;Xnٕ"Yȴ`UKRJϥmםxcuygg݅{Uf`XN:g'~D񆓨gIO>h)f]̠d3}]ہc26dK֭k:3]B#:-o,82hh,3xxFѠ22G0( ?8Ajxɚv.$Fl~}<;( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( QcN{%xC1aIi h$\svV IX)>c52MQԻStHPXH"JnZe'Ӧ$r9ذS5ԯ)y'N%|I-ޏX5եֺe/!QAdǦ(,/5K6dn#Pw=2pxJQ4X NA'kЛPֹawIv rŸc9X,>uGҤ+ӗWq9@% ]]b5i [ 2>n/{=8[ěcdmET.]<( Ԋkhu?Z[wϧ\.m '4~a0y&UpNx`#RP4hb6'gR=zNPI[NIyil4*^ќ2c6agO%خRI HRJ)%OSڥ_%E6beT6 rs\&k'P_[j*2BNOB Tde&37I :Q+>_݀yɠV֬tH!IfC[PIڠLvSO,iqƯm(8bc۸ܐmDvkxΡ߈'nY#fmP y%OUJu-u;w4ѴF(ݘcztR<)4.E"GS>ZUGqz̶TF3=qU1EtfȋHَs X>6V̻ g?-g܍ؚe.^1,(cFiu%k(U:QУl3K>$rP"k-WԮ%5ӭ&q&\9ր;+-GS}OQa6$R }n]IQۅP 9adoJDWwA:yYx`I 4FW77Y's@,Aڠ5ƾ.NڕY;fWq&i'Gbi7z4yYo㿗A;%T;ۍZHV qyx"w ǥ'gSgW|wg3r o+Ki{晜!+&t`J CAg#]-$-#DJR2UUΕu6sizt+-*&+aFNj[9ucQ{m.9JnYeF|Pg.mmc$GximAïY8ЧgfS6W&˔691tG(K$Qǰjg%V|U\ @ GĚOU.Z+P ݪ嶡iݤ4 C\]DqarזLHP?zI±$<3w[vVNk|o9&I0G=1@}~VBIvp8`XRϭXŀSrYOɸdv#>kUHdSPQe" KgھOsT5ӂ.Ycϸ\Ś=n'YD+;i3ѳ&b&%u !A3>ŚҠmZFMʓ7@I=w37t>ZJ-cxPq(Z((((((((((((((((((C4f4f3f=FM@GME=秭[EHQ*(ª=.i3@^!Ne-̌ *p{+F6n ? -[Fh\Ŏkkr T vAY ѵ]if'MkI1"0ߦJ 2Etf2F*ꁢ)l rT#5Kr=pTd+.q_H{KӢMdٞI 1ʮsZPiM-ͨ)9ُ|Ӷks]3Uo.4y,­$w;B]Apqӭ58hNo2I$~y$ֺ fqko-ǝ*c.-S{bt9٭K_vܹ{WG3EC%ݩWEuR`Gr? j{- Ao{TI9su4Xw9\j6 %<ĖЉAPc\FÏsuhKJŘ#)+gQ͈^\[˨ĠHԚ÷w6i$"X:{WKE!wW+p%#nF;{_Zm+Xpew֦0:Af|»OVz3zFy%A,}pꙞ qN5O-*MY;u5kkx * 3 ۗrx~u m-BARA#o  nٔ2= _΍wV^Mksc) N6wess%vPF ֆܿQtkj9$g(:` F}E^rx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀Mѹ?:Z)7/Fhܿrx~tn_΀! o:Iq2ezrx~tn_΀aMi y~.ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tRn_΍I?:7/@ E&ܿ-rx~tQEq4њvh74f3u,F¨I 2|׎ t˱z g RZ5' w#V3@ZN\*HcP]ЅJFdXl$d?+-%}k5嵔8 wmCc'nm9k7/v ?m!y2 y+H~k2\k]jW:fop>o5y{Fj4amgtYnm5HJ=NO3uSˁ8Y b;I<x5KNFd]_miDj bU ^{Wү./%4{6 Z E䤖|| 8.vp5,n@(]c3nR;u/]8!^AZ+[y-U'AfU#>OwK:{'y,H9</SH,a\㲪⤗ŗsuF(R<10p8+jz/6wjJ4'P B;TZxn$>m1'̆ǶMuۤH'V~a l߆Hm ā p p?Ƹ.ԕ1fuNJQEsϊHt w I9SO:+qmoE,%D3)u_[׫m/woZj?3csOA@W??&E[CȥtU9 H3(3~5^oX#6o([++F hu[Z̖z}։x`s_x+4F pd[V\0qj(3VΨbd8欣#W3@(((((((((((((((((((((((((((((((((((((((((((((((((((((((((ȹ Okx4ښ,D$lm\@glԅV)%@K"#kv@$PK#kym,}-r)< z++O j:]XKnSH*AUUnKGf&qrxs@V4%AoXnoxʭ{Ȉs8=SpjơaO8;E%4BŶK\r€4h{9eki!̌HVPuuFAqmqlf@u<#o>Ŷx>gs"b)<`@K 2Đ\ۼyd u!\Vy,Q$[F\u?zK7͢+m-ʠBs @4Vf&]]u?6KLCޫDIAf*" BI4ECiw $xH?ZieȐI34c[Fu=6nn.RY$vHUQ[4QEQEfk1j֚jO=4ۈR#dP>eף.,V7do\S!hwcc|zPSL#tMB+dY@ F@'Z@JSW]$3], G9ƮEPE1&.?Aހ%7:-mc}:ݏA@szU3Rլ#-e!YIVR;A[vRॽcEs 5߉,lPYZ4e EAr86( %u).'h2!V݌'oJڵw񬑸%# F%(((((((((((((((((Q@Vh74f3LQ@ @$X_6o ~b1Շ#yFۻ=@6vqq $f5 0ddJH汞4f@jo#Kfd{9P1| :!Lƚчxai.< -! ͗[ӎy_hvwg1 ~V9f;9EJkwJFXc]Joma*SK3eI61Fat#ҹ+%ŖʇMUdz;ŀ1 sޯfh sO,2y0s8cY+#)Qm°;[ǽyť~#켆kv=7@>Ǖ>׆ifom닆RbУ"gʏ e(ti^}MF.(|G˜bK"(d4bDh9'y0̩p8 žbQ}RM4ax:b'e6Xvm^Jc(. d dO7vSqT4x`AoA\]εϩf2qۻwgVKV[D]"('>D!WNc 8N|FV* .8S9sΠ> <->+" yav` d~u_n劮t X8Ά9R6S9nZzBrZh]āNvʹ{M5SCѳ%XUبn[c-e "`223WEq A'Xp}Z5K:-n \O ݠ<9M玼to},<.I oB@cqqE맺o 8',z>gOeޣŨ?qgjn>PL EZjǻ-3ސKU ߖl|6g{ Z3ZG+r'LdpN{U-h5#a^."Ki,Kn> :lI) * ?aQEק?5?S7$2Jױ'( |hqטx};N݂4Y8#8=~yM9K 7#IEbW6ܿhhَml0LUJ4P/P:}\Վ6t'c2Pø5aK;iND_:m#TNҭ6N;0P#F$I{z &[hF3q_}Q\E?x-{IgͦOnʷ56J𵭍pB:m6I=I>]IHԴAl+[. )l rszaz6}Eal& i2x#bpOc鮉"2H 2*T:dt7ƺTjk1}Rk䈰#?'1 <[-:I7j@[쮰…I=ji:^܈r Wޣ#VhUc5~!keg/6Ď=*}gNU/n/e1Om##"LN2ҽM'[fO lÎI8#ҡln!_ڥM*Yg:|lov\|I8ߊ(((((((((((((((((((((((((((((((((((((((((((((((((((((((((gT7%gӵEӬIX%n%(H Or쨠#c6zג\g1ԫG=i,ARӮN$ һs\ u$WsEyex[PepV΂iw˱C3[s|ǜ;뵤=4ٓ :l@G5 SII2AGu !"jopt{{h#1ᶑ |yoyNKbD]RIv9`wZ)[71f1z@nIs>i:.#E9?&l*+8P7q Y-V ^7yHrKx@7sb(.gC.l.Bi-9 *rF[o:`coX6LȽ,oPsW8㾢8oX,vZɊ$c '`\C`j  k}$h w*o-v)u2s.%6d`*v)CCl}{7Frq4PvQ{NKtYb@jfqz[i+cFI(uTP_蚨D"vfhԎe^;]T]ܺww+F}q aTc#!d+eq~ LӼ1%֭6E9<> ,=j(fo#M{)LM<?2M]|k:gOWRǙ3w}vϧ5P iF+tqc:_ٔ 7Ol~O`uij3jڄ5tAopF,{o=@>@9{Wt&"?C|zPEPP^ݥ"c+-+p ={ tzG phڤ~D|bInJssVނz}Dh{Z"$9ww ]cxm̆cs#P71OUmBlm8t])'Ot-O)oL_-}8ǽ_H&7r֏rH&fKp>lg)ok"1Ť '>]m.K[˦9x9ɨooe |9n1֬RQ]\+;IrX…{q5fKCZyj{gorr4A>"Ѵ=2\c|0*6=2ZD6F 31uμQsi'%rTv Ɛ褢ڡtbMEA忧GOETn`xfdFV 'Aif&0 ƣh=2Hָ_K_]J8ź2kl]mM#؄>_v>KMI-LԬ\OG3pQEpz&}oo~5B鏱ʹ@,>nyWZ? :n¥M(a: xvQ`=ƅ#wKwq=E <`WvZ-6[{؞a,aWEM3zbȞ[`?gO=Ԭ/ḻt(`{KkCqr3 џ7;sbR!;|yoďt5bZ@a-GJ Bn@3L]"M9$rDbFxPG^F8jZ-fQ@Q@-;|/z}CZ887goϲk^ya ^x|0q59[Qù?#Xb_ W6:Q<A3\iI3X\w,.1yGBK!rBrT?ǟJ5=zr-3Tӭ.{2G@6yNu[޿jayR#4Y@2Oؚuyu[;k2..2Cx2HN j6~3]$1ۤ.$ $i$8#n(k7XݠFx%gGҺ-bɗRZom|ńQ'PN8Q(^[[xΣ:8U>Ӟ>\s[m"&HN!}Bi7}вI'c@$m.%I̖?(q_%lI nījf "98ӵey+MS-Ēr2*'=qYڧY#,c{N+ԿPHiܪGlw@Qޥ[ۍa94Ivۥˮ入Pzq\m5)dԬ) E }c$sZVfhxWtDT6ь:u +Sqy1\G#*q5'WE3 Tp~wy#Aߜv]QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEkZmht^r-! WsI Vqkw*M'Y h i磆pBxPcmK;uG@G嚟Ed羵VR*J( 0 Ea_5 5K#MsP1)b H|)i=t>v[wJzX<_IMp&-Ϙc˕VeqҥӼO#]Fi{Vpa]7#SYzvh}͜bFy]&2 >W%:3Q 5)ma-67T}{ɣ0쭴uKDBd qm Kq53g4h#+NG~*,5=_NQqެqr!ݹFx}J=o(h1g$0F[96|E⨴k[YmrBfvmϵFcM6^Do52yqރ2+!O@;O+^x7 .e&$0( H#=o ^h5 %u+B._P\$I* rG84ze*)2`2=ykqƍc-x)#6nF92Q֮2{HVW)5mmR0O-C.zvacIIiI!lwzҷe,b+gr o|# 7[aKai@[i$q 0N4j>_\G*]QWk*Xm-ۭn.?Po-K&SA  _];?o_yYqxV? Aadzṇ(.K (F{h%L5w0Z[\D%dgf2+1:M>;bRrʪ'oZi |iak乼R: A$Yk0ioȶ/k%l3I0 p׀E;D}G$v XQ>˜ss@冽m>^o49`N7}*욅W"Kt&Cs} rxT:vp$UC6;rr:tװFv +iV` v%@'Ė+=&H.Zdx ah| wJڮ#FY48!ӬT`D`)<]QE̒m$gFV0wLKUgI引qp\'dڟl5+-'̞dV.yqF5 ^MFл}2vܧl_Zgmt/\&fks$b3$s׊VNbg]/.#i#sbNyI&!b &G s<~f8PAyb dvW[%hQk$MryՍ []a_ 0HG_x[cO+QCw, ȃ<$ml{mJUHl㺹m߿Pcca+~/k;UW`5j$uOhΟo,~ 2RK}A]iyaXZ_'s'=hF((((((((((((((((((LњZ)3Fh}&#dyhfp9q֭ QTejs˞秭[EXQ*P0 ZGln.&Hcb3',  RYړ{p*JF9 갩]Vk{5݄B {Ȭ4mn>oj#MV fΗ6\FsO#"oq$xd+(ؑnf~~u5[Og[\0T ;A ۿ ^4Dmf_ "zqx(%ׯ]1ݵSq[FϘFByִ}B]AoȼʩzrkVз!xf~v*r8oΡӬ:V;y˙H1{zx8IaȼYZePÞas[V-ڒJ@AB#d,k&sCقH A'F8sҝ`X_I2ٖ'y#bWqR2pSSUݎķh[7E@k듁u5{+hC ׌J1NsUK RWHcӞP3a˷Mo%սyQatE PweAF4%u;FJ%TK{ v#$J{≬K8]ZE8l?~dT݌Oi%v)1}0P8tLm.#-3x2Kzcu.wy'Ԯmۺ1XxTM%ak$K捊-a2[~ =^Ml$7)8u gG"h-ݵd{S)u >^Mq=̩##G;g}O[:p^jxrZ\nl`dFM3TWU03Cld2U]F0JׯiQfxFyoRG#8'ӼMo=wu*%70ڤ KdҴmu>XHi @n }M,[ x"cT?%džnΓش#3I*E*3eE 5_VkM;u݀Gv3Z){k ,&$DUߎ}zXbh㳶1#r>kM,cD$}:,S:TC%;Rđb" w7m0˞zf%'.uGj UZ1LJ%qe$LsHgEUmuy+HW7}nzZ}M, 9GSFy{u?e Y |ϘGU[ sT0j]ۘ$&P.$y'ҋfFVMl7F:wuZ6YvƧR y|W Ǯ>kw:F!v} ڭx;bCX7+o, Q (AHikm-]_ϙ Dnzy-iKdzy{/?LhL|<Amh^[(wq:=[Zmds \\,!8juMVslAbx.pxEvbs[ -DtVqFz{uVn<|i`=Š/Wf_^OZ@oR7 D&Vg%HbdB@e' ZJ($XjzO (?[G@ ~q}Tvj/+͗y3 ckBT\IƲQoo՛ڜ3w+lv%O:"$dC^R&PA V=͗-aI,R)53 U9XꔪZZŞExZmu{}:-+H돺;G}kzz_XKk5͜+F9~#u)IXɣGڼb71:f(Eڬws@_gл%<7P27zN9!y &sx7qKch#_,_82Dp6 luwW= $S_^[[d`bBD'v8{T^]wykkgnq@,{`vtV?5+N mȃ;HFg\=(a2# =.%Tv `傜 pr4}E4.?қh(ʹ@!s$f4htV$Sʷc0 ۶m9mm9ItawNf39 j+.ZUt@R(GS 85xaei2Y-ZY[20W 8܁HYEs6(V-ƻ[KF`7ۼ&mϋټqg<:+EK\Eo{qjhb2Iv9c:!nʫyj+g 1=@TW7-IXiҤ$gfͥ2tQEQEEq#3>%/Ru icf9:[8*慩ͬhvѤS#,ɸw*J{բ6 gӯ.4=3Aip=j-5%}AX{xǧS@m^,ҵ˛)u=-hX\=ޟms, D4Lrc$T:P( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (-QEtRQZE% ֐OY7;&Pw d ih/XWUSVhVtgv9۸r6s"5 m.[˩E9 zV5Զba,Xw l9▃6t4,r l CPŦ{n>(d\ =9mV|ڭr!# *}'w[kYI$n#?8(6K3BYXp|yX:dq+ ٭kf!E%-;ʂO$Xi eQ@~0;|+|}CZiM,m3*(G1+t\V(#i$**א%vZ(3 GLeL3Qz٬1 igHv7anZKzc$?rr?$ʲ}k_An))d $AI'JNZ$uya]%)q*";& zhJO{_y$OY ` zU-7U ŵ6R\iYT- F8(mNImS-Q$y|rb9=XMEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW+{x14oKHFA|8\UEr s5-od ]7 Br2NW=v4R|p5<$+8+6u^?,Vv˲`čH7L^hזvZ#[#hlW *UPNWoN=Eo@c2{+Ooi LM6fS.Wp9-: 594Eڋ*ƙ ]$t4P'i&7K}]͓#+vT`q4=&Pu ߳G->kln,sڷ([OƟ [-{=`ʡ@]&ừ;$-/H㴌M#ZI9(vs]Uk:^Y\i˧E$HŽCsHGԻZʒ/}r!J##]EeM Bf_ ~Og ^>Ղ6Q 8XDi#V"Q=i gA4}2kIZ=k@:9FuxOqF!Mz~+]>EKh.]F9z{k ]f%L!d)%nrqct72[&6%cLҟ-<֐I4rG_=(5k#*y /6nS`gz.}Bg]*+IOp2)Xl(L;>.29gƠI zYt9㷞$aN rwڝG-Zr ġdz/5 ݬ Cǰx rrE받Nvc %mdqMKZɱ< h㧵 253nMn?y$ NHlf4+u$`(-%QEekSCi04c$sکi {HKk,B˨nϯ7vw{i)ʬчGf4;yq@E%.h-NJC"(*Z(/'ҏ'Ҩxy-?w,NQ€FX̼O;. cf 5[s#A~DG;f0K3Skkh"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ"(*Z(/'ҏ'ҥ (V{=jok^AiY$6v p,D4(\2K$WA`8>[]oNI]I8╔( R5 .XBMrR 0ZgUrA8*h-i<i`L2p:uu]6kGE cv0Șuˎ4x\iSӮ`}(bY&Dl;@ FጊܺW}:ypX(GY3UFml `] _항HebrAs˗úϢ[;ZGo˙20h^n/u--6Ė`Qs=#^;PC>y$WAo<5}XXw5DAd 3n#>թsooir&-c61f2t;jeۢXYn jpÌճ\ƅ6ourŒ#NY݌K-'Nӥ[>IU XS[$x-Qgy[n+×ڽWtme[;Uc*dy w.<ΞGLs3?[^kMB;;O &hBb7 q8$oAK+MZAYA! mF rO#q{uYϱi(攷ʹ"gb'&uuI,04gt d(89XR f7Zr@S(}[k2$lwh&WT>5k6R qWPmt;1EEE3Tw$+_ j֞t/]JYbL^{+]r@45txF[EX u ?֥ckhൖ%C1'qf9#h,̳uo=$6G_CTτ&kuV[{H~ 7 u=ޢϪO,3 #hNc <SkcV|2,L Cd221[+uMSsـ'3qEijiX0CsfzR\%Sk67"1+Hẖ-$7lUo-3đYwpIc-y+drbO׃Scll Cܙd* Wu+[u[-N dʼ*n2{.c=45=OzHsg V7iF=r`;e}j[[ͩyrn?7|i>Ql;%KxV3a$k^+gRФӯM05LLAF*0{srjQٱ})?aHl&,k>WM5ޭcgctKPl27),͌Z7+eү6M>l$/ .2FnoλqsO'q|&F5l{% ,r(?7jv_W&"2Q.Wi`'d_Km7Lq\jܙdFKU¨!zvB`J%lcPA;{F{aTnb*\K<Q7'ͷ#iN0qniS]hWhs,g*$f|Uւm )%ɴ쑑p nl:6gZı+91Um b .0$$;s,=hŷx,,Kc H # w室U-n'TXc8Y 1$s 8guHexңA=ZFn'qy4mu{ Gȷfª*(^3崒mo+1F0g9zc8sx-nmM"ym ǾiMc Ӆ ^ 4 ʒr0O<{2Oy}"f O( :< w^$7]D l'r{f|Usy--4M{-h|29  烍|'=O՞+-ͳ4"U2ۑj}7°iw2p[e{-Rk#{/$ܫ#ka#RR[W& o#]~yN14Jj^)Ѡ_UDy{z>c]eszo-=6W7K%)`b-p ( &Da ad)izw-cqeʫ9P˞2:&յ'U2!kaAWu jAdT"eI( b EHiO#̋{Ԃ8,5;xR㷳$ bzy %5umūWVP/x ]ҿ*)QxRyᵑ-Uv8‚ybLU?zk}lZ ^8ٜ֩xZMlKuE|(R<~a9^ӼG}huF4f-LFvjQ7R^WSL@?ۓqPNg1j-U5t{HmY71;Rw5En]F,q( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<ފJ*Ţ4pE q"BFJm^UPGMEF`Ehc"CGzs~U-%QE0JZQE-QE Z))hh-%-J3@њC (GQEDb%P(Pgn5/ _ZZAE3y|BcȽۜ6 *5sh8F7Wp4Scſ5^9 xPQktBݨQ?yյ|YkhKGtt?0f`&˂TQ8L*(j.|Oslw)KX໖/IO>_5䲻+_yT䌒 M6Rk .ݻlر$ zuxBKk5ileJHX7f6d:x7M쭮l"o|ْ["p2lP3hk]i8$2>ʡ$S#+7 N}_Am%lmhP2Wwhnņn]=L~h RBcR' p*ݶu}O $yŸ1G;W  @4V95mC%&D3nxl^9z ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (_]Uo5Ǜ`(c uWe}za5kVk9-!yQ@^l<犰.7 YXIq̛m$TӥuW Gڴ:pkʺ.7((Bw>!Ԯm$[Iu9<̒A;"c9X|Iy[vLe}bH$(rx8cXu F81]q--nEƛ^NOî!o 37-=WD Gy0;9ZEϧۻYgKzۓ"/Nvi;Q\uk {Łhn-;yWWa#8醤5ϵQ'<#;>_Ҁ=57ON}NO[kd[;cGnj~.KV̱nPaYیwh> cR^Ickqks*G`EK0NqPEPE&2 KErzmMU#$Fp7}>Z>{$[mOhnm5c}鷿Oax翖egC*ǻJ5|Qi)-6OkIK8yYl=(=|S[[E]m:Kҋ(ڽzѾtp--3LT;m ~((((((((((((((((( TQEyii(3((((h))h(QEQEPEPњ(Z)(C$XjzOQEaxO k͌^Tq$So ݔ,,6A_p-?ƾ+Nj/gNS OLՈB[LfC^ƥthc`or*!>&E5x/\\" VFc"89ס:XFRE8!K)EJkklz}szO:R-[!؜ȍw:s(>Wwmψ.WɞTѲpQwijgt;gnLv~ctsyZP7<=4K%harǡ7kot[tt[B^ beu pd oXs o^ ^F]Fgִ.lΛ%~t^1$Vh{Lzw"ֵVB)xԦ6ADSZ[Y^KpMP\N[(۰A=Tdg3tPYZ[-֋ e`#pS.Zv׷i<`A弘v$@ ֵxjHIA bX۷/#xcb((((((((((((((((((((((((((((((((((((((((((((((((((((((((z_ 5Hn%UBT]ђN5@j-K%Wܸ "}㹺9'}‹\:I#K4W&YY2F=t4Pkb{"1ˆUv *:tV= S|YFepclnCx;GVw]>(,[qhj<Hۃ#'jKmMML!"{rAU H#n^4GXk:pFpmdsst.[7W;|ۋ.v kJƴՕ踎[T2!i3\A9=IHxvE5@m̳2$=0sq@8Eb]b^n|f1ʹ723Aɢo YOw$=طuH&з\\}~s6>@un~ul!4D[sr0yR[7rVZX0@G ZP/.gncFەG$MnEQE<OjMx=7`}-XE Fr@)s9]%ͱT.Zv̮1GkJaCM},O#5$AlTC.e;jxySSUK؍ 8:(>]GJmʛ&< ڱl ˷qs=Պ((((((((((((((((((Q@kEVaKIE (KIK@Q@Q@ E%--QE Z))h(J\(fE4Q@M(iT7OO֏-=?ZuL14,pHt__6 ^eЅyq~1AMc燨ChΐvI0kK0kNJua=kQ积r0y>J7i姧N:'yi姧H " $,GJ# olp (Zz~yiwWPYgG* ԁLPPInFx=YW@i姧T/As5 ^bw) SkJՌMLxiM靤\OO֝A8@ OO֝E7OO֏-=?Zu-=?Z(8?0kfՠũ?x۰;LRW? !VnMM&]^q5-/ϧ[YGlD>/#$+k ¶ڤzR[g`N 篥|i'giOt{]-//e[4F (.:|chSj:1RQlC$CE$q8|!ec%$nΉq,K#!؁F j^]X\JR0X&09p_l5A๿_IF+GdT$|mdqa jikm岽 [Ub0F9cok4 >lOJцh)]˞U5]RV m#d,1)GZީ~GuBwi7`q<5'ޡ{2Lu-ڶCkZDmvFZ0l!~aן^ӤVkycU \`~xRTthn$7y'%W=Y>(SHZ%M*q[9G9+neh֐r|/&T#P:T~o亸 ,>$uVu*8uFojt g,l[ϓUgS H;2+BS5]J->XJһ(aG9rsIRВZzN θ̙00H\jحbX0H ZKT+'͛y&998TQY֚a-S@m*p܌rIkF ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( 5>ѼC-Q<ٚt#Oʌݽ nMu7YiFo'*e;~ހsV'LK0.ˋYqe*sVI-ΑkX5$q1Q9yV$]2h:dz)j%6spX ZxCF.m>+ 0S};[9%ʒrO#4xGW?k}}> 6h")(7ªn#ac!)&6$,y%RMxSmt눦Hd(c8 ?w*}OE_[^veH88z{[b}KNMN+M {KP! 9f2 h,8X4j0E+FV-ݚ:ĞZA)j# 8v5&@ǵ6 @&eqkО84&YRNU J26u $QcHbFR#qFӹг^clɚiQTFw!H F:BHPl2f$ ?=h=wr$boduP=IEiݭtҦ3a"O:1U٢.$尤wNze[kx?wu67H^N @tSkywM+PKkX|'XIߑsӭVKMq.։ZZLlRGyq-\=R+)e x|9ͨK0g2,Fq9L,00g@~!m m1~ дIGT a˱Ӷ,4;{zQI@e%Bxmys"x=33Ͻ%tpr0 o h@lil"dh HntƗ2]=ҵa;FX94ց@[~3'Bvv[GooE J#AUG573DXU2Ip:j.>0GsΑMDLH8`?]Ͷj:ZGe%pc$+8sjzEwd]ŢH̊aRP (4 2Q]BKoTYC 6;dP9of].+ַkm>:osCI#ѧk lb1F A H'PP\Clonmbِ*v錪@i SKG"F1ϞI>͟й¨g]ͺP;"@h:qp:֎3"дKXZo(}=WӶ_xcKifW ;U,r{[h;U|62~zEkoFCDA8J((((((((((((((((((4+C1h(Z(((Z)(J\E&hbIE-J)RQ@ECPY=[sxtno΀ 4Zy3(A8_Xyy搬h2Ʊ0^  puݟ¯&t?7ͭGMn%9;C&Hy۽}Nxs`vkƹ 3v*N3l# 3WմkY1"n 0gzL>ۡ,_Γf|qY~ tRk:|),]q"dd>WbXJOjV7\a_jQhp`w^2Э]ۙ9>qݍ8M6Oqm&\Ʋj*Orvv|5͑Yn⸚9Ir.]$ n.MTP5s$9Uw*28R8z݌:t2̈́m9mObvm/Ś&hn- Uwg  :1+tTf1d߾' p=F ixN :9׍b[&7:0UGuM v]xA(0@[wү{M-"K{1=Pˑ؏kyGW sZ e#`ܹ gh54m*'췣G.d6˶fܰX PZ0}T mj]U0ISZmPHd2&sٷ|מ[^IIeXy5V7M*=`~ )u ˕[%d٢vNnPI!ݶ9jz.. obdOvG`Gp>ڞ"kX%'',p*-+yeDtÂNwSn9qɠ^vI]7[xʎ,Ny鞆><ҼAu дɾ?6ر8ؒ8hW 3ZqrqVzsڤ24qpF_D?*m7RլR_2T< ^Qtz7jnᢲVH29 H{(?- !F|P%@@A$I,ѣ"V` 88Qo|%]j)#_I'}6:Wjsy-sRg]9cPm@Wnʕ ۽{PqEs^.[h5R2H<9WK@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Wk4[&]0ܰ=B5bmtrJKt[ogPĨA?}@OANxks ]"hDR@Y\4P^[$'r1)W~66U컭vKҴ) Dvbجa:>:[ oIdxRneI RSV㍤f00'?J4Ӗ/]$ܙDhU$g'&8 -g+dyɜp3Ӗg"xꧡ#j;?xK;t_[H!O=`6זz}Ζ]^J&>|#`Usn.xWq(f'Fzz %HcxydJvp8WMB[Yt۫K;? ˕̱~`dõBOw&ʝjo/mo쾼ǾG@7P$42(d6 =ARW -'m(t4m.##Vkw |9=zT{y>M2؈!1# |q@;;hiC4q.2@EK\q=ߊ5uq~3mV-#;}7dֻ()3ꈣ%NHdcha@ ;}ll2. ##G"M )#;'O}j  3 * Z6kkysof1F468(B\5 lnnCp$5yagˏ g-<''6iVzjvsK&.=) m|硬}r;6pQ%-&d# (_ѭ kCܢ@'}kGya}CU/!2+)3` u>.|)REZG$mPOzע(((((((((((((((((Q@iEVaEPKIE-Q@Q@ii(4QFhRfFhQLIZ% 4RfZ)(J$XjzOQE`O kt^G+qq?ca!HzuM:-WMr9QA'ty߁WР֕_.ǍaQW<!YMM~k;fD$eOЏ˵Mw;TW#¶WGAwO}7SK'X:n2rk;' kffqCn*q+2AԣY.,BbF 试>oX]V$ FVA7⃇Fx;Nq)Fhqpvn@j?qG.K,9'.-F* ]æU܃Vuk'{]Eu$HWkav2x#⠱)CReP@1*@ď5Q@f pu[ʧwǰhV]\,N֑yӴKng1EgZD:DvǗ$d`˻ / >y)o\< ݂7g &.ⴁ.eec&Q@졻+;tbɫ4Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@fM tnYFsZtPqcf.Zc쾄#کoB[=NA $620p@BEjQ@hZ{YkvK"I S*bjՕ*]Cpe8t#=z(uOqgo,$+2bGzUI/d2N(`P##jWh <5,oi (5އp}*}?G437Nq}~38[[}oXCv o>;dY2s 󓟭[W䶅X}G'K[yHd' *H*@ Y`v9 O26@Mck$.FRǫG_zY A;SVh"yF0B9PYA8%^}>n--3F"m ![#xR9TUBI@^P+!XO>E(BZӯ [%͞ߴ R6n,$`Lӵ/VᲺle $˞@TU kOm4Bɋi% <·_Juw])`NP8>ͧ$OQlKJm;1xUaTV/~)ȍ3 ހ6(FGUl쬮g^-'yQ\P+6]Lcr4l9x֗Q((VW3<>Tqph;Tq/QT5k ]"MZK6̋6#ڿٟiO^W9mݞva}o[ZI[\F$ܤdQU--d7(m?0M1u4QEQEEss Ha@Y:YѡՅlǔ6&BNUOwwk2UDZAvOEgiJXݬ\ruȥ[mOt䲟pG}:w,hr OC[1HDVS (((((((((((((((((:((P\Q@ E%RQLZ3IE4Q(4-f)sIE 4f3Fh4QFh )3Fhh( />yk &QtuV8+yi@F *ȈFq}U<3pW||h2[6$;O\785a^ :Vqkq#"E7$GWm5|uitj{'8z3Vϛk?*w\X70IO-ۤJ@8uPok }Nѭ|)'ʙn?B_}z zHKqG\97Yj݄wqn΋q:@>:&⌱@D1`--,bKh$@p TmY7ؿёEoTDGiN / jmmZak7QOJƊOQ]UWr-~WmM1sZ(HEyH22Tz&K{}w64κ|kt|Ԛfi&u6j0Cm? }>k(SwzލnY?:َ8>o?#EutQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@p!&}KoKZ[W7o,Iu`ęł:<`MkwS^R"?OY]@=c֝iK[PkKYty&^BP8LW@g6s[{A4ٕ|Z坋q8sưX|/INW!.s}H/&äw+^q #6[{:(KGBW{An9nnѵGQ1Ad#9 % `'']%Coi›Ld9's (uxsQF-n\goxVϩjgyq{'i摤 *r>N:(/o-ITGA8Ϧk'2Dqg4rI e$W&y3g@kfo }d,󮆊-:KuA-Ԫo]7+eB@l4(e>ZFG/6=6hI̳Yȭ29gR?ٗu\ 2岌]m{e3Y7zwc&igؤC*_ u"[ŷqjݤFur2$S$˗9cEyŴڈjo,&Q!o-˩*;p'< ,Ӿ~-WH?'O+/,wI9ERԴ"K'tN$ sUm>W>-w34'x25#Ǥ@Ol,~Ǧ,}9g0c Ɇ'`ҹ}~|0+tcHh>\WSi:mgBRڒiBnA`楳Z5.'[=&lcF]nP1wPhS]xóC_\+wcDYH:WEQE^M+khYX*NI U(̴=JO J.M6hUgNYzݚ=qm)᜼ij%deqQ@dDVzc,>dv/-ON_ I.6EUB뚥h4nr} zU:P|RTMBɹRfI8f IE`ۀ@ <;EkQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@d/&`ĈeVM 陂$5k M"O/eQ}1L5?hpcc\#StOݮaIe27'bo/eQ6fX8d,[w(x*//eGMj\,Rɱ6?oQpK&x4d}ƮE/cGMj\,Rɱ6?oQp)d}Ə(Mhɱ])d}Ə(Mk?S#σ!A)9ڥ.o)GMHXRJ)9#Crx~tn_ΓOO֏-=?ZQC*e+gg}_&?-=?Zvҍ?M{xZ" vl"A*Aa]{}G{Etc{2aU]d `u⺸)q]^TQ\8dAi!@q3OōV^[N#{ 7n@"7iAt QhNI*F<9eG8 ![A* Zڠ(((((((((( Bў |FoߵipUs 2Á:sjU-_As!@5Q@Q@5;XIxΑ.ylw'~ikeIeRU7 usiR[;[i'H!H`F ǿqW:jܶ j\#nK0fo>X[pSTm$ps@ƧIL^v4>rAq(Nq#8#GxE^M ֩:ǐ xmԬ4ؾs FE 33; [k1xZ;kH.Y܆BVEMuWZΟei!.ج$= zU@+񞹥Xk~QH<$JFaw gֺ-SG!+ThB0y}4M;[vp]p&O9]2RԼkvmxZiF!U)XH |K WXf)-5&[Ir[*.5é:s-r]\F1or;NJl߇|qgC+ZYʱ۹\`H38KNPZdF< 1ߋu4 ?v˵aEx.QC2<ϖhtp:PY]%AsIJn0Ͽ5=fwE+hhTd 3TYa3`=G<r8tVY^?~ҿ2O ERLlm7[G!L~#IhGu$ƫq@g @/$:PN7`\`[YQQ s$O:!Rz ^6\o|Kô. VoT`dPG׼Y֗˥݉./[n}FGzv&+i4MxN$VhVs,б 2   +ϵ-n_ \ͩUX<28㑂:Ipˤ]ZXERvCHfaې(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((?o\6:zֱU4MZ{kXqŠ(\?8G(Z{kqŠ(\?8G(^{kqŠ(\?8G(^{kqŠ(\?8G(ױo,7Qa= (pذo,7Qa= (a?,?8GŇ(b{lXqŠ(\?,?8Yڦ>D*NYQ`QEQEW"HYIUVNT5'4'/xQسGoy41NI؎ַ$~Z~\c''R[[Cgi lXLj9Q@EPC Hb#2 d3*6S"4q"+t$t?PɧZKyCq|a \qY) %<9lT B>'9۷+[h]Ds(( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (? endstream endobj 48 0 obj <> /XObject <>>> /Type /Page>> endobj 49 0 obj <> stream x+5T0B]eabg[ 343 k)*)g(+*AL626ѳ4S4P0,55PpWA endstream endobj 51 0 obj [6 0 R /XYZ 90 752.9 0] endobj 52 0 obj [6 0 R /XYZ 90 689.6 0] endobj 53 0 obj [6 0 R /XYZ 122.1 351.6 0] endobj 54 0 obj [32 0 R /XYZ 122.1 521.4 0] endobj 8 0 obj <> endobj 9 0 obj <> stream xen] y=<x.] zA>cpA޾'h R)ƇǷ^?^.^ߞ_/_{yxIzf)7_k^\~ϟ?v9no/k?߽rۏߗ/x^,lt뷗ӗ^}w}zus_wȏ%nFTEe9"{. muM.NqPH:$=+5( NRkQ${YNҴZg^dEs;.~,J*" o'*"k҄͌Ghx&^C/Di^ P1AƋbi-jPԱI2K>E]d34!9$E^G#i$`` QTD tLD"uI*KJr]8D&'E*Ji^=bbpyE~'jw;!FNnhJ)9wPs$|g"IvI [ӳuфhiҺWcܥjaKf/B 0X$fJֳXo@Τ#CϺ%ɘ c ‡IY#Ҷ{nCk-ê Y<,%yO`NÌ66o0!ŗ'=w'wá]"#O9y}рh>}5vQ{;[;[} 6]7Z[QԐYDDE^Mɯo ALr`-qv͜) Xs sήo__g2|/ǧ//k;u endstream endobj 55 0 obj <> endobj 10 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <> stream x e]Yuֻ{^w[Z{Kjuklɶ$YeY6X1cxz(jwL ~))nDp~]Qvןpux^Mn>^v[|ںW? >8-w8qGmت39zt@'?rPjAQ{Sw4u~/qp9rp;7s\ uA{oqڿ-;vpwp8^ uY ׾΅AKޫ\z78eU88up\ঀǏPvaV3aBv_pm8Xx:xn -PV횰^G\e =/ nۃtuppp8\i~zP6hpgXݡ_~A?8|m;8/|1@'`@7pĺA/`(0Jh7\ pN0vaYvswh a=:͆nX[p p wx0@^O~ q>{*O%x6vK=l`5@\s"˚i^v{;vxZxe!/-p%o {]ili1^l }W-=^˜}E[}[/G^ 㝵C] ta~^NgBJǮniS 4YvKf{v"|BY`6PHO]?|wh"y#v>.PK7o>0v7AA3A<<. k\  4h6+ani(_}-P _ 2iwmp_=fXO.^ ~ mz^{wqh+Csvh[ =`hsmA#|aN@"kiء\(oɌpff5VY'ੇ)ˇH}yhZvW+ a|?Sv{3TC;[ &-4 ̷}CYyxңPCY XK5(SBk~SOV-ӽC9z,B6)OGB U?CI14ܓldՑ>/?@Za=@:KgC2@=@g MKk&좍,Ď3zjlU>-->^)ODo.P|"hr։Z'OH#:huٗhk]Y ߻) % c uRe2ʜx#9ƀRY6Ř֞ Cڡmr>/'ecHPGp~I,S>Km8tOkqOInOCjG׆8g}>z : gCȣ{ioq&$vTf%K<n,%)+u ǯ1{c:.*i홇k跆M|DSڶFzW[Q;ц2$;=mnG"|ZNtfLQyݞK'=|qQys41Fh]WyY.[!u'{'CiH>e%OiGu]vC#=VZTnR|pծs8ʆşN-ECS>ˊPaB}}GmZMC:Pr%]7P7'tr՗߹<_y{G4^ka,)yQT]=*EN-c<'u0?BzSzY2㯧ч!>>}[1f,ʹ%D6YȞgзQN#OɈ>X'&, mPyMّNC2S=/_k]rS'eـu =ɀ:K.1Ϛv/S5dzY$ڷC{a:_Ayǎ-tP:IgPw.I;)ߦ\لh" yRƺvSzڏaNo#ѷ62 uPO)_qJHf:vT"H&$O“_lK=B|{˜֣>d5GܦPO297t֕>pr-{YI/~u'\7µdvuǹ=]ܗm mϾ0.]K9 TG:Zv/ܟ@<s;kPpoDM'lrm;SuZ?y{ ߬?hP]+/ }-,h$vѾk|"ntZ?cGK}LbI;zB;wƠq(Z /WĹaMvW[+PVGK <o Kxkz.guk Z޹:Ka<"p-sxV#|Z}zv7DcW-9x&AbBԌ%>.sܯ8M9`əpKl 2:ec8KhoUt3*Nxtxz2Xr8:QS{ O9Ki5'LX15Q j= QQ}_+\ϼ=Qe rvsk{ٙO3_>ˤHƸ'UP@(cnDg~S}R˓ua^5=jMĿzBcb|KD~.5u27Բ2qhu9U=;%㾚;)}sC4S1o: $zR6І?#<@պ2N~"~7j=h(Ľ *}%Iq>!}3E135k?Q_ 3=6m2$7^Xi.Ine!pZc Kx3b9~R>r/8{QQq}xCs׸E[tG]g5^]I%5pM=]>\nOa3Q&Ѿn}vKFD+Oq<'"#ޡ˵^<ʳ>8}#u$$]|QXRgc\OɐHǐ.jm~9nМ'ߢϭu/Y/:)p̎Ҽ/FY|V<_1DZp=uO=uGQNj~'CͯiGq|mʅ]{夵lD[ɞˏ3C6G{%pЧN;Wy'}r q,h@@vQ|*=//Ywbiψp7D'#s7}WRi+'9.qƭ3K4-gI_|Z%tC^>g3+?sHԇ*E}ȇG՟؎y'*px3#V>U_ʃK;*ᾜ|}Οe;z>vtc%^QNqP>l֑y̅A摧?0anE}~VhE)ŽƤ7>7ƪqvP>|byT_k|K|{ÎƄ1ϝϩ,Q ov/y4i|sj';c:J!cz_#h 먜'^k̀kzۀKvf!<|F( 5wNtOv_υz a~=[,١񮅶XcOgjnGɞ|sڧ ÊvSv{tE2-;.S9eW>`x)Ɲ>Hgq_H~yHZ;7k%;CO5Vt_~|MXwԱyJk*ѧ*6S;j TGkh{2o*"Yzsσ{:RqS|KZ/XWs/CqS?j3ؿt&'ȇ\jŇO!qͥsŲ:[)~R};mAI4;ẕ4+qz!1bTi+ eKY5 ͍h9ZKܤvh㲻/)<+QTGz7 Ϩ0o̤S+ḩ~1g_ 䇉Ǝ#Mkd4>"Zƹ -( ݹ T>[|by 36M/m~7ޢKDѐ &'󦚃CY}!cN[} XvI<(>ho?y|)sK^2E4j{>&R/| k9ؖ>Puc /s z~Oi%EZ3)̅QW0rڜئ*袾"ds 2OGyP7iGu o\ׅr_F m.hK{t/yRoZMd|)& >mQi=bn.+ԇh1qM?gWqz&{$Q֋5X<)=Fy/ziOYZ^eŧ+ 74G^_|gM_sݐpRzW~xy6kGןkN8|Nk]1>{<ʎ(&(\̃qWjmD*6 $eq#u_ǤlQGOr+a5߰¹R-s,9ŋXƼ)?9>e>>Bt&mvT|G$w2-X1 Ų+=.;/ kFx=Jq - r qH`_GݞNx'{_e5;~Y+y~u= ڎqmOlЗ'hqѧt=f!\ g]b}>ujOy`8:8(4-&ۅ?ylY{:H[㸹ZX?SF'FsV _9d 5r%Ҟ.Z+18%<|VqO6I\S dCȗ׏eZsj]9bΏzgDX8}gm;ZU&-lq Ag\##=ibZWHCؿ)ߥ.oA>hjրԳ |Ŀ7>}Gv+1qQ|-}ڊ|/skUCx}t8]z>p2w6vܴ #ýv>;w1$3:&;*sf+y^@*w&9s߱anYӎit,D Yzޭ! _|.W=:1,Q?jYkZkLm꜅)'LoP_ ;G頞+?E=:Яڊ֚~ܢέ]ՙzw'eiGxg7=*=;Έɾʆ(*b&}'θ?-% ԋ[e |ZW(<5sӤO;)ס0q#\fyeQY 8H{ƂՌrБ|"4j=b?>0X'5;giKQ?Q|d\nkڰ6lL_FTZdQ=Ց5Rx.4ȓ*ZQx>yG뚠ͯ}Rzj}gǺ\_ѓ8\CGW㐯Jߛ:us>u&@{R)0cޓqc1>6{+ ?Ht'hM;s3Nw.'c>͗q׉7?'_[Hl'c;ыU};s |=ڨ㗿_.y<r̫ƺ6;5)QƟ!ƥq+|# ϙ̻hҧ;D 84/Ιg^Pν>#G2b3mw IIV'O]N9!_gu`Bsd,9gU9u5>l&#pm٧Ǽ׋2(z5AX>-t>è;%h(ͽ /ԡ_#Ar$Qi}q؏:h-hop-Q1Ny}X0$w4nx 1r~2;JX7J>> ʧR'KsO*qCK2$0>i:+c_IZy>~%[k>O8~tώ1 wuP7 7#ғgs|=&~?kSDeTFNCش@9(хkQY%hO?{IPџ6D)YIVtx=/-r>X<q2ssG5ѹ[!]c2F_?wcIu6V=Vr\5^ԘԖ#-hu}և^~q*<P{%S3b%]lթQ~Q>l =%\YƫQn~<ےx%*PhMiks\&&nƾU'}gҾ]Ϻc$Oʜ3.hOsnJ?Ncdy H>wײ[Q{ bi9֢ 6<{,{@߂D~m2Q=G! !P~-I8ڀqvsLV\[K)-ʏC礳J>'];G=9͉T?y~Y x4>{c!P+4GȮH$B%X$C]օ`ɍ2.z5"ȦK&)+ޡ.Z-#Maʱ0;,(Vε&|c(صqE/ͅUtʟ$u3ǔ3kqq{%JTF_P+R=ḏq$m 3Ѭ^Տ|8uP}R_1g.[ɜWJ:L9ZI빷D8OۥJџe(њqCcV{ `^yWjM;T cN&S;]d_w6OkO$5GͯGQڡ~Ng˙L16Ȣk: t_>.φPZw-jǘ#q^Cċ~2KP?vVGga}{h\ZkvYxGr;̭ӯΠO}7v=;u۠qr;>F;7>X~"I$SMOG~1&ޗ|E͎9cfy})n&Q u2me{59:/Fsg(3O3Cc=$G+6,_|2_x5n녋 blc,})f^Uh?$ZqAŘk_bc[Xa|@PgqnQKt`%?>< _jdh<~%jS:RjŜdP$ܒK iݥ?zg=Cqܦg۶q;}4ʷ>wLQGVL' RSc >_zK gN8.αs/rLq,9LX m(cSnd݋Y*V,'Пnmd|dM8EXDki>|㸫vAٞ"&Nϥ*؂{_>}L͑oUI^OқU+x&~ nmjc +v7֙+;O\֊ g/ѳz.{|VZTv׹>Kgdz+vr7{:gf:g{a}K'''*y?"w'xQq5ƅIFe,רYF=F{(my:氥皨ü$7Cm ,lu?bj f8y^>:_TWKGmw>FMBx&43m:WvG0gGY;]s|tx;,(#|i>&z-cFeѷ(QG8vt׻uw.C,h=eKh#3g^c^ƫ.4)@2ѻ84emez%M?@dP_DԸKLG~GD8r$'zO9:h3 7ƛʧ(svCٟCCDw$/>;0&#ZM=&~ߊ($~[*Qor㘥bvV{i/z u7`g0%躈ʨsTnqb͸4`)ձB,eϕQ| -bX1O/:.6)2JԳ\+Ekür* P~:>}1sܧ'm( g(y$ѰcOS͎{\~EcٷN1ltFjFGS;O|by [3=Y?j=Uf3>}-_~"M| E1`&Sv }g<g$sqmc]\b2G=߉;_c`<-wߔLĹi(XN3YCF87dϹ(7?CumO{@{(>D _ϭ42|Lļ/8;bn)ٖ.'N{C9ODK̓F3vt|m};F]e3lbwFr Ż|od߉8SUaקPK NAŲk=s܋s}=ӯ=(IQ}PW:B>'s_,oOrwuoYOh-LjcG'_CuQ/ (ѐҏ%Әg[jvOh7QOaoѼ4g%]>}тgS%{9>ɢ}N=3ړ ˕r䞚ƤyxgfHiȫ'm7m*,;D=XcL$C?AzK1>ufUW2AqsYOlohL/(=cl=ZCV ԏ[K}"'џ|᡼pޱX5ǘ&?(3ER cX;*O+;}ne1>8g=٘:e/;+Bq/yBz峟G>t,hwbJ,gZqs$pSCU]*L^N*IS#1=o\2f ١Zߏu9"~!Ӈ>EґĥeԇƦsy8^]eav\7)^8':gե-px{s3#WhseNZgYO"cnMce1R\6nsoN/iAguNLkDjColHr/gkAxQ;\3Y')}J}qkqSkAmDێʮG4({9VsϚ;uclN(6AcHIYcl|&2 j\#L$7o컪  -jw@دw١@~j[w?^|2ZnsɆSG؅r/WΏ4]_UC_-IHevT_ц13|L^ՏpQS%_oJ_ hF><9ra,wq QVcYʙ֗16hG@M:J@>͙G}C"=FSWȏm u>>һ1emG/~Ua͎>VPaL@=& 8jvf3Ogu7:X\^Q7j$wI/N,ecԘ-ʙ_x6kβԹc5'18/砚OXQDxOQyiGXeX;'I2gcqtPYy:y'ߋz5Ѷq~ c9?o p%a^J.pdy*Ҍs/C G1,I/ű_Ifh_{_M x:+:~8=^_׹G8Dؗо򣚯| J:qy)o͍r. }Ɓ@kF{QI߰c~V<=Q;}I;j'H,~i;Pnts+xgW%IяJgK]%-Έ_7.v/ZDkZWv1n`n$O| q]<$ ;paDm)kՀOm|yS١Nh?8uFvm RId߈)^4Q1xLPG#}@}t9Zwhx{?eRvc/.O"vP&HOwo7iU+Z7K3M]U6mGH(ϤU3Gz>GsIH zVy-V|Ҏ>[Dg=M=s<̌I^kL;%=5־ъBy!ƃ|VG>05;{! /x>Ǩ7ǹLM:缔_䚑 K-|TFui<(Gs/Ŀw,rYT/A֌9mh-wf]zқb{<#ŶIu7GJN^d1x>=h*lG~3;M˜@"mbkž8}OпUy|&n~|KZO/sKv N^.\歹B;IggD㞍|CSlgIyV*~74W]q@]K /R#肺4(*>J6gȜxoS܋?mtõf}!^g]8xm0_*~A{M:/ BHk-󁠃3aq\bHv&yG8S\,=B=W;xQ_ͨN-lDxo?ٖ'+^z8Z5k0?fM m{}:we·Y.~&IX\}g/;}51W)'0Euxuih;N4?Q[҆㦏HHp0^{zg+V7}A'ߗbssQ~<@ެQg[8}Kh>|?]7% Mf}n=|i{sPpLj6,,R{<2愄viԞt-טc|Z9;~Ayj[3]W.a|yjQLҿ'yX>A5M-xW|(ZLmjQ;2fuRPOaU7صVي4ZQ:|@< c-Tɯ|=5| ;]঎j١~Q㚲z,9Xۊ0ƠD1 6辌8C1?G`^]{WHXw1ky2l6*g~I/b?OJܴót=69duľdr8hDq~e\ZTyn3oE[}@b ^,kͼҾ3+^ʼƔ~lQ=CkDe,ofעu pߡ\"9̎҈~"iɜ{\8ߑ&1oaq.Ӊ:Y~ߓ?i1o#ү dr-${u}P~:28H1?GiM?tMݦGoG_4$_H1| Ǘ)*:($fev+5ut;8ΑƔ)ޏϳv?*ܜ+N0Ovy ) ;; Wm+P_}3/8>sC'coKgM|K4'141өDKx90\[(/)]W[ m-t~)nEПm5PLpCPhGOƇ}3O穸xmc#gvǝ x<ڞw|S2iqLMu2qx(Χ{} NէߓnJ|2& ɷ#D'Š+oE87ίYن@G:s|I}Dcx?}iqW:N__?$,'wi~=|/AC6__v¡V;9[ڽgώvuw9i>(^cڎs6ДF}͋a{Stkfvq~g gПpo+yI+œ9״1Ϝ HgUuێ ڽP61j[nqI_o*?/[iҟgΘϚhA/0t޸a}zC9.Ou(ͽzʨx~'^Y;7ʃ)vMb<{ D/8eBs)rkqsF8&}ef ґ $ю{k c8^J۩: s*oI;o1q~9[yXSG&ܿ]j'xh?p^^ x޻(>]9~~GLG(Fx% ' "sq>cwaH+~\ϝ18[J^aGu }Eu(MH֘b*s{cY3?sR|r|jG}h?&9ں'OFsgv/3 SqGsIWkLjyQLTN߁J8ħڱS<\hw'g K cTpWܤ3oahn$-$j1#ȋ Qe3M[xK4SD=3y|Ϧ o4F=֕Z>ڧ"g>Y"ԻdCkK1|e=[.ݽ XAa~{*wz+OgQ^,s/AADݕYEfz&Qq UHc* Kʡ2i=Md-[ݣ?3w'h:mi;xώ,~67&fV,Krmqrc/}0Ìc2\'eYzLc+i:Ll\NYLIK8 l== Qmrb!mѿ|/>Ŷ_XcGϭ{} Ɔ$Wf>n!%$Zސ;Ǭ|@4֊f hϨ}kGYد}<1ϯAˎl.a5;|Gt֖4ŜaܽPGy]g{jβ7>+Z|A.CmJ;|"z I#~3V +: Ȁ?Rȷ,jO{)Pi'ʕ2Ԟg]{&"mƭA!gN8rh}U"h!hRZ{w^EkJEoHR:RIW&;D}N3v<+ͨ.e:ᾅ|k1i_N<-6PNan',G|:nxe0'q ?tk>#vnѼѓ|:cxggP4_)fdâ}(ԻiK |2a.o%nT14mly_>?:xʱ<ZcM{oϫspsw:x{j:[v|Mv=rpU(v9208.5~]]Vmp"q;8?*0C`k\ k{W5s>@'?Ǜy-v(Ϫa-]r}C{Ocaƪ=П+ø^ cXׄ{~Nw|_C5_ik{r}wܾ;V^sWO['v[}$u#kd -Q{5ǖ wkz{k[oyUcՇs#ٮ8osouoi7qOd<0-`xC]r nnyGcqmmG>u|ꘛVH緼u.\4p8w8l]%k|v3X{ԚP뤻y:[Gw-k;88p7p׸ݱ߭9[ef3sTPs vTPA<3 *A[v}湃ϟ:S43/?؋ ;e 4\|<̡usx/~aO ff0yna濧 UAKRA/,O}فՇ k_yyvu_d'j/1[܏?piO]{ߜ.tk|6W0<[^pq/ \zwxsGwFQO9\Q5^:p/<}STPATPs 7^}O?{#>|(|ߑg~'\:ᓏ?Ӄ>+>K/~=ySQ_;;S?mO {*x"Sx|x3/O}?OV|ϵ* * *6 *x'Vr~=[A_ ]WWL_~TPATPW[ATP&TPAO ~3g?|v~'ѭ *xO ~{x+ §*x yr;?dTPA/4ﭠ *'v_/W 9z{³'g[VA`UPAT|ArqTP^TPATr;+ *+ *نG*} Z}iC+ *}gLaԱ * *x`3g7̼̠C0waTPA/UXh<}X߳۾O?=Xgv석||X?{>l9TPATpþXATPATPA1鼹%&X&c:qH~ߛ_t3qw7EoB_N\6Ӽ3I-2H֮_Lډh6sxϚo48Gwxc!,_oLg6mfMvi$홬PPf톱sS_lNyc`霹ZgYjMS.36'HVNy9ϔ:exs\,[qt1I=&jeʗ~ s>Y)\'lY0^jlͯGFW3n3SiRZMg>dΟXOI/|o͡Vkamo} vs(N;̪ý*~>ny7=ݔ.7tIhdXGE{ٿhʋ6n&n1Vf$Y+w׼$5sd3m[j9bZGl0Cso :foP]u7&-y9o|rӪ|E}n&ƼIԓٿ_4~mܚ/I7s) S7߼~tzp4Ң(RS7iW+0ݣmsz'gLCv4ufsCJ+tSr-&/mڻonϙh'8J\p 4fe^1yy'vEqY+pKXa2M'5j"kͭexjuw+Y6Yݵqnf>g&͢dMe>8mRiHvvkϿ޼ci;Z^Ķ.v-_Y;Ԥf7ͺddݜn߷r|(|3 z_467JR71ֱc6o['WF-1yrs']oz4P5<\%3 5{v5IIx>d5G_q9t橯~d`ڕ~7T-nu+`mLRfYMx{85oNӕve]֫jy;j~mN8eeR̾N;m,t_z축nHs]?ifSs;:^^JvmZ]L-Njj nl[nSIRN}ӂh̉μB+\o6'>lr[O%昙I fb:u$uWU[bJ3j>Ƶ#`^$>`-J~^NYsNǴI=]Z}^gݵMٙ ;&m7,=qA' NӪ;f_[[$pvr6?PU`f-]Q̅]3өIgwO?v׺5zf|>ECs=㕐MJ7?5|iRc&}q6gx 4Ωllz}9陷9’0w:q,o0׶z*5W)&Ӳ~\[gz^Q{=q\fy[|Grg{]Ў6J˭irzoKryX[8z~S#/Fb.3W+ӷ/9rͳtve\ۚڋ/'ín_˖6<0aT=t;Q==Emid.~`ս;ℵ_!XQ⮢J};"LWҚV/^%6jj4N}2|i_2ߵYXˑԚkiڮ^bz~9t =䴔Md'k>K7uOst>)g tPNW o1L?wVl|gLEOOߙ[~0/\[YviU\P˚I&~k&nۘN8{dxùGioq!KeŅI*[Va\cifr݇/{nw6iFb|crTf'>37fݳFߞ0e+lLNdwۋ$)sO4ڼ}~P||zյY\٬ >O]nO&g4;ƯWU-+֮XەR< , >Yo?p,G<j3)-[+PI"{TA\k&eYi9?SvY7dzfW61n;nߞ;iV|^wԼLI]]hW79v lVNo3N,vIsL<%ӭ+ϚvnOw 7z>QdSJd,]uU+R2 @7[$8e[Br-ǖYT7 a8 p\OzVln挽s}g>IgꍉŴޱL45K@g]7񪾏jEg[n\,ҍr \ﴓ{:]wJXXtEa^V_UwcJkE>P{$]m5-2텆vHJIwvLfə7w{ާ]]X#ε]߽q߽s˧,dˎl"mŦ^nlnl~l! $\ϧ6]뒏9yyLL CKU7_o,\bAh^'LL zy¬9gE{ߚpJvvEu;۝,^zmǻ.%= 6snXtvn We:.X;/h&N+qۜ%sq|@<1׃^mѵLvK='B:eŒsMvU_ vu:$-kYڞvt z}|懧fRKB8鉤'듳劳t9l%ZʽskuzmvZ$?ڛmrU#3Iy;V޻/L3wtn~*[X}.\ژjE˥my׭΀h79WfZp<;teWl۱ϕ{v)fWU7ݶzܺ)C:`N-m1o@Tfд ōs4uQVs˟s/ַ#f99zYs]J3XyLy'sM%} jozxx+YȣejAӫ^4qRsׅ ̽ye=n G?RsQ.Fzt4Wl{e;VkOή,]/λ~ S^#˖$ywf޳a6M'S)k"i\Ϳ;97YS/tXQڜ9-[6 7D}0_Mn ǸS93&+۶-2si9=GUT֯9|yyج]t_}_Wk>ɻD_eF^oSCsiz").fr%E}:ߟ.w^֝<miQMS:mLΛm|DžD9c]汭|-_sϫѕn&'Ӎ y:bs=VwnJ]f~+lkړ8zqBe =X"?^Nh&*LM&vPHA*fd}<Ks7{@O>Km|Vu z,E{k<Ԙ{=s҃C| nÒ+<*^ 'E6(1$| O?LX]1lޠ36 i3&W^#]n073%\۞A`6 |ѿ $uHE뼚{P4O)`TrY瑒s."B0C`tsp0 |:Dd 햽[]a>d<˧O`ƿ[9Ȱ 0f>S[m>Y鳛EHzK17fP׮_ ^ yhU GUF0s۔oD?7 30]D̪j;HÜv"*zB&nD~+&|B8pb*sٛ3 @nU,Es"{^uȂ«§!sw79~sW7%-,3D O*kG\q0fdS^Nq7Ȟ WX62?Q )g=Û ʆ 'meXG97yHammEedK<2ZJ%]<ƏL::嚒liY *2Z`RYؖ *$N޲ttJL@IvyXj?M@kE%/Zǔ m#wG#놑*I<܄kA@4e-!!Sf) (چG}2;-rdN? lf4X\! kR w`5ӂpbǩSk|Hm}Vm)*`pp/vnk9Bq cB*ԢPx+.CmK5LVr% ?@"RDʸB99(fZXcr:Uc7~ I\7Q) AO+|pz>e| h1isQ{4^7 f15\.YO6?Y}@4Xed C J0/ràh1\(N)(4ό@ z5.t:S|*i؆yY-!l}Id%'%KBh˘0 '۞ZgaPΙnA1a78(M ZM* X7Im'ykyyX 1/&khwƟRyiKLBiЅf 0#r#U)~ ;_H*d4*Lbp پG1h^\Vy5&Gld]eU\ΦmN9ˣ0H/>qN9͕zj WjLcB!?R}Y i@qSV(p 4kʣ,t7()DRkk=2:J:pg{|;\~(cdZlыAA$l +%0 lnxP"-Xy:6`=nُR^pog/#ֹ=Ҽ޿peW截A7Mf> ?cRBaS8eKH@N Ca|p:1gi*. UCJ/%|nnU}aߑÐkܷǐS-۽{)qXk31\?'?$rWwHBmdsK6E:NJqxi(EؒSOUA Iatݼî X%M ۛJIe=0()`aLfR4&utZaEcp(ZL-Jx1(- ${&= *۽jgimC.V' xk֠N}þTK.MAWRXt8r3# xѴ&pE{mINTOI EdZ^Zj5cO鼩ȷ(E& VƒFavq6eD 5.sR[XTz+w3eZKp viuqzk (FF749bv-C*}%%~JA\đ\*sdmX6Um9HٓHpmlPf;.5E(楧K9IOHhMxk}gtJ.{REn$ @/MW 77}E>9 V`~)R9Վk21R<|OS\ $ Pe]Ӟ;cZb (Ya!GϹb4! P+*mcHYK$HG$O:nzTз<ޓtdQ Ͽ]"pqu [x'l!'g}6 +#0a%GINP?8̙dD2ʔkL2 ъi~'|ۙ>|{K "f =XD/.tЫ!35 &U<eHeliˉ7sO8I5D]:=,l<誅D(|E(\v3pbXmLB1 +vw,M|7J#V N{Ǐb29yXmAqs}U 7RO*U SN8fEjl|^do]ad6']W ?~^r\dM϶: ;/9RBbDUmcՕC':fPju~#86}+K{6a\/"m9+(&?N'!۵a"1,2'wסzjOaW2\t׽$ґUw{y8^CN` @&j9h|tx\Ɨf<*4zb5: &˱=%VrmFht`}& sJ ~ ,>}r?'vUWEIU ~:Zld\&w )+,׸qa#JɗI7( %7]Tfۡ%Cmjm8+OQrgR"u V㙱ܤI}̹|wNbŝp1{}m]}MkrM0EŎ#e!b,LmQ!ðQ zSk'\bǶbD]xrx`dfloET SέY0^M%j\d*iYН L-ijz9 :H3?wIg9\dyfcBV@9 ƻ//-OZλLP0>*8z'߁%^0єȁ|X@y4(ˉ<&sn>e'eŞ3)4#VEaM4'!bHjAF7n:⼂fƁҳ5g}mbklTefw@2F$,wP=+а>DI_PS3? J|W7/{L,pjc0R 7$zUiNh[1UL;aZr[ pӎgjM,"ֵ>&}e5@W~_#$\IW6;:t/iCK7/gj̤w$ ׎Rqx.(귂R"8:>mĻ};~?pM,s3$1ۗ*G˛w1Y+>u֠Y-mBda i4].JL[4_]sKUͣ}i'f ݛ5? z$?W35~mqi 'HJ]0ўA;4w"h6^1e+|yO(SFyDMMPFh@s|$Lbixr5çcl^/YڰwQyYF0[s&ŴE=oĉLn ]G^9v'ܴew+߹G<&5n 1L>"QEWykqo\U|!>X}ߪu?a욥KiK L|Va#:N: M=^uP [ ]XȔz.%l\V"mEYRΛ1/ͻ:x7ܿW^<(GՕJK?r7aǁ{w7Fo߆"n25^(;<:NT0bQIY e e t ކK& uȡ+l3:1cusi")PZo \J7.׹͵1_ R]'}v}& ri\3z&d8YM'8n@8 6&aqB"u,@aT(fҏx)&)O"o0$P[R%]h8 1CU!+*Z/Jq>EklpۖKAniRȼ7Ӥ:S}dfa0;w>̇'[)DkܒK'^= o7?үIU<~L/A7ECӧ~GF!:~Y99#>݃Ac 6s\DeޢmU/ƙmi"a=hlHiqCze'aU<22NQ?085e%O*bҸğJdc6Uwt c..yBT&b6|]wY*hm#'\P>œ\>WNɌiPwzu2&[~!zÞ>F84ES1\H16/85y\9'<3X8x0< O8.xTv"%W0V vJ/#6%KV?io fduFQ>Ѥ#ko݉# (A'96 |wz/Ey\NJ70c۵rq8zU՜: MME]/ӏ)6sbI{ ,'y ϔ"Mn"$~J*^h9h $Ò؁HpTib/ &rsyVvdjϵ߉uYN1f4X nvQKyTi '`cg\D(sU=M/$1C&j O&9OLY nZ eҙ_Si-1/;ԱU9w-4t|):*>Wo8hAz*3Zɰ^7k:k`>?QWօg04F/5 m<;ݫC=-kL{)\hoKpUjɝn6h^K)8R&@o(~?wֽ1cY1ZJw:s4hk;ii;T&4_fSfŁ5|ν69j~2=u!)QŠ⣵ڌ+V>=ߞKF%}͐ݩB8a$Du #>=^h(DVhW 4^]*LW\|Sin5I>{즎t,|acPa8+أxA.}rYDp;]c})%Uˉ@$bSqTb ٩ $s[/ؠQg6AǶAr)4"I!oӤY)%)Dn@!r|{Pܡje.bOVCVĞbRt& DYp2lfTU݌ͻn&699PPt2^Rd<ѧ|+ҐߍSI*u| r |"gpܦ1tLcuemdȎVOl^wrMg?u5fU#&Ek5ubyG#7/ط&,SA@6OB 3$6.~F1F" =%ĪGIFz4I BdnQE;BNc~\s)'<7{旇ՉҥCQ`Q,ƋpOL X p,5aM&WAŽ8t8Vm'z]XytTN --eR#[ t謋293&L  ĤV>MzIIGAٞc3JϣFGuiKk@{/ zHdku|np<),u-. <R|;('8R13]τzN[2K' p!C+AByFXE☏HH&:#c =Ptٖu AkӞJ0 UqHqYgЋmx\Z UvKߠV!Dv1!kx1Gȧ]>3O^yH2T5MY[33z7,4w`Nfc$; Ba{s'0DžtruV"O.вFyb@gf4ߗ8_v {4#`r%,K@ L…ml)E7DwɃR9Jsn!1xP\!SuxΐQ@qB\vBxk_o(@RihIv mP ="X y1"2V“jk/[y)!ԸPmԎW,۠ƫr? @gg蓪h/WH8coxQj [>opJ@ﰶPD593Ӂ0; D91fE֨_Ǫ%xK_å:BC0'#(49:af5bX1^ qjƍBX! :JMwd+?g#^#D:C:î)hhj!"[){K^:{yD> WewVq3ija`>B|R]j*Sp"kZKX!BܐRi\31 9i* {,h#kdHÛUy2=*PuH^_裮*V{ƛ1|tEpo"`>R+bn9@~"=Vki!^59wqƂ O 8__h_덭L̬\jfs{'_,l~:^^1dY У3e X|e|PE7,=A qˉ9ছg;n+Vluڶ_H=8 rM8ٽrƕV+_c(gJQtxS$̩N9OO$Qf A[1 jzVC [3dFB I阅l(8?&Z>D*,XʉbVhFƙ!~+Iqp~3?Erm&B+0lTOOmC3*t+ϲ9ϧQo\e HB=+W3.Z"~Nj7d';eOr֩Ld,0Q`Pwv݃M*.6\u=[SAx8}ŝ=FɾC#J>mp)=pvu65y.tFwć8~ucםI.ywcB&:_"7aj[4ӭ9_v /rskPND a_ 3'G ׅCѪu+2\_W6c9GM.t\Z!}3bD)t)#֓nux*֖Kiz{nV_O_Gh_^M> a_C|3_#7m` .]? ̇U1:0nHR$?g~(y( FE8w(;59L xڡ*`VqP|rF.lA㎇9FX7\Q ^i&bЄ] cs.8uq7ڴS֠%J5x1z\,Y 7எ-y1RRmP>9\ya a3Zm\d`#OW(/7R6sy rFVe]&aFhq_g)Ki*.20Ngޡ(0R+\aAasО? ǰ[vKh7.˹H'c_wFKTSo;ݱ8rd9@q7mZ9vϱflrvD<̥r֜ncӼ>#SL,U]ACfxXL庥׬hCܞjw\MFF(O,s08GΞ#R}k`zՎMd6#y+rZN*+ET#8#ǯS80.|\TJx$Y#bMy>?jcSi <w(ok<}~lJOgv.] Iw\qJV jeHǧ,+(f1Kd̰w930chy5C/n-K: Սn%jD r~Q`:uKqf\Lm1U8TJ=x#nVSݟA w)79/gvG' s_|{RF>- ;u>^'` 0Kk]D|] CIbVstz"y%})Kh}&.p:|kǏ_Ȕ)l&4LKw<$M y9Ta4E*$Pw&@Vc-3z>n61ErLx7 !]zds9@VDOx +6oL ^=roƠjyYz`(<<(9t!No Ǹ1 7 д{Uē~BBEMK ˕[jMÐPq,FjG.酛yP%־Kf:T^9FUo'^pi6jd5ʽWǚͱ !p_?+I{'?g<8`E6uȑA0{vh^YG/R (bjZ!+3 =AxxLYP9XT8BcHsWi=iq9]όMa]!妞K Th?C "<PS'()cqt{tNu?< J~@We{Ug`X3Ƀ4lkX 坆EI"2Qo6B΄n<2$"l)R#1DuX

O9ƾST@r |>VM͋#A%/s,IӦSM8ЭbPA<OZb5t9)RD笌rܿ r'h̜Z<"Ξyob̴$̤3I@a--<^{ z ުۉIxׂg{"d!64l>KiM B|k*1}i7a3NvLKQuZ# UHGgsggbL Bl4t>6 XLօqBFsz?ʳ%Slځ-T>hasFQ4gHPC 8KQXSO{7^4y_I쭽VձG:<~0uNnT@7g8xNޗ|{)1HUIi'^!ϮG-ҿ8뮪ANN3,xy6U^Bf5Y?Aݒ)7k ȺX1AH薝f[m-{Yqu82HLl*=|3+J](%FgAM%/<7\%NË|vȜ˹΅ Bs/zg??=RWz`;L>R1ʊL?=KGa /_ː]hM"?VaiOeSu`Ŏn р7,;4ThE7p&ebO+axax9 =ŸƄRXݎʸJJRKd,b!l`b8xnwZL~q蹣 '.S=yfy#!0\1d~Dku;t0`KF۟;sS`nÐAEed$7q" 4Vؔ&ÿx8' 6 l* c$vۓӬrxrV815hua Bf>VzJA" \*nϓz8sռ4Q'|7zS6ԷOO:Ԯ,Bog6Q8wmre(Xw.xhIC!#k\m4 dٍmD{ x'qcb_gkԎlyGz ӄ|à>XNJx/$Z`l4ĉI`ܒxڬ?ßU N(X woQAfQ۵b{ǴUs&~؜ `Ցᣱgr u%0#;B!_i-:ц^>sqci驹W}ɧؿx3?s7>NAdЮ>3t3)!ߣUk7[&OZh,r]}5i+.5srH`+@# Mu5W}, VAL7F󛎱y|:wq9]ގt*ٗ0˞X# G*BDat,gH,OuZgXIMLoˊտN\g<A9"rN7JZ{ 眮> /#)u?ub'>Kܴ=}r㑏|Kos~=sLGR`O*f6#ҁ?o491!ޟK:y$V#iDZ%ǜ:bxbi oHzyWgZÓe<(=3*n~F mERe׽" oME1 L.TWED,nK!]HSA D|~VZ1_Ttg[Cc|` 8 .k_FLBl'3u7MU/%[Xf I0tڝI JhQ6-ĥrxTAOivzfu]]㩕x׫F"`2Q)0v MeH~Nw=I+͑fC+.CۣdW ra}Hm+a9VnͱԈB9mm{QuBU"<5Vqv +P3<8bcz^z=Jot:MSgeW7z𖽤Iv(Di$}*g, R>RJKP4Tj=~gn^fС=lEdx7f>;L!D~kq|{f\+ܳNPΣWS/ꋺDiک6w_ta w<ҵW`mٹbyytðVOa O7t!<l?)3jO*]V@Q){lRؽ:d"8Ud;SGQ۰rQ^+y+]}VIpl" )M2~*n((քddz| eiϖB>w+.8gBYWIv}bʅKCT% [!an:=7IzfԿwGpm2NaJ^oefgח掚H=wi {/ u/aN\QߴFigz\J2qOm5e^`l^~pyٖtoQ]sF][+Z|vsgV=3~2VŚil5'XߝYd_DU h0PBKCaW nطw/O{Hߜ5U~/]Xi[|0;3;HeP`GI Mst 3u4ӍSx̎&lt-cౚгZܣn[bշf)']EdJe)=) K2_*Cc*//VԀņaq˱+lQ͖3ւ4=ge#Y5st^TtZf@Ckө^=[puGTU^$uγpM}7 g<ߡBD7(]i} |㥼Mb[rD.<54 $ĈЋHdN6X>+BѭL[zJۈp6VNζ7@hZ2\նOA!Y8ӭKoMV6ƓVa_l{874 X螵l&E|!G*zBiv2v Jd:Hvy!򻃫:Hڗg)b1Ov!I+!^l9vnLMs" ] aVgc~1X-jp3g&%I-]Пm#vBҒ*b7J>wmKy Qeh뿶u/"i:mڱ9;G5QNԺ6pZND\^:R5튫|Qk?;O<7`l>8` %/~bn!ۗљtKAδ2SۜDрvO0pwc5O} J":yHrvhi{ J%HboaĸtgZ#،?-.)j6UZD-e198j=qt4 (,(,5HbНWd@e&L?E0]aȱ~&b6HqaZ5H7+:G 1 ?ކ6VXKZ6ձis,m UنMJXFE-41v t<;=b nrf_y͜^8yhgOc_H78HϮhݩnp6{tcW#8v ; _T^,^^pqoHz[]<&񭹽xGS!mȴD8-Լar#,*[ hz.)ZHR-V=ycPXڻ@֔5GBs=}iULwAwC:Zes CM6ͫby2Bp^R7j6<7h:YCD Z$3ǝ:z018u4'ln&0ܪ%U'0_ӳ}]ݮ[ZY^:Zs'CjQ]< O=x_|dYQDP.oD!mSVV!;PoJBS^@ܾϘvad5}4o$Ys°$z%KLD C qet55X Do9|rr/…x8\<w"eWlI摹=j/=Շ-vֻ9g<>K&a2Ovcβ0"3s'I -OW1q R׺м^J{w{招/oN~~D:w}K;  ƈf8Me~F9]nDdpUE&OZ6XJ} T h0vPg*U@%)l^e$ҦYDuY Nq]GpӒ9POe'/@5gMvg|uѝzo#(O=q^篬bLGtjGd`|@Sî3edtL2N*]|R𗜰>M#j&q~zHz V\nygp?S T qHv,7Nӿ±i޾eQ Z+MrΐAS˹Ey`w=S{p]W!{k+ЭZ'0:vlY2;A;G\sx9{yBD"x#_ ?)Mky` |MW+<,F#ӺX7/ͯÌg16i(C3Ll T% )bds*-GXqplbbSN5p9 ƚ,7͛ "[~։Ch2"eca ZME6tktUtcȒpñRt:MbbrAL'$.cn#H]6m?zXdkE!Xy|{1H83FY&6(4omT5s269i 鐃c88q]{;rIgսh|hxT,PwOeO%nŠžk*˱q͞qWoϡ_?[(nn$P_*wH7X$2o\:jfRzQߣqv 6-[l_RgO$?y//EsoEP©4aAJ@L^SW!g+h?Tkdқ6U U?q?ߤU YJpqKrӭew݅5VM?ҊLJ{Wk} fWu&F߷1g[ח4M I],Ζkg=ttc cSRf٥6c^KM`9T qj9!{DKKqIÇW]<y`B;*<2@ ϥ%Ͱ[̯V&|cuI'k1A?qRٍIDek!wJ{ּg6ɌomLt~•Qwn~`c7~h(w0YY]}WI^bI=bR&t?" GNWJjRv5ָok"+t1" ҩ`bX=EsޒK5}^MbN96D6.;9_E8 bNb4HYwoNEcd.xklt&uĢ}Y sG- (%/dnyI$Ld4h~;Q1%B1>HU鴍`21A> X@RcT "`ԬX,`qɓuCh0YJdZoF&Sۍ+~]S~?iФR u~9?^9Xj)z]x ~{6Alu4ɟ r4P # i%je_,o@8ט=#W¶{ɀX7G/ eLB}[ J ެ!w B< S}hw- M\R-Ӣ^SR$ (fq]T|ZيNW{޲сY|Lilrf 0mF252f"#t7SurN44߳<,t~Î+1z(@=`١7 sx0/ Jd}U~X V|efZ)0蕚#M*>ʏ%Xﻥ*ոaPZ,}GCK+Jg@+YN3aXq0_iMp=RBث~ޕqvNwSt=p~*gLM>> ;C iI<]A}V1py:M6|FCIPY-~C_L/ D0u8%Fm~>֠J?R;)ն xAV=IfLki-98][Ź)|1_iǘI2ьV)̩P6XTZ6m'h1BPJ@[]czR-'ʔI%h0;\(©GڼfoxNh0' ]u70۰I> r"R!K9HK_7Sy^+/ɗ^{5bm k;%)i0?/jӼf|^%q$"] pLJ=Zp~ Y!X0tSy5k44r_8~@h}Zˆ"fam% >ug([?ޢAQ'%װ9|FQL\]W1U:X轆{v]ljq[i#=Kav,4e2Xܮh:ML4r+* &-j uE 3YOpȨS}_C3LMj̧[   ۡC ?ezpK[ )} 7g}04҂gT,BEV1Zz"F4f4C );B4: BiDBʨ5Ĝ ת&k22N))@0K{&-M3)SOqĘb$:imn٫šH ndQ-qh|E`J董&❉2-ȍebr:@"Fu4`bvxz̘ B<KO6] Ojß?B>3?ySWoJ;D^wј,m)U6b4z7Ѝb7ՇQt ghZ_龨b4'= L@&cJ!)aaI%mNA5Q@C1HղNhpÐx:0 v5.ړ`Ǘ' N5ubG *15ũ=ն O1Lt 3 BVVjх 8q#'7U#D}g$f:MCad$ ۊ=1]JM?:.6ĉH' D';FOk墒YꌞDvoԨ6cIH\hg'>;ڣL'uAҨfU+z;FpC^ 3O<}yu ɍ*\ ~G_czީz)`.Qv y -X^\Is19;4gzeس.azkHg_AFQnWup8ҁl/F.'i|1+p8~NпߢI?6˸"A3/yta {0p@"Oq~>} [I7uU#DSkvsb䵑|e<Һ=`m%S$ sM{|j ή?,7:݇ 144ZmкiH^i*h ƬLZ}*i+IE$rhh*k{t#oPܳO[橗{/=`.hgۨU7;6}l(y#&no}Qs檍]hg8 !\ZIViPv%¨aE4L,a1qxJH 2S $z Wv 1"4F8E#/)ɚ[}O"֤5XD9/m' ~֊'Qتo!|('ogO`6g>a%qWo>\u±Ve/#G֥<4h(SRGWN G~B< )'-y,e Se>0TGd=f6 f+<\<㝍y޵ܓ(4F>w(Y1s]Kܟ6B%64q q&\}> ^4ݾ/xx֠-&YZ@W6ޕqP7}հN_;}+GosQ{le^Wj p b(cI`FY[Ac sZQZ~qqdغ .`cJF!X3GzoGsmQ|"sɚ!]K*m`u&I =?=>tZg1L((̖irKAI)x f8| ('IC<[!n7uj(I;Zß e=mkߦEة/^3mC3{SBsKװC Q?` YތUS·%%:{GKv mCC&(Z$t՘VLǶdO07Z HkVMWxng{QW{ڏ|FԇvyA|]N1ػy)8DjJPb qKAkMfB{=ܗ -KX{h>q_2VY&|ba8؛ad}S#m6 5:N3UMU֨ՆfRAy|o/v{Ӣ8 !Z sLC9+%1ڬZTBJWb'h4i&"r:<'balX0rDa~!q*@FMOQoO].owϽmFFVyۙҴ÷a~Q1|0)Nֳ9yA'wiXj0KD1f+R71qQ9FGIyhgТcW ŧ5M3`CK°IXE60gfb"`EW3ЃUMqCͨzdoϴ=^I T$Uff^B|H""4r (1K>SnN3'?>S1+HSt\ Kne6Oj ds 'e]z/w6/`los /J1t'{|j%/AxkK{5pw:3w^Z?DdZhOF.]B"ٱTEɔ?{ E`L=ر}x ayAj e.F_ScUc('ܓzί&~W,3DPЈDn{ ;"oJG{*ѩpkV嶛SvƏYgoWa6+O~Ǧrp3$m\d1Mf Gؼk@P4Kɩ;vB ?U?~[uFw4Ԏ ۥ9Vi!&ǧj1O鋆lHQB铯6EbY3'1_\ м3! SI-MfIM߰\f.6آ[K(H)_{_:EPޑzE.Ez'Ӣ(2`B6ˑ@,{s,Ā ^FdwK?:49SXgK J ۆ [Oؕ{  =rVىWiqI1;2սt|O~ǟ~'#Z:{ǜV#?~[FQHhp[C7%g&uia]!ۙscBMĆdxD7 Jh@Bg1uGЯ2_8Y:CZwMLtَmó`846m\D~γ{aK`vR. (d0 3~"uT"H8Ixji6"y!ī*(1cFD$,$1v3mĀe[,\;z= VrYwe?$bsj֛5ͰB)-Tl$:a M?\=,S-`ImafO>[ 9/lsPo4OFn{ٌ)ޛj!lyn`]ٚUa;0m:>&kR_@;?bԡ*8;`4V~o @{碔-܌\Zk/fōϠəO=_8L&,r(8M~elGs5}uH'[0f^>7LH@a?@$4,U2cΤ D9w sj]Șc'}WE1d˚珠"d 2:~QGM͇nm_gQeoPuU<67Tˠ#]ȔX Ar.nq<'"r'*>~"abyu=A7U As&` >K}.\Uy3'X >jNx&3ΩxNY'TQ@ap+ / 1Xr?i r#wG!!.: }_OTRI<]ݻp~}1ߨV6HW?{?⹋ި_QaRo\7F^vr!CCnNdbq-W!Ё 3J^_cPEPTޗg7 e{ B ێXdQ<۱2ض.(!8d~Toپeۡbifx~ -8ѡiws qgܘq i,Yc &-`SKs @k$:Wҙ`s`;|4[yFwI 2lU/!D',dMH>Oo=s>>u'XqD|KvN,Y6{u{%ҚED 6% Qc"lϖ;.ym>ѕZ0$S̾F;Z+"NH)tITiTӧfWF_V;ZP?6^>-'Z@'y#Lg\,A6c@ : mcf2ƿڬG}0li5+DZs"8*QwFkgOԗC֏( z(QӝG:`h)vwψD uW'̦oċUg#94}E#`x}OHfζL4I4,HZbdBoլV}ę߄uEpuƝO:w/}ɳ^y1YSIWFa%aO:rV(%!Fu-75 Atj~W&az g_B;>ɲ֤m'oPKR+P۶lDak!al=MP]`Ŀi)]aw5_نB(- \aJCPKwOgurk?_9z_sN1,ɡ v\ᓌ㼿?ZfVs-Ce6ݮ)e1֮.*ŘųkS86LdUNSM9P]|i M;붧8429"5#j2%3mS#/Dy_|m2 ~(chz1խ`xgzXzEW 3lw2bFe V( *#1װvhF~z! 2 1(@h7:݃@'utRT2.1ʹLD'aa;idNlJ67a/Ayh]h]X&tϪd;F[ќkF7{u~hP"ِǁ!uB`.q %22Izryb&cATLW.$UZ@+$< &u^bzUz0;0:.S~X7`~Հէ)SR J&ʪlIҪBf0<,hOx_GQe&n`zzfՠc T3e=O1[fy\1z8Ώ$b=&.w Y> {5KxB@ Z1Qu:BϺe*3q4͎pNf9gF%DE=]-|~__$ı~G_̗역O=O?s?_yk_q݀w@Pkbש \N*ְ=tXyqm(s#F.M nD6 35pSյ0Ќ80 ^M2Ix#k7(rBJB.E@JR fM_NfB֩@ yL/0 p'O:Ӂ)"Ƅ,gYI滂17mΦLlЅ>a %dڀ5਄=!H1;,W#s߃IA~Y;/K:jzkԪv "|NU/Sq|HG'pRzE#(:*#>I`q֌iB0mwݔxaaxzE#UDddj*`EeuInG<^e'RBTFM6uRـ RP^HK$)r{& *QEΕҲ 6Z6n.cjfBM>ٯrpI==Pp(L#KUP?wn4YDWOLw.|U%̑,UuxnqQӒ1eQJO$wh_rݯGO`̓yl=Ɓj=X\5XÅrqo񳇟% Eq~YLa6DIV공 [Dd& &hI"E3&O7A5To =ύ j`=]]١񻷟螔έ^>և)w;.7IMe+ۀ&8():r}ShI U 1-`w-HH뙘z+zvD71E7wzQӴ-HBDC6cASMM#O@.t 8VD|bw T&2q%` Vs'en#q(~:X0O՛bs yi'6bD"q[PY9Snm;V4ԥs e8qG>qzDД1D^^BeI7i'|uxcĬ԰e)OLj]* $0>b b9\ugGnv|>+Ttኘ,z$3̷Lr.Ұ/MC% f<ᝲr-3jG2}9"y&J #?6u[& msVQuYH.% ü :ۨ"[`1*By[$RJ"nRx1ؠu+1O Tx&]:Lu'< 5yڣ X= Rv`o(N *bvMGX5]e+f%I!ެ+1 %SEA6[ =B6\u> ۽g2.޹{p¥ +2oܾ_y3}pnL٘޲{u{@k O3ڧ =0Ny76s%la/㱻&wٻiw2;Wk/( IGwz?JgFm- Oƾ4MτZw*sx@@(|]jjp?+fUgwyd0$[靛tXaOGw+F)135;,(hRDM/ `R r5gO#ܤCxޠliמ~^wF!jlJ(+ItE15-x+rwa$n"ʼɼG >z6A"#͚ZiER ex0r*yŦqM$ZlnUpٯqhV_S+=GwuFi2j"[ʒۍhR_h~u&!B3+>m/nE!V./vø PpbHՓoc)B`/9g%Ij1I@U1 a;$B'5b Vˬc]ڸVjt"%8z&^gM Nz|9g)G};jȌE+ª9ԛz,fW/Y$/dqűILHQwg~H58Btl\HmvOE$Yb*-%2Am]#dúNs|sNzG%gKúF#1yUMfÕxT(關6O:SHNqE$/z6q%kooTr}HWCU ĖXޤ28{@O*goE>>y/_~ȣ^{ęιKB07댳wQ(*S潭qNUéњuncJs*]f/3CFT!EXn@EEfS>u3IzwD&Mh< 4 Բ " knSom~z!uqd չ2e랕x#'>O_٩Ժ@){GwDä贕_*U=k) 1V5?ex HXK5&"XZzˆ6i&aU!Z+,'A 1Vg|fTj}{1XV !..>j+(c< mW܏-@~ ;b]@R\mE6 3:btu6SqW<2V^y\4*W69j&fODN`+U';``Xm>)6Uנ5|NLH3D7HYΣw~{~3'm0XGqȎ8mcq/0l*{2%-?8L)-?g܍0a7mAKNm2k仳E&XģwRǜv'GsQgSjAdM@圂.끈x euhq)) 7I=Fu4J+tl´8[/S Ӌ@0(E4IRˇaXͣc8nY5Eg%Pj8+TS>g-jZ{aq[oWԇqFT~^ eG{*ʠ=~()1L7f0OvXy*P)ʫPYz-Q!3+lךģ:JL:bu`0Jc㺮w3fpH WHJ)˒,ZZ8Xĵ\om۵4IS#@ tڤMA[t ܴqv*GӴhQG?sGEC3sssw <MW!MK+@qzRq͖H%&4tP)`IY mB pj>gdOI)<>nVGD(-z^O7]jLo~߼µO^{#!?篾+?5x P*ewa{Oʷg)]8z[tn q$lX}LlQ %eb*thރ 糮2Sf&˅}!}HG_% )MY .ZML|8i}zS7b#z%5ɎH h |H'I$!I3MC%GLLK6OD_UѨZd-hqܓ"0.#b|_N話/+湍z6'ң[!Ma {߾yQaK_ι'=5H3nė_(Ѳ2LWL'"dI`O\=6.FIHo')e[i5KNl!y b7A:_a_ 0zkXQ?%8t؜Y2iWJuJG{ h[<!/2Uz˺MS%l (׸ry~綷rmMD=]-A&`J 1 o]y8Ϻ?kE4BUu$Rv"D4+:;ru}Kg^b䳴B*ΟWCT؝_/_rS(aH\³HuESxEp8!c/ ĩe8z f!$7XT$ 7Aw~4:u/_Rݚ&61,F;TLN1:C@q?Twa=Vc5\ AoUgGm ٢7Q3O PRzwj[b P)&="MbLV\sC1&1Cc#a]:O+HL}EFHn5tA̫m|q|fu06'TeT|sL/(TQIp"隄0:I NTC:S2R)z=3+GQ:ΜӞrn 'sS m;{ɨ GXCs5|#7)$M&+W*NeQ#0'ʑB;a :,4OMUB)̘Q7rYӈu Z{ Xd *LW:?1L*Rअ$'YKGFDԕDsliҒ5.CpȂ_+%GXA*L-O7]Jl~,Plfiٛ?D eմVV`;cq@MK#%,m44ָ ӣkoUw=/4]y~[1z@:ۘ~I54o{>ۜ] ~L˃qi^?yyOf K#4G Ёy_eri#h\vA=!}y@!gxX'.9&#`pk~ #تe@ё}#6ےz9qvO0)Mvd<1*@pM2P1xSޔ CeJ,xjC3".bbnYN`2wk6Lm5gDg0я|[inz[D fm#- ޒE.N4orz>[@ #BwZAyCnN(^@Pqe_4Bݱ(EDNZA$~;R¤EySġh7)MJҞ$ cp\Sʤ_9aE_ErNe m;hAӼzwgFћ_ZoF!I ͘ZdǁE"%X Rv Ge7G\y_Z8-޶KۦTeP?!L:C1^%Qwj|_Hd/Uou8 UOh6;Њ;\?s3ѩXe5[t2P}`H&\(OCN%vK"0_ A 4< >,"{\LLI{cwn~u>8FhFC*;H4l+oc҄d)BG`d9Coq`-vV狕3w:wh:3^jh* eH/:.%x1`;݋u8B0X_ kC:0#W=e0r]nJ$@dR>6:&ez$۵Iɾ ->v}Ф ظO'#U{xcR:ל\Y'6rxG\4ދ?wx7O_̅y9Z)~\m~[c~8{QN<}UN)iM،8Vmi Пr+^t5nFg t h|hM^{=ՉcH>,ՈP .죚AhC#P|مsI<Ȥn~yMzCBLcL Pw5?t8x<( zI,~Ħ+-4Ek/] !3$%UkWPi+AEQC^:tߏ#P#"]@k*ok/U3 .taAsvVq8ٚj]Gw`USh>2~Jzmd$JqW.LjҌiD,nHEMESTp^UVuJҴLYTGn;v^㧑r S]ʡ9LA^&,tұok (16P|+:,eڄ~ .p}nKe _1B#:a 5 URm?A%t+0:Kpf>no5Z=l>jV$c<`@ȒlvG]~]|sM [UF>LŮhTl}PխNLO jHR44l4HuH}^ъ\)Jn{T,u|[Xw"01FVhYb<*dQ Z`=)N;2$|ׯDŽO _ci)O}ǟߞ>{Z -$|V4SD@7<)5g@dȟ"ql2b% Cel 6" @X EH bͣ#CiPvsഴ[6SQ5KN:$n^e`Sg,%xm4k L*74k>iWZ I25tl hKȠoGȵ9/%~R'Yn,OND>zLgy=nķRV\jmNGi h6J8̾]p/p~\.VZщNu}’ָ2̓oy m[n}o'?6Qd4O ~P!8 )n]ac׋B{/֭4M,FYȹ58ꙏ`G F{jX0O5D)ٶjTDx;9ƈg8_ b1* ;a(EKMSA6WG 6o'NL cM쎱L?t}^O#NsɸtaZUĽfӓמ)P6)3Tb`Ca rUf \3QYDnӒ>n]Г©H0{쀫u|-&xjmF GD6Wү1䅈& p>'WFb]\!u:m t6ex~~K/^)Z^\qr}_g_\}*^xO|:ӭrro 4TK %8m(=fT-@VAVoH9lU4WF$]XCн|Ř&sR>kP'=B葍v MiS$6 M(yMtӛA(faTaȪ‹l8vN4S?aq37 svlP+o,AD QooHZ~: LO5ȼ&F@Ri4[dťg(,=cһZR (e#fqKt`V`$B̀$tI4pT8Mi6I3D# Dcx9VuZ%٦F-8# 唙Fm r&uiP~ҽs2`ّ:bu4]/ab{x;}V9GD}=i9Y/.Z.h#$sh|s,X'c#pFqق336VcTMP Wwqgx>J]^ubmWzwnޚkC?pQly9+9nH) -jUeT}-t;sHZ*},.l_ӣQ9!yu>_(7SњZT`}JpJ#\=+-)){hL9Y|H)XۓSq!!o`'umb́[Cˑ܋]b qJe$-r 6v OBzr^Ԅ3'`Y>@'X[i endstream endobj 14 0 obj <> endobj 15 0 obj <> stream x]j0Oc{Xğ"Pp͸j =.[h@cfįںw ll.W;;e^ș.x>wqay`lΞJoV=}U3EKo^, 0ޮ2, V`{}/p,oph/Ʒ8|9taFE'܃$2c$+F:UGRTV&mHiEy$eDq ENYD:=n*A2:IRH/cRsJgztO%v-{cZ{F7Mf1  endstream endobj 16 0 obj <> endobj 17 0 obj <> endobj 18 0 obj <> stream xkmqzϹϹ g83t/91C1C˒(HÒ,9l"A c$ı #?IJEQ8 ɤ{fξ8"!zuWWWWUWUZIML?|s_yItc鯼 7~?I>?4,?/|3ͷLn뿜|6kM?4O}+?cnMz_q>^Sh;{?~5}+RxWE??bOJɧf^즠wyeGͧIfawͬf<*ø|+ͺ6f_];0˰~n0@o4 G8s76.krans //g\ȿrG~!/B+y<ߛ˿q~B~c6co4z뿛˾I-JInWu/H9 riBzYg? ?a^>TeRVx_ \>RziG(_u>]*}D'+/gx[W2<^q'÷:o <[q\Wk“grch-um?R뽻W|\Z>Q?_^?Sۿ+Yt$øwG_Xc-Vz^Z~R 2\?m_ʷ{>yWkYR:2^ex{Wk+>eri^-}|`m_xxm_llZVp{[-29{PL|Zm{ZۿM|,2 7j_??Y:7wu_yk]矩s~Q q#G~B? $ߦs{PvbYeY<E׋}W:םb+.tno)kEw/Ge-(z_K綮Թeئ&(v*-؁B$W?^x+gCu> ҹM+YӤ9u}~f{TۗJ۠߹ਖiyDʻu6=L[;Wi۫{oetP[W*Q-[ʣ:eT<UݪsN*}eBUyVJbgoq9ҿ_\O jG*?8o>UqVG֕'0mk]|jXs<7Zv9>J[qq-;3dbF֕u}_qc_VF.MjAj۹AsR{,*?Q3uZ:Zi״^e  aTά |\'곯z ؏pz>u;ki6ʉygχiQ Zu/2k{aNGvQvw.zzx^Cԋ:fz_|n@EЗE=l0nuH%>6T7Ιˠ9XU^&=ށ9 _LT'bY7O?KE䦆m<7l,z"NjRw2uOPu<[\{?ŧ$wui#,@ϮT6<4<>?6/c,+짭|\qCkk}RRKLCl\˕ZJuVJ#J?9E}\8f;> bL,gl&p L\sps șcai[d>2rT ɾ:8Ժ+ءRaJNO6ϼ{G;sl tôKpJ[}qp׏>6gAs[u+t8߁mݹ=O?}owC;=׬%Af-Sw/hRo"J?Iz\{۹c9Nm{~i@Oto=sb85]vOcOuz󌱻Rx :Vejӱ xw➴: Ylۢ~C6w{js:S_"<{^|ˉesyGt{׹I!ls6zXk>pP=tx}FE}=mV@_,msrSˈm8A99Y}XNR{L|ˁ9go@9MR} qN{~h,IJ^[z^^Nm;J>^i$dX^ ^eNM7y4[ޭ^O?|c>6ek! Gs=s@D<X{e1嗳۱=~>@V8Yk{_WS1Eܻcg8 >7m^,*m@+uǩmkA'l_ g% >T6qo@|~,c\{JyyIp5{BmNR{ > k8<ߵV Ck1{}Cx}XY_SO2eڨMy% yс.OUz|<:s{[=1^/+iG^>Ǡ>{{DgLF8Ϝ}If鳴ݷu2}>M3#6u5Hm}!m% WU6J>S#1_O[edױvq$c]k/q*|^ r<:B;da}bcɎG}֬y2\ RH(:^QNCR;~Bgo>⻕vëikw|߂fA`y~%mm"yiikoK=[:COMY~Lmm/3,[ ]q|=igFLmYUg-\p-uM Dz06E9NK31S{HUy6T<į2}RV`-S{|g̼E|]ZfujsSM^uڑ=?k8񘉓ٳ6mZv>\,]Y=yBu/d*unng e?qJԺS;wZӴ ֏Kiv[QVte6vfxZ.}¶3S"'s=gV…e ~?3]~w|3&}{OrO.}-}݃~>QR9}NC@u{m'/B[*bnG_Ӈ;9&=hMud;V} keC>Y)/Y{e]z;9m{y}8Hu[Mջq32ǴvU̿zazjc̱Ynixpz{McYfM(]8_<.ڢ-~3gn)`kd"ζT'dsW.~{gZ&fyrvq_ACӾzY Ju^L_Ӈ?uU;_n"{ޫ0e(N7A˹;unc[" 5c{\RhLнPto[1Q5 =t]ftn2HkY.b ǶIC63b}^Osчر~ ?|cRAL?;UuS.m;VaS8мE,'GԖ=X xŹe*9Hש-3UCuԞUGx-F/|FPo?/ث<}=~gkmm{lbl|؇|2lR}l~ƴn[ߑut}^,Oơ~u{XpCk®g95 uc~̾-nvP>lܨ?v u.NƳ1B} }>!F7دL+^p!ssG8}LC\4iSn;V-ta>'VJ1=3J }{/Ujۮ{Z(w66YOj?v :=ѳqjM&]ľZ'j;ѳo58P?lYSVavY/hvy( c=[>Z{&R[8GM/}jԷpΎOR[7jLȊctإ c -yxWl 8Mc~=?{m'ۣω*\kUDD>I{}z6ms{m 9c9CVpS^tu~,^קorv=sgb?T֫3ݱ|zlu(<;JmYǶ51 eԖe9W99B>e{Mwqsa|ˉ8]C.sFCĦlk԰ҴL{Byna \~!$.|sEcWENsR{·ڔy\Ўo>"\}~EpԞ':c9a>7{?saqM&x뼇=౞{^y@I7 zd(aW)ߜBv'<uއ6ϞsU{DRZQvbWZ|& 0kgenϸ},[>'?#g߬>뼱s~F_RmO:b6tY;s%gW=yrFyko~b {Oļ53}M'o/,o MpgLOl^6{3_K0};zlkycxg]^>h6e-yj9 Y3Ќ fܶmeܶж{.Eݰ̘OoQgm_t ({=cZdr}?0ts~sa[%7s~$br[}dcy<|R|s#/b l#2Գ޹ :`wd;>vu]7x=ef^c=0_&4֍f^{ Aύ>]tk\}]|zn[ M?; &}3) 4__S[MǃΡE7q&nrxNW}q#7Ou]cwyz\~gȻךӜ#/_wV7K_wv?ߓs~^8d;J[?n~M4A#3ڎ;?x}s+1b<ݻ-e=p[1F㺟Yx _UD#u|6<+rMu kE~;S0FcLS=GGG32o=dty>c+u]|v쫍ψ ،RNP9C=_! ⾝x?3dzJ_ԟ>Z)>~»<;r {u>֪w3ϻ{%W<_?۞p2]7ޗzL@\R9kkhecڼAfXg۽Uh ):݃gG-w93>?/p;Gy0WuLϏ9=Ϻtù˥ _.:lw.΁q3׍u>f2~q ? ճ~4PW(s~Ɏ h{w{~>XgWxH6gy?NcCgǥ=Gv_H<^w@/!sHS?ԍsųvBn5mS۶E}21㴛?5y2hPgJ¹K_v{_39O8 yr_yk"ahq:^..o}r$]abp-;w|{9itQxc<`1k hr>p^^2=?֏Qjۮ5 f=7ϡ10ۙ˂&r3`Ly7#?8-5skSm{g@x.}<>MK1xm.nΌuOl8O`/m'br}q1C}ym_2>mBqpo+;ܟs623 m {Udξֺ$"#L}r#vn䙞G|Zo2n; ôxND8- ,~{"1V}B8[ww.:kǣ=yRϼOVP=.^H k࣏"|_Ct7>8m؇s-*k ; |m*u}g.MjzxMy a3%:ߖIcyk_2s+'>B/,G‡m>%v*9O~ur: ޙO~g:"ԇ~CsE􃣯f5_dk 8-Q_,fyn*]t^5]9Ǔ_O>Ӂ _;Rw7]S{~c(~W] [y*i}|^p7m}{r%mߙ;>u;mA{˴}}VVն=Da9Lm[c8F au[=/ԳG=_OKqLvKbGA7܁2܋jc=ׂcOٌW#%+w,6Djq<ņg-#OQl{,ujϺO YG ;+,$>0 u\1csY*)3)ZQCc-7"ϴ8Eĺ-5w@.v=\q'r> 2qTͺ 1 X=D;kxw%kBl>|7N+g5iuLdO'c#_ŷ[)f;3xg5m{+NU~.`~6vil<7SmmsL!qx0bpb-aïHf"w1Ic$徇qj1M?ns$1 c\eC$F9):|S7Tcݕw _ |8wNiFüR'#{2~]9?>}1E7w ge,HGw&CnB} wAI<\_ԍ]y=3m}1]<5oky= /ډQ9#csW|cMpc]/ \| =VEY[WdZjysx c3-?bE7U$9W?nd zP>ܱ;sK[j 6$mcs*~aG|>K4m& kA\ 6ۇ~}T S9 ?hds{/xAI}:sgFݰ8F/P@{XE#GBuT۸k{vȷ1/YO;]J8Ȍ,:#N;c~w33'{:!ݘp"6YjukjoS>Q{*wcY垼Ql=3dWv26擽t5Oz6nd;MXm}Z}|R? e'3׮?T=hOu>( #^pRl$*>6_kKE;f8бsMFjFuθX{꧷Yy_;/jk(ϾOz gh#6u>]c8vv}lWC?z .pVb;:N}iS{\Ӏw2j?Wcơ8u['P;㜉} 'QǺo>F/fG>lÜcg38Oe}vh–'u^gtQX3w:qĵ1{ AY;F^F{1[O+u2cwnqrG~;~&6a>ʝ B9C]<}a)XK#AԱ0tX~Gzn<@Wlq -E-AB)/lDϽq_C&xm?K^9!h(u/^5PWuo(ߐW?UpqJڞISy8ms0B JZf}s~HT]M[a@[F/2o<ȝ:[ kDm3vxHnyCxx>kuEz<׎C_ZڤԦѶ[}41}arNgyahD;<c(y)p[}Üi~ v?6ͽ}d1QxB_]6Wq-QGR{=2uC_+mǝðy)ukx Wէ}{qg:~YAb^ Z98}&6Kyc bahûȎ|:p>P)X ߪnB{ϰ >3=c.S&>'?>wqoj=I@H}>fmp\R?mN9h2?i{(Q֗nC:N]>G^c@ˑ|;> { ؕñ*3^C?DkbG{htўYߝ+ug-z1SN<-xOr<9^ǸoONԟc{S9RiwogKb t05~"cq,=[,01;g39@3ݹBs! -w\۟Gh; oC=gOIJv܏ci̹yM(|s?=e7n똗cۮN ~ePo1WXm=ܼ:<Dx?4mlTx?Jmz}9= /,9%g ɑ˓8muӾ%hR;_s=s@V7P#qeOf{/D﩮x͸|6qs>1h?{Ec?ExbuƱ}b<x:kiOemվ0m-{ӱ 63ԖelC..l9 >Wq8֋>2~@å#mmsu6:"x}`|!jxh#%/ &5DFolC܎0>[\אˠ1 2M9D8O?ε{lx` տv#nqM sq >{~ :fl}84PFJm}[wU)N@'3rϡu;w=m4;eϥ\!ד"aRڢso6b/l^$cgs$ G|,L|?<Bu'FjccwxlS3gC |>{Uϡs>(uh0~Ӂue~z2~0m6bUq z#+X(?5za2ǘ A6~yn+OCxcrkw>1gY);'e}ƼyOb.98gL nC^GOg; s.2Q997o21bi'csۣzYf<]ejkb>z8e{=/\s/]Ý"7z uzMS=oelZxǺ ?7p= m:ֽsR18< 8#(pr:뼺smݑ~"߉188Ku$mыomȫuI~Pv:.jtLmz 9Q}d e]\6׽\x=q±8aMt̺5e9Fqm>zx:PØ;h5>[)[gK۴[&>kajA'Y1to:?3oop~vgzKe#q M. yGXN=J׎_x@kY X;|Rݩh'>[@c"\y~1+1V8ǎ,8fg~.kؗ"~ݵ?6k ~zoxvrvMǭ>/{3=s{%|=p?SEG~z1}j? !FW'[_g;:Oq}kRu{J#9TY!4<2yIh1}MCC`n >{n^>#V~9o mL=eg3jc,}[e2i4q EcX;ر\}}vŴQjӇq.ԟdv|c8Yc%'81x 3e5|fύe\4+юD&{dfN>=gc3׳{ J~~:'=\6wt9#|z:m=C&9ZMs S?C}mu. z3c3up u(怜vx̑x) b=gY7?Mv_!ZppgigAyOt}v5bgi{2~#,(m5>)k_8%z-ı ?朄yu=ځ|K;r:A|g`ްG.]g9@w`[.󰍙 u|߁&.0He 1G1\stQ8h&X{7 hRڭ!F8ǽqB{qяAj$u>|q}|hÖ"lu<&_7Mm]Ǧg?]O/]sRgc^|>~̋py?̎^?C'=NxgڮAs-8/3zW5g*>s@H=8s/m6Pa+эiC!w:Oox|(yXC{?z\Rx9uLb_~=2yc9HmzEx.G;aϾps` Ϗtc.$X _hm^Gۏܞ6v{q6/@穽7@9չ`T{M%\JܫcCQ_">pV-|wCؓYvyFXwV y>eH? W ,cOQ{.gþ0~'>^?y{{]"[\/_xpyqjϐx4PgLx"Z/ޯW3蜡(]7RyHls΅@[Mw%Nq=|vx^g i\9u##oOY:_Ow}Vyձ"td# z !ICBǾ8a>- SHa1L;=XO8qlvsl_{v~?O^\?H[]<&{Om~_Ι;nDϦz <[s1q\؂i=q^wwxX{WyTlws^/{wlS`~R:yz|vzi<1,,Zu+'2Xv׉۷GfMU{u~j˔zE9}S|]c=_U,a|._dLcjWeFC;1pvjrﴎg yFv+猍\1Uh"zO+- 1 7Yޣß,A#' 3 k=~ ߳]~ZqԶMno5"q8]ec~<'>w O'C>WsrxyQ(maw͉{ږaw*x|sRnu =>;8>˸eW\i{kulao2TcbyC'c<6S>Ctlu>җZf|v뜫hK3pww!|6" W6~vgg]3 7!T=|胳c;jK_>>Se<;sODy-"SNr=km#i ɸ%]c\sD86^@똓‡¦1} B)msڏ3Z'|`=ڶ΃;󷿈khC\bsF\\C~s[l ÞD|[yJCėi{mIq3x>" 9| wUw=}fׁ|Kl\bl{l} yrȾ~Dq(^XgcO =vCϐ]WឹX9Iu,}?:{O3Gqk]P6:U (E'1]ʉ7*zyN.6^뜥my-dz+3N9Cbl| r^| Xf*sPS97Kx8X*1zl@;sPꝤm~7oW3 R ?r>Ϛau⹡yղҹnqVv:!}k62Lus0is>rL A7Sۯތv-Uv}]s6o/wǕ=[~2wR[@ˇ< -<wdJcd|:߷t-|U(ׯsy,Sd}oyb}>!*Ph;QzW-gxpzJw~-f:o cx*}|#5ZCu>j=xr~uL/y+o'2|§U<+rJOfDUl_Roƫo'|-q̳O=y~ī7k7>޸ï~_81{ïO}=o4O7SkHoKh^4Ӧߟq/F|3x2|B~BlePҞ8lU\eY 0Ƀ^7i:̓8Lփ3ٟ?X{i3M^]>&ڕz?f~H/W[fpfn5;ߙdNNQ39b2̚^nt;8t3_ONo-)R`7/+5Uiy;?Dϓx7Rq`f33rp9YTi.<>{pο~5ӟ<4Fɕhhz'{Vx&dYx/}fd_`f4Kz1|tdz7Y<=eMogfmo^ؐsՃYnر13J~?|Qk|/6k[Y,óc~YFq7o wtzE!\1Ow OC}h \ mԵ?q'o/+^^Qrro@Q' 9#5]_Z]K-O׋fN''Wzu䑟:[{A7~~FgQ/ zGMso,/?3a&5r{ͯ5޼|槛/6?z 1BLIH2wO,y=ب(͍JӢ*(Jm33Nf15Yi9϶`4ܛ\Jlzp,gRz_NO?̆b9_;Ygo5UOGiVhYʷ ,~hX/f+қ:۰)C,˫u.§qS^XGӯdn awk]{I6KH03eԟ'rp7RtW-nys|;GOnfO6Az~<cQahD0oem}GjkW--<~pnM|'"eo;Qs0<=:tR-3-k Mp)O_9uͫq'ϊ`L|rdCt}psV콟{}ǛG_bTpѣsOe#<5t\Ui5 oمb ;{q6.Z\ydZqz]=H)WlCf9]:)V5]3^OzgN6k]r0`4LO}J|xtX扬Gem9@{ǧ3_ JYjx4]I9nrm:?e'[Ap> endobj 20 0 obj <> stream x]j0O1aQDصh2@MB߾̲~$I2UAa5(r'|Mp-V*sBh`tjΨ$u G_7oԾ~Zm#+.A`)k9^MU5Tg?7F۰' {3D:Τ&(MI'RHT Frvi" *U)Jq ᒷ\䫵B ok~i endstream endobj 21 0 obj <> endobj 22 0 obj <> endobj 23 0 obj <> stream x `EOwuu{@¾ ;d T @@DĈ&.*: q\B?%}3݄OuWwH#"?=LטV0v"9⾡c* A{GJ?"h=sDZyسFkh3 P ֌賗lWfw;ixwA@couFݙ & Qo.}>h)D,aRDSD[h,G9JkGTzGT\ jcZ?Mhr4Y`B w`5a%(l9T/hY2+i Aۙ`lp?0iqؑr!hq:xQ͜5uS !op!SS01i c<D,KN[9fdw Éק"o 5![HFf`鶥j{Fn#00(i;Y ~GvMi0 mo= sv\; U~ ||IG\ ȟT?@1-t7қ`-|)t#em2Լ˵wڅ{cNYzȽwSi})nq:|qP<֋ٍxrx>X>+؃v(Bz8s2P'w'@nQX HSҼ7Az|v9Oc9)xyDc<1Y yK^;Z] 3폅@=cG=brw;hw Oчf7L:){\#^\Ntz@ԣnKꮷy!#$.'aZ*; X'@.(g[z<`ss2Νo|V^ӱ` q1(y5(+s@+)^#Xtݰ*@E-)jnNC[#jԌ]Fq}Zxq-׆Kx`,|T门6+]6M`3ثGX Q#;z6B;t&ApӃ[5FT~e {;V>+z>)>-Q#J' =7]ʃ|A~.w`cjp_TZ AK-WNB/'! sTY13.y$FMN,[O[cr]\DGo Hu j9vNgLQ.s1hyW#\0ΦbL[Xk;~>Q9EySS ~E.Y|]`?oaΉ0P\^{k%J/2bMR YZn]]f {<SFo*zI] t uUkf=C5kC++4*$_]NOt܀>ӄq?i x̣V&4x1!o!tq0kU»vP+9Ԛ9^3n4 ~G0O9{99{72yޢ۝0rw甤q͹:(kC\oL;li|#*}k0ksmhP9WjJ[Ul[?H_]'>AZᾓ޸VEy 8 Z^+79S%gyA]ϯVa᱈ UCe:<@Nu=hf >A)E7쮴 B_cl ӗ I> ^qO?7nȑw*@Vm>W>:;zÆ >1^(k@ASK]h7-y>hq^NjK_~&A~_M 1JyyAY۩i6<D2}U} tp|]\[=Yc_1U03Bi*69|OR&_t^<%QXٿp ‰`{he-&/(О^h4 NW3i~Z;Z"ůb+N74on\KX2=!T04]N]o#XC:H~H ̗΃|+ mO cFwm;(v lv3'}t+׋˄4٫kIA8mK%6 GeX{r;rjW`եQ~( _\E}XL > 탐{>"CJ/P:[b> Gh7hݕu!jcm>\C;q$jn))c=ZH)6R[ yTN9~:28]f<A6[a賷,5Y7mDw?&)M)SRzis=$`y3a+eV|a@eX/&MP]|kG-n=ڥ@[!WN,;LMT}3pmI󵤇ӦLgţh"^zQ8׏75m1¤QS/1~c}\p yH2ypGBYRks7ܑӑ^Bm$+#\D{b ?s}@Ȁ҉Rfف*~-:qsওWш Ha're.Gže?e/?LZA$q=h1f`EdW"r?ێU;$qxͥb'/$:WW{!Cڞ\Ds-pU iZS-!(7|c̄ <鹗?yހ75͕NF:D_Q=\7?CVݓ{I?#}cC!ʵO uW{N47 iݕq<]Vǫk㤥ݕ{a>]:0>Yxs/?m3 fU3 ͋WJ5`k\i_xWf;^l>|GW'( Q?֓"|@7Ld9 .zocCPvc:\ߐu 4%ehpIؗ>N ƴLFP;&z]DěhkfC'9S!<(']es0_ MMM(_H1pK) gJnkrb0(}*u)J=ϥ~>ZW{uG-2|HϐB}(PBB]Ke)OJyH~V|]}+|Ac~uNYJQ"V#%|f EZ==sk -aEUG>K}VYo\EԳ;9j@|&|+{Z[~P Ss[*C9tVNy)=b3?2#xIJ3@4пTKmez{t5?}R@PO`Y tftw.;c!Nεo{y4;)EJq(.0[6Dj<_x֞ua=0GrϹME?ʭZa_J=gpH8-hq|G105]ڵEQsa([[^7!Lrz` "O AOg^ata\! ba8t q(ʾ֭."(l*h{pa ّ ^^NC͟rؗ$aLOɁ /1 :jf,bdC{꽑c(kS6[J/1봖~8dKyxY02Y!49nчa𴎙[pH3?BK/? j =TWe qǖQ! J``}<04:MWgu1!GrD ?9NُHdBgw1>ZD Z{43 f&O{8 Iͧ-Ie= fSXBf)au (+ Mh&zGi=eln-D^(k:`|si~J̌?4Nxmi=!q_kT)o4BأBM-+)ck:xvn`#([).q~W"FsN/ʻQP ℗!4?,鮅?Z`7ލ9``rJ4wT_E4Z `cn:ltyo6Nȗ]9u|2@31ov7c)_R ۯaVp,qZ?C AnܪB&ǹ~r?~`+4)ƽ!@ZhOŻQV62agQe>m4ӣn ^;hךp(_߾\NU֑]cO'9}Յ|cMt%m8AZ m@> 釜ru/o)e 癰?: \z(tlG+VԕR!C HNo te!hA4h뺽斛oui7qMBHqD+tܸ^z˳.\|u LwC]T:`r9ΞRR2 d:uVN~C zm8@ `3>w3Tę\7-/,L}-с9h>p}ǁ,1㡳zHGz m#60tQm'K2A7 JF<;OYxO9z*N|Hq<\xߢG`e{[LbQE'~սf=Su碟:3WP>]Z gPpw7 ?1Sσ|N*)w֥L{߹WZ ~sj7:c1{{ǝt' g,Jk'bMGg^Y]DCǩ,{`G8_u0=k 1]'y/LSG{mF/YaB6VE&3FHiCb\4CN@yCzqa7jvָu \(h%VDy ە4GüsYg%WO6E_ ~xu%ߎ 7=~{i_Tgy\Q<%b+A{W<*8ooFAE4).]㳡:K za3[ﲨoJ{ZQk+`1ևt|v]V77Ņ-exN8_m 4/TΩo$Z 4\!izOxIrHͽw348MgC~yXȍ,x&k`ʱ_$n#Fb֫(O K$i);O9Ի_7wBlۍ96O%(X1Ɛ!:~a.7FyN6ƞ:ǮbWPUz[;G XW!z d{GYki2@nrku[v.q!LOMA^t5oN50o>+:] ak9}WPەw`%Aey<4mTݚ6;|F_G]eYaMjq7'0FCxVnz\4@~硫FTbGW9]/ޣ3Dy"{R|Ay߿49aɌE(Sn0O-k(F67t/=OEVB' dycq+"\{y*qW8,s}<Mqe'}ծja =̻+j/Ү޷n\,;HlhYwоp}5O7nO@'.8Sa΄>iC6& vpZoF{ J~ gUQ,S­ tT ϑ9пrNP~W [-UgXhYY\>/Ȧ_SK|'At˿mΔ.GPw\@ᅧ_w^Ѧ0&@=ky&n:G뾝~[ Z#¾^ymN/{ o$fc#nc 3D+;~^|v5U.wns#}lv8f-5F[gwT| |M0gwko$[ 'yx' 6Bw(&«o2&yWoCXr]K (F8ۄ!?pyJ/շ5{[Tg/ Bt4_Z4|^, 5(R2S{"%.}W̌wDD|/L}Et‘Jz-Y}hzZjn^$@šYUK;ڈ {.hE7bXuoјϲDZ4,gVߨd ΢~gYL~%p#Y">vٰ +ۆXvdtwmX~,7^q9X1Iәh :GW*HgJ>$sS)t?*rsQdb0{S9|_Ȝ(* =:o`g(Iu.Znw @2QG턹7heJC5'wM}j4lfgUϥ^1o{7*rHIJ<հA^^MC:OgAq2 ɭ>lDKjX]-v%[\LH?ď[\ߓoYdnŷY|Ȼ((b[ڋs2E=GgLqEEcqds8%>OSűħ?*$NG:$⏕G8%*@U~owgw+wʊcmoM[ambg1+YmضuduPZPlM3^M[lJlmkl)C+6nH3ĆrC)-~BXol-֕kmX&E<+%ꧣj[<-V=UAOOU+ē˳br[dňe,K,]+KbEŶXp\XdpXf<,bFr-5 ΉsĜ(13,Ԭd13^7ev@7Ft@7ڻR*,:&u-HL8g81d|i?UIG;"U2:tR9th!)(K6SpyO Cj0JSAGi0δZ;ziO#p=uW8Q-@^n[ZS_E4Gw4 9v3JB.bk{RDES DȂV2ũ\SBm#. 7E[S?~[3vVUm]b+_@"D!B"D!B"D!B"D!B"D!B"D!B"D!B"D!B"D!B"D!B"D!B"D!"tngZdQŴh4xk)Hޑ&ek'׈1ܠqRY;ˊͺQ>,M1;io!cD4(ɯ *0}qoi|ro-FbMkċMzˇ_>79l"IRhf*Py;r)eDTkmo8 o:G1Im;Ed GIۋlCϖXU3+ Eq[]V$r[jj[JR>yx49ek]'׺1uFYȯ*k*T$WGKi{6~iLvbu Zr8aVط[nپuM{"x4`|V|ny.Xq%k˵@R2ݚlL*ͮ`dT0*dh9JTN*L8i}A4(hڌ՚^W;i5m e6Wߝ} u/̺p#n}!''g}˻LjkSzucߨ 6ơuhLZ#J,6zĘlrv%Ikꖭ\DBʵU f]n}@`S5̅3yq_~~.M4FաՆVϨa `ԨYUQfW}c(RAnC7۱ne׬R~\$Wۻ{MZ'7V-ի u!J,{$#bH$lGZt*>#Z(T,FU,+v޼x3GZ祠.kܩu;HUVOC3&]&Z :ӯj 5}˃eriq(?u 0G(^#C:Q-> . dGqݤge3ho9J3G򼚨AZ |11z Jk_J+JJQ@ch>-ͧo*ZW=S?(7&Z"(T^KՒD ﲿՓ)gs9Ge b45>%F%]L/WR+ZC&2MCB4IO܆;tc1mYڦUbvHbWQ;bv`b9s޼6Ip쪟W5AD'լPcWcG˗`_x7.g}sܹT=GOPk\Zh=6O>nTV} j5#fisYs7v$o_j Z%zWZ7Չߋ [ 5 \ȋ6oKs:\wQ-Q0VKl9VMfRJXkYjJY1s޹u?}:pϽ:}]6÷6թӷoZjkRM[RM_ӾDfScEkߤuL~ 4=**`[>KxG#tM$ 4K%'>U%(?Dt^A5*JJdJ֒kD #Y4kZɾk5[fF3_&΢fifNZ˸E6{[|}}Qv>Re#͑(ШGǎ$}44s5 $4ѳ9rarZ-՗USrzҗ8+ymVl06ߛ6FxxMnW#IdeR+iOKҒҷyؗgǏcƐ,1-Gf endstream endobj 24 0 obj <> endobj 25 0 obj <> stream x]j0Oc{X1FDgq+(=.[h@s2q2M՘qcu F[Xp֏z˽-n05fY[77ۃͅ=}-.L`6Ƴ01/M|uhz ~L8Rútlg.WWuK;] V*H©RNyB:N;$ PJ.IQŠ)JbR!O$%(Q;ŹS%GN $J%Aj9Fp)}Wknc˼0̢Xc endstream endobj 26 0 obj <> endobj 27 0 obj <> endobj 28 0 obj <> stream xdW}73RMTSSiJi5i0 cYB,dVUK&C!,! Q$˒{8$!^u?hS#1?{{;)$Ir y&)&3Mʫ.}6-ď_&H[~vy$)\YJ #?S}$)Tk?#ozϾK~)vs|!}` zO=ٟo|2_O=_\Z|9y;)d7?$_6=_G^>ŏ<_sߵi_¿_u% 2ɝə卣[Nx(ԓG͉~+{g Sh~3L\LҶ,TRRIB5f n/̧.{ϥ\r\2o$)'M.CRklwӹik>})7q?wvI5ų>fS\Ia9O=(|!9>oMTJiLa9|2L>?wd8dٕp\ YO}a _x_2P/\LҭI=IowωRN[)tR8VR¿Jk~FݛWGPMϦRh_)'oHWSx[DB%B\og-ߟPM˾ʨ^]~ox]?7}L?>뾯)Ɂ~ҠLdb8zc)%k)ҲϿŽS)\ƺB/yw K'<= m6S0Ow>ÞX>3xo _x)/O7Ozh~FNߟ^wxi|_F4Lޟ—pgsY?N܄Gd[* q6eƃ)|0--I/J~Ət{k -+Fr/۞NٓN߼Fg<}̷y؏O|=S8­͏[p:vӷ)Jh#wq)?^{6ﳑ߰,q8ON.UH8l=vݿ&7H:<`#]ʜq:~<'юO{o3wtG=]7" ~ϟ4p8fo65wrfLm{Dqt8~˿/~Z om]w6׏O#G>HowN>NJ6sOy+x˝Zt~?gLF|狞~OV=^Z^~}_8,sѷ_}U?_?=;眧Mj׮׍ybCYsYio˷l\+o-_mϽyI}߿~%~]],o ~qe[6[u;hU7k{tοCﶟϼYܡ[GCE_5߯[[1^<ƙsqPs~s^1P׈dݨ]+m3OV7xtκmvwmL?௎3[ߦ֘2roFP,/h71Z={<'7jVcV m:gƯFe۲ulm[/2~iSэh6#eq1o4w3z^}'8aCy~y;Ŝ4g!m6}}׹k췦n:s= ӽG{Əqٲ+K|e}%ߦcY[յsw;qƧ-MsS>=~bN==+==w<_ЯD#ZWv0_q%~œubâ?}?&;'XU߷p{g MO7V͂[>混OowiT7Qݜ*~~M_6:kwGem E_K~m͖9✰o133vެ}9[? ] 9j\|c.X8Ȕy | ~-3ƶ.t`/w?;zlcfMO 췞 7ü?\OPګ|ksr&Lxe=yܲw,ķ*o[OV-Zȍx&8nX{] δ|?ol}_TYmӂZp\sr!ʻ8ydϣ]Ci|Mϝ'yv~9N{:=mAnB l7~a<[n_]ݏ,{|οQ̟=z|M }{^rr36BABl}մ3Bsy0cA5,ZaU sf8;~ vbhY}wɂ}ױ`mx\K9M?- 1ZE95O&.kq72^?]?ob< ?P@_w. mO _zB!ثu?kZO^cn%}ڿ3'x6E?=[~ '㻹=q^5v3Ɩ_u /G^Au 9֌N#mO֤}o `^~ߙeor6fSm RAB?%0/c[aB)%_fzShL^|i K΁\ ߛ:o[uZ5~Eϼ皌_|i6OސvVVƇ)՜o[h^3uh% e]i-,إY yt҂͏c̡"zegCˮu sfMaO_!GHl₅ =_nr gЅ}߆Q9GBa'V"e+a1kO<[@G]w-䞡kc!7L882sdotuID.qsqf)EϘ5 ^3g!8e8=>7l]1 qfUnCA'?PC>Y-2m85XI_|Pd b~gEpwe*L'l8c|=gs:{Zz/×u~c]:xB쏲;-ӻ;aMWQKOx@7Ė}'~Elނ\!w]{zZۀ_XYϜ qE_ άk"י7xϼa j[j΂'nsMdlUro[苜c h=IyG#Ϯky!eUg_긻n'|hXsk$6@oS'uU@[h<Xtm bS8[CDZD/g2o%t0ZϟqGĈC=s\|ú__i`(M^e9M~3?/uQީǖRב;*W"JzdTYIy,~qVU6M?U,["6ڳs#&, k5.>E׽?.;>3\7`W- X5Cƒw|"-}|y5.}u̝ssmAlBRu$AK/ʾ>ЎX?sdBܝ7ot!/^u ~'g:չ1ז:d<0s-kWκМU=[ՎSAi:#/TW's  2[=e*bз)q_mes5Yj/J,)rkȎ9־aw F舟!8.7oYa5 盜YBn{ e 3]{?d`FGdj!+?Efp~~_,dޫ}GËMC-r8Yڻ6rG`= rޮ9u,&y+}]s&΍_ odTG -gyG5G⢌M;+O Y3>=8װUwdMK@bTQޓxcs=lG/WI7@̗c#p>XxInbfx۾,57-84vI,vӂ~^?wϋx[Acҫb%Ʈе%;Yx.60Lk>Æ߲1wX4]Z!;?kwr  .Y}v`_hY 5=e鏬Zv2oE|68koSQ([I8.7g瓸Lkw ~Q&>+Z랔u!?C |a,e2~ق{i'd~KܗZc,/qwd tO~_#U?{CnA&aiK)z!_@J.üԃ6~`caovM;xvS~Ve Y ?2kY{6n~農@Ű۞N-~k .3Xh8%g_ 눥/;hfdu:,oYڗG䃈r4WLDjϕCWkۑy~Բ`Q}\/qVM`?gcn-CsJy'զ?dY=e-Vh3rė9 >y=Կ 1Y1se ߹l!9 ~}:>cŮ4msR @x.ո6usmԿԘW[8bi?sүag.Z֜èӜch96>-uMΜT[!O[ЭgumUPV`>xZpw9qҎ1g3Y, 73WFDsNJSry:8{=Vy_R*ѓFgsl+ʹ#W/g&7Yu v89&bZ7{=NT^i|{AuyձάiΗZ3]Pսл,z#wvOaTƘ-_U3Wx\Q_#eИQLd%%x172l+ ҏ9}?~S;Zp:{9?b_07b3TeEydK#ʸbQԷk[v&v6rH笱-׶W 2W;Я'O"7% yL~mkC82ALw!X%>F:れ| MU'bʓscCŪeyؽؾ(nͬSlS?G9^k]}Qe 2f̢hk]a)֥9Vyލc%XwնU{7ob؉lv (ƿ]u 6!9qu \ZE ,{/V!q=@܄>7$i,{fh! }8{xPBn`Yq[pCش4_ʼ54Vy=:WYpWh]g]=U4d=jYZXt==%w,-4f lB1S=pdAI?~\,Wl÷}\hٰpyYqr˝s6ypUry(}-koltiׂ.V|#C( ϶espeL:zzlU]<?1nK @S HBNyW-I#`hVk\AGդgj6*co-k2`S~ w]6,%~-w }%aZV/ 숶ݴ{eހ}yg|\v,$R~ E*q c=qif+ќ4Y ‡J#該 e rjkJxmڂ&4u}[8*Ԟ@[iH9~U#j|P:kJ;j-f쓮-r@Eǰ؟Z >t_:0eLcM,8=#Yޔ+=m!hߴsU öd|Gg4m S11k̷"m'k|3HU!2(u}I_ixâiZ46* XYܰnvh{+_Ӽt:/ߓzNsR>f3l 2^[jOu;E=? ?v2G^}idъAL[@{'ٱ{ h i NXqj~a]y}F819߯.a4ׇ>m^n*^΃ݗs;<ގeeA@Eiד~rb~U y4F(xQ]}򕨮7PoQvzǂ;Z߿'ޙ?ۖ]<>lI9R)dEkRZ>sԜ"[k nY(qc`З[ybkeiՐMυzwق|'6X+5Ϧ9@@{^rV_7T' 7T k}bخ2ۂU=}>ΩnϤhn [+T_ tt8ȟG[ ?fT-kkYRհCze\iA.s6D6}髟 eTV~j;_HpTVhՎwV9H5/z<ە1DlTGܚ"WYh, ÐwI尃ang%2IГ~ЙY'sŸڎƴ5{Бz=cδ[v :įRNOgU6}8ϚgYϯYЃz.Ϝ/6#O'*O4.ðwsr}\ve BfWp)%PB 5yjY=A 8+@lA>KӸ1^ SJ[սa6n|t\ُৌ܀*ۂż6elڒ:JE+yD=8E2nB, TbLYs|A O4JEsXV~# -+3ʣ|hng47ʺ8 B?֯~ rMmZT< y>2aUݮ!YO]XHbWoчo Nڔkq RFu1Ų?ǮDm5W|I NuU w&'z-7Wb.nf#2'#o7mY9͂o KWȡu-#=S_ q3 }޲2h_ݺ;S@9꧰.!< (rnE DlA?`o-!X}&SʂwmY g󠅼m]ڶ:,4Z6~CxU %mTn\T,+GTfvPMb!N[֨i)s\ ϙYo󠟇Í -!e v w#LjArf,Gӎ;h~mկ3NwK['y5 6䚔-I5_XƯY.99p` uqZVinGc[MXcZ{Nc{ȬE?@oYE@c@ܡT x>,,ۖ;.ز[ehl:6;p?dA&Ce-coH_E?vV À]-Yi!N> |TizsGߌ߷)jqbZ蜹!eEH=(s ]5b_\rS:sve3nK聝pwB?+xՏݰaќ{uqguۂ}A,89j }Ył.!g}vt!?M1}9*kh#6OY1.[0.O+~gs_pn{B΄xS[wyop_ a!7el#'-, egi=] v5tc-l2|M]G 17/&985/89'_34O.wP"f3߶sCwdG֣1SxFhSvAT=2_6Ψ't9tm*K#)h鞩\j+a_0M=iYzяR _ͲsoBNg-M[ Nn]Gÿs29n7_~lx!~–dzcYSn,bCްKgԼDۓ6{'q--H=:zs)>?hT12 ,{uShYsĠ -7,Yow>Ȫ57,큅*e͢`myߐ5zV-ѕa^2Op-h,샾2e ~ q,,Y92>JO {ՕC / d߰6:Xw+4e^~0ӛc=5-֣O[kͲkz~̲_+Z vrBeuq+)'> $&9mY?@|zֲlE`V])G 0ڌe}qjb!ƺK0n, ֘դ=O1h>3toٲqU8ҿ*m{x>|r"l/Ʝ'3Y?q]=egh$OIFYޕn^eҏ+G1Bpi%mgoY+Ҙۙk,Je)Wިغ/H9qm]3{SZ4.}k2&eMߖ2W-ps(Y?)1nho@GWaدm>[>s]syG@&+pge#2jڲ|r Z6?16cRS5ui|'Ϟ$PwbmiCLQW]c w-ďfs26|TNl~ũƔ'K@*1;Q?I6BkUP{EԵ>j]@\J厎 |y>k$:ŇL_>|B\uvqE98 g$$\ږMٛ -yKg>'?E?P!,h!CMР+}1Yd>!t4?-J_t+^.K{CCCezKs'NB\{]?,[Ӎm! ާ}ݲq޲J܆AZ=xO`;-8y5nڗ1] gjv,V1g[Vi0f/_hCLn]f>‹{ϛ @6>d) n ٛs$뇦;6L`WC% J/,{k8bEgi_Ѳ|&E;tGʦ,Ʉo3m>1uצM /aAъ/OJr=V,{Xuķ_+[wӖPl>{"cO5.uЯwɂKM-~p g,5CZcBx9X`c[Ҏ> R`[YTWpe}Ղm{ǵw5,ﮌ:NsC0];S|Gjg ?~ү,Zv5*YE`ٟG~6aB>qA'Y/i^K\aAPL}E9ymANbj;b=nNxc#[t/Ϟ'2ɘXmcp3h4ܐ2KcLZ{hi3zVINM >qw\*ԬoYyNc6,(= ~A[ʸ_zglqղ1 Ɓ8+M5l.΄΅3<]:|л"Oއ\cԤY}G6[aYPޱ9Ǵo#3AALurW+l6F+XV5˞;gb;@vұpZ8YG4,u#Kx-je-,ad\qOL2 ̃~t!X<>4C~vӂ?F ?d/wl+lͲ2''.򋖍sVص{eV|?;oXg#+]DzyKB0Oj ]RoL[i!ڦo {:cAO^G8ݴ~޵`û,c|[{qTe M Q,Yb~~_vI֍E!/4Xe rX#~'4 aФm!Vq-{ϛ!z@(uC|rv[u4᛺#@ P;_\,+gcU4Z^mGd:2}=n;n/.y-C ,ܑess6V;/[oj]-)ʂm-gƔi|<.o@w#WX:"7X@Dye)6c[wVڶwx,5]ޣݬ=:l;x8ˎW-ph<2+kf&} hc6̳l#nI={\t8cchnĪ9Ce$z`&\,+]Mr0mWEqХԺe ]#CFy r̝m?l:w.o#;e߶l!~"6kd 6߽h"^ #U*S$ѶpQͣiٸ:$mj[~#Q+Ob6` |5ݒr5Wg"{igb_ l!Nu^Gl:xAycc@əZGEt~ PZu U*x4ܑ^}B8Ѫe{tףtAWz- -ؼ6}N+U:3#Nɹ'FCmEt͢aOueg3\.k^.TۂZ~G{׋Y1AUsA NGIO]LpA証N0=Fvc,gD혧"x-{9]rBc&a3;DL9-_S1 4h֎K= Ȩ1UӐ1.} ֯{}f-M>ZO\VM kΘ's!DX,/qShSg$.z h!LνiA#bӽ$d3x,=R 1Rre냖OX8ݴpY.ĠR9~I@pc^a#[ȑ T'O F[?mxZ;kr;Z[hyG2gɪe,%v eA|0699˥/ki8y7>Ti7O׹>6-㲌IQN<^]7B-ZV,<#%?jCoZӲ\UW`O89iy9+qюІ:]zGlAN(o!'մ,oYx;D>(:ؖ%)CöFrXg W٪eb!ޅ\C=Fu| |b!VQ|g;z_@M 2vu0q:pB/́0/rm ^Ƶ;@z?gs}yR zb|yq;Q:1񅆂Sq{[w|4?'u5Oղڵ+ոҢꝈW1?SC+2)Yˢ v]n6Z{]wC?sz%>~9+9kkRᬶ/R3Agh0}B>Vvkn|7~3]٩YwҗNS݇࣍A8wm_-ICSbN |5?{ѻ~#3wC ԵTׂ<R4qq1g:a|dc "lkܠ͋)hO70mU8phm[6V1[,dr쬅 1{͂?>6uBb7 o41(;ؑ{gćwO[ Akt-]͑q]B5ℝ|E7Kb%@YS~;m\ǵ^@7GPtjNp|;ZQ_{9Ƚ$-y{4~<[ߦ uC5Hibsn7q2lxjIwe1-kw~; ]AMA1e,Xh8:~5]E\<,b.vv$ZR~H8:>l)c )'&=f5I.}־EcklU#}u|hJ|&x|bO}Ƃ}b+k\h!/ڱ-zA}{fq1mr`Ol9Oٓ8`oi6l3z4,WX,z\58bCkwXE\[bkQaoG4 sqW,?ߵ/Xg8k$YhA>Hcu?eg8۵pOqushدlOo˖kM^}sج>XXW=V,`lZrՂ $D<_ f!_!][m谶 YYzo>'{)+>*wg-A;}~u }βQ鯾#5i$.O~B\3QrA9s\ܩGgTe,=PZҎ^vjOh|5 {RK8+Xw/}#eY=pӢi~\s QY["/1*5g( Eg@?s8M+«8:l[LDЋd՜k@sG>ue+cx{ղpUSɜf;Fr=*cJClaԎ2 tR+ez}wꑇ-q˞;;|U>*ȍ]U_!wK@WOs2m-mV,2tr:wkG- ,n 5.b1{zcc~3nв1mAs̘'8!yoƼo,F-fv1Ԥb7f\xsQp Z[CqfRFwr6ݨ_y=FL}|A? GB͂^_`1E)Fܕ1Ԟ&N>U|sh>=2w!iӲ6Ec_҆#{r c4nJbtrkugrCoؚ3~z˲yrxh^pi^~Ĕ-~kKZjAZg=c,SGƂ62V+Vy<Hʻ|dl~͜/c#fycl6l$ڂWׯ}Pц;T8GCxѲemHSw8gSUg7xOm ,ޠxNgL8л/16c$Ԓ*q1Ƭ]J2~E|y废+cpͥ:bXғ%᤭Oryj]x,'_(Ӝ܋UaY8Zt'F1ch8^b-)75}Qٶ:6{?+w"ǝ!=s] K|3W=]6'[rQ:eׯ{Xz.5 17=̹li;cYe*;'|OV c/W̸'9,h^Bx>FĴfR_@P1-Wb=2-t`8] ARmb+b'd.NmCuߌ@ϡ|lxc޲f{h1lI[G'w|&}a]1}2!k3xTs5W,큪eVJ>Qx.z׀ԫ>TYWޠ]]֪t>6eS2S15sZwuok xkeNϯ`׮=fΚEu>SG*[2v9KVo{_dusϹ:@|`{s%N =y?/Jٍ;i7(H;?4_ { /R5jZ>o_Rc;@z H<.ڰV]ܤgwߺ1oYw49շU^]U yܰW[7~VyX gu,Z / Wohw>+BU ?{2{4-bO-e6kߔm K{zܓZkܰ3R&4X~Ï?,q?ׇyzuusrڲuɲ?3N"cWlȸ;FwHxCcZfÚsPoY^!vM>蒡/PȯYѰ2&l[._x  ˌκY+//mᚅ;7,֠% -{w9ךY9["L*+x< &fM-@ծY{emwB.SW|>!o>R93Ny{Vd޹ύa?j{JXet[p߱o! \Li:㛳o 84Ÿŧ&ڪ$qDz򀜁Mw͂\w=_>e!\D Z񇖍C-g[gl VóboѡOr/&>^X[U.| RfJxҸ#n,q/Ye wKOE,ۓqX{[1y?ij3xλ곣A{\-lFKǫyD[ؖqId|F[n k<Њ9 BAk;Ak_G}ĝjLٞ܅@6З]rEz6e3=K{mȚG{ޛR;񒊅}:e٘&g-3tBN<oRWpDZhMY8ݝu m5Ѵ\)6IGR_=;m|u զ5'ρaWNi|u {ڔ~lmg7'hBXTxV=@dَe:UuL6<7h~O4}5/Dtn# j[7v=YL}Zb-9Ү"]ڿϹp/D(+ʃ*c\;Q*4wI<žǮ %x;i7$\x'=C;le|Evvm/GG,K+sv!vzJcyྙ7j5,Է| ُhH{lq^.caOjnYCնVKcpx*Sǹm䍮#Υr:v^u|R࠭[=kAYjRMîW@)T,NCu 4XVm<ԏcӮս v& 6j-wXY[,?)*_lWwE2fnq{':\Wr-Zw/W CIe|Ų!"@;mAfCqr'7lG;smYpOmj{,˸+2G"WWzɂ>Ö .[5g |ʋvBezpX\ B3ė,n`s,9|f6Cy.y¦[\=({A\ob |<͆Ϗ*D07b%Syž6,Uư@|-={qтMul[,3`?Y?`A\] 1tb{<,F#jz馅종ǧZ{fgȏXݓ|6vR,"1?Zm(l3䮬ڊJ/DfY~с!k} w V-3q% r |cY\Q%Kѩ[ZU-G:d"IT<5>9o)[w J3WY(hv6 s薦+X~0:n6SU:;Q5LlN]2v-ns]kOk 6-gЅă -k.SgLi2 1^p#9{ߖ87Z5>GA|iGR轘S>8l/Ȋ犵=Qۇ%όd!';;KI;o1pv)YNhr5%zi}=}[Jk'iz2,%d7RLd-mUO[/}i~UMk-]HKikl\^bzo6s)޹?7b:Z7}[H1ZI#— _J^M'xSn?Ǎs)o"Jr9IQՉ'x:ć ar_V[ɗ_)'t<5q_k7S )f O')ܔdpZ`ɹ֟JRw IsW _(LgS%o'=Bt$NW&[('IV'><_- G^Ϥ.|\Yb}ZW.-}{0iBs_MK}H:~=K}.bɓ#%=ߌziGa*J6!=~WGocn'[>"3S+I ̽{{xs},}g> %%szO~ғ0=/'KÕܰ>mHAqRdJ^M%/VKӓ|.yUrSrTZt:GrTٛI/_/!r!r!r 9gs!rH9C9C9C9C9C9C9CA9C9C9C9ßDr!|0r!r!r!r!?pp)p0`c9C9C9C9C9C9C9C9C9C9C9C9C9C9C9C9C9C9C9C9r9C9C9C9C9C9 ߟC4f9C9C9C9C9C9C9C9C9C9C9ɂ9C9C9C9C9C9C9C9C9C9C8pr!r!r!r!r!r!r!r!r!r!rIXJ'08?89=]-M;28BcJ .<+pvrtsgs#4[ ҷÇycv)~{|W> ;s\X|=yӯ}ѧl/;S&1eS33u;kYzpfᣯ[[o>[8pt{o^=z-g>[Z,~MVZ$/i>'+I13J&?c'O:? !Ζ&VWw+tF#@Tvjn)4-Gc{4]?2ro}lq)z*{:嘫u٭;^zwkk'[%_*f*;7?ғ~'W':Լxtp߃o+|DqNj/?ྛn:LV;Ég <|T,^LҹMiģG}{wN846x?\N?9;;ܹUk-O,N3Zŝd.&9Ξt^ rn¹JsGS'./-Lye}Bx驹˷M9sOϮ?K?v/xб@ڵήWB ڽmtX;y yd$qr*`ugSr1d*t{[RT G'sK/ ^z]8R)>|GOw;wϼ/g~?s/?sK;}ϝiꃟl/yb_unRi:/yڽG.^ݙv;g'.CKKk_+R,w/o({;Z:qT:pg޾~ҕҗ''KR}n'|ı?~tM)Yxllod=yM|j-o?AJ'uSD']]J#䈗F6EFKO֗VVk^6yӵ3KTկj ?÷̫^yǵW:x_)PZ^|Gqr1b郇np;OV>y~k᭷n}}/N>{ǙG쇆߾˅ɋ'NZ:o޹2r W7o/Ntgȁ3W ?r*=ϔW/?SKc%?@KT-Br)ySX$?}1{s[۝l)wGS5C{ h6'VΝwӉ'g^}[vG{k|'g֟xxLio?mۥm7vJ'(;3ŏͯn}It[G7VdNj'>|kg:rٗ?\١J +& O,lwo.$m{/^J&ޞmǰӞ{$tu}*MR+_ܭγj{{VmWn,|+*|;vN#)W_c/~/;z?ߑ'z\nӿxdቧjFNﺘnܑ;,]iKW>suty~3'8yB3 OO˯c/c{bpxO^*| ً{|daX nwn~M7_yUTMg>Q:pniarRc=~ -5ғ:1qlmkccc޳U*ͼ7_vxSSRrJO'N:G rr1g>:qI{Lw̽x%PtiT.^3/9ysm3UL_}[7Sӯ9=מ+]?kf_{K;½wZ|ґ?;z/f̙LyeXl;vjw_urLz1{OK3n߱Uhm2YM_)vVISi!)V:w&ؑԣ;6dc=Rby:(c=Ú#ɟspїv:;.OpMmNds[}dp=W?!h{{NGj3_gwpBaҟgƻc% '?yu׾SR_w٭['{Z.|䙙[V}ɯ;Ό-]S ~spa9ݍt&&i; ~G\NRΗ4^d&Z@Ѥu4@ZŁ+ФUb44MhB= 4ĉC'.;N;X%}쥧"g2RnM5jERSӢ"CR|A^ %c(~eYﳸ9](OhRf0?0$. sO_N+)Mq'K7YExLR.{j`ш&.v(XavdMOG DУ Ci փs7vp1ЪRK%ޞdHRWob51MwqVUG4#6yO웵b3ml!nei6gW+o ='FVOZ&֯rL 98 G\r08CQ8Ox28 e86#t($,P5HFS5?zWҌ-ݷ.3b|/l@]x{#N{[;]LB_T99_n/iiEW^)0|V}Y JXO0F_ xmkxGU䮿7sp}x> endstream endobj 2 0 obj <> endobj 1 0 obj <> /Outlines 5 0 R /Pages 2 0 R /Type /Catalog>> endobj 4 0 obj <> endobj 30 0 obj <> endobj 31 0 obj <> endobj 35 0 obj <> endobj 29 0 obj <> endobj 5 0 obj <> endobj xref 0 56 0000000000 65535 f 0001102593 00000 n 0001102503 00000 n 0000000016 00000 n 0001102680 00000 n 0001103215 00000 n 0000000354 00000 n 0000000546 00000 n 0000963489 00000 n 0000963633 00000 n 0000965441 00000 n 0000965637 00000 n 0000965956 00000 n 0000000282 00000 n 0001037741 00000 n 0001037887 00000 n 0001038288 00000 n 0001038458 00000 n 0001038774 00000 n 0001060814 00000 n 0001060957 00000 n 0001061318 00000 n 0001061570 00000 n 0001061888 00000 n 0001075382 00000 n 0001075530 00000 n 0001075937 00000 n 0001076109 00000 n 0001076436 00000 n 0001103088 00000 n 0001102760 00000 n 0001102863 00000 n 0000121245 00000 n 0000121465 00000 n 0000002813 00000 n 0001102981 00000 n 0000362559 00000 n 0000362766 00000 n 0000122791 00000 n 0000256106 00000 n 0000519650 00000 n 0000519823 00000 n 0000363519 00000 n 0000435606 00000 n 0000793420 00000 n 0000793627 00000 n 0000520027 00000 n 0000653174 00000 n 0000962973 00000 n 0000963133 00000 n 0000794190 00000 n 0000963322 00000 n 0000963362 00000 n 0000963402 00000 n 0000963445 00000 n 0000965368 00000 n trailer<]>> startxref 1103270 %%EOF peony-extensions/peony-share/0000775000175000017500000000000015156143275015272 5ustar fengfengpeony-extensions/peony-share/peony-share.pro0000664000175000017500000000133015156143275020243 0ustar fengfengTEMPLATE = subdirs SUBDIRS = $$PWD/service/service.pro $$PWD/app/app.pro $$PWD/test/test.pro \ peony-share-menu-plugin daemonConfig.files = $$PWD/data/org.ukui.samba.share.config.conf daemonConfig.path = /usr/share/dbus-1/system.d/ daemonPolicy.files = $$PWD/data/org.ukui.samba.share.config.policy daemonPolicy.path = /usr/share/polkit-1/actions daemonService.files = $$PWD/data/org.ukui.samba.share.config.service daemonService.path = /usr/share/dbus-1/system-services INSTALLS += daemonConfig daemonPolicy daemonService SKIP_TEST = $$(EXTENSIONS_SKIP_TEST) isEmpty(SKIP_TEST) { message("build with tests") QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage LIBS += -lgcov } peony-extensions/peony-share/service/0000775000175000017500000000000015156143275016732 5ustar fengfengpeony-extensions/peony-share/service/samba-config.h0000664000175000017500000000316615156143275021437 0ustar fengfeng/* * Copyright (C) 2022, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Ding Jing * */ #ifndef SAMBACONFIG_H #define SAMBACONFIG_H #include class SambaConfigPrivate; class SambaConfig : public QObject { Q_OBJECT Q_CLASSINFO ("D-Bus Interface", DBUS_NAME) public: const static SambaConfig* getInstance (); private: SambaConfig(SambaConfig&):QObject(nullptr){}; ~SambaConfig(){}; explicit SambaConfig(QObject *parent = nullptr); bool launchSmbd(); bool isSmbdLaunched (); bool launchNmbd(); bool isNmbdLaunched(); bool userIsInSambaGroup (); bool addUserInGroup (); bool isContainLegitimacyChar(QString passwd); public Q_SLOTS: bool init (QString name, int pid, int uid); void finished (); bool hasPasswd (); bool setPasswd (QString passwd); Q_SIGNALS: private: SambaConfigPrivate* d_ptr; Q_DECLARE_PRIVATE(SambaConfig) }; #endif // SAMBACONFIG_H peony-extensions/peony-share/service/peony-samba-service.cpp0000664000175000017500000000375115156143275023315 0ustar fengfeng/* * Copyright (C) 2022, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Ding Jing * */ #include #include #include "samba-config.h" #include #include #include //static GDBusObjectManagerServer* manager = NULL; static bool registManager (SambaConfig& sc); int main (int argc, char* argv[]) { int ret = -1; QCoreApplication app(argc, argv); SambaConfig* sc = const_cast(SambaConfig::getInstance()); if (registManager(*sc)) { ret = app.exec(); } return ret; } static bool registManager (SambaConfig& sc) { QDBusConnection bus = QDBusConnection::systemBus(); if (bus.interface()->isServiceRegistered(DBUS_NAME)) { qWarning() << "dbus: " DBUS_NAME " already regist!"; return false; } if (!bus.registerService(DBUS_NAME)) { qWarning() << "dbus: " DBUS_NAME " regist failed!"; return false; } if (!bus.registerObject(DBUS_PATH, DBUS_NAME, static_cast(&sc), QDBusConnection::ExportNonScriptableSlots|QDBusConnection::ExportNonScriptableSignals)) { qWarning() << "dbus: " DBUS_NAME " regist object failed!"; return false; } qDebug() << "dbus: " DBUS_NAME " regist successed!"; return true; } peony-extensions/peony-share/service/samba-config.cpp0000664000175000017500000003222515156143275021770 0ustar fengfeng/* * Copyright (C) 2022, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Ding Jing * */ #include "samba-config.h" #include #include #include #include #include #include #include #include #include class SambaConfigPrivate { public: explicit SambaConfigPrivate (SambaConfig* sm); bool launchSmbd(); bool smbdIsActive (); bool launchNmbd(); bool nmbdIsActive(); bool userInSamba (); bool userInSambaGroup(); bool addUserToSambaGroup(); bool setUserPasswd(const QString& pass); bool checkAuthorization (); public: SambaConfig* q_ptr; QString mUserName; int mUserUid; int mUserPid; PolkitAuthority* mAuth = nullptr; QMutex mLock; }; SambaConfigPrivate::SambaConfigPrivate(SambaConfig *sm) : q_ptr(sm), mUserUid(-1), mUserPid(-1) { g_autoptr(GError) error = NULL; mAuth = polkit_authority_get_sync(NULL, &error); if (error) { qWarning() << error->message; } } bool SambaConfigPrivate::launchSmbd() { QString startCmd = QString("systemctl start smbd.service"); QString enableCmd = QString("systemctl enable smbd.service"); QProcess process; process.start("/usr/bin/bash",QStringList() << "-c" << startCmd); process.waitForFinished(); QString output = process.readAllStandardOutput(); QString error = process.readAllStandardError(); syslog(LOG_DEBUG, "launchSmbd output:%s error:%s", output.toLatin1().data(), error.toLatin1().data()); if (output.isEmpty() && error.isEmpty()) { QProcess p; p.start("/usr/bin/bash",QStringList() << "-c" << enableCmd); p.waitForFinished(); QString out = p.readAllStandardOutput(); QString err = p.readAllStandardError(); syslog(LOG_DEBUG, "launchSmbd enabele out:%s err:%s", out.toLatin1().data(), err.toLatin1().data()); if (out.isEmpty() && err.contains("enable smbd")) { return true; } else { return false; } } else { return false; } } bool SambaConfigPrivate::smbdIsActive() { //return QProcess::execute("systemctl", QStringList()<<"status"<<"smbd") ? false : true; //g_autofree gchar* cmd = g_strdup_printf ("ps aux | grep smbd | grep root | wc -l"); //int ret = QProcess::execute(cmd); QProcess process; process.start("/usr/bin/bash",QStringList() << "-c" << "ps aux | grep smbd | grep root | wc -l"); process.waitForFinished(); int ret = process.readAllStandardOutput().toInt(); QString error = process.readAllStandardError(); syslog(LOG_DEBUG, "smbdIsActive ret:%d error:%s", ret, error.toLatin1().data()); if (ret - 1 > 1 && error.isEmpty()) { return true; } else { return false; } } bool SambaConfigPrivate::launchNmbd() { QString startCmd = QString("systemctl start nmbd.service"); QString enableCmd = QString("systemctl enable nmbd.service"); QProcess process; process.start("/usr/bin/bash",QStringList() << "-c" << startCmd); bool isFinished = process.waitForFinished(3000); QString output = process.readAllStandardOutput(); QString error = process.readAllStandardError(); syslog(LOG_DEBUG, "launchNmbd output:%s error:%s isFinished:%d", output.toLatin1().data(), error.toLatin1().data(), isFinished); if (!isFinished) { syslog(LOG_DEBUG, "launchNmbd restart..."); QProcess pro; pro.setProgram("/usr/bin/bash"); pro.setArguments(QStringList() << "-c" << startCmd); pro.startDetached(); pro.waitForFinished(); } if (output.isEmpty() && error.isEmpty()) { QProcess p; p.start("/usr/bin/bash",QStringList() << "-c" << enableCmd); p.waitForFinished(); QString out = p.readAllStandardOutput(); QString err = p.readAllStandardError(); syslog(LOG_DEBUG, "launchNmbd enabele out:%s err:%s", out.toLatin1().data(), err.toLatin1().data()); if (out.isEmpty() && err.contains("enable nmbd")) { return true; } else { return false; } } else { return false; } } bool SambaConfigPrivate::nmbdIsActive() { //return QProcess::execute("systemctl", QStringList()<<"status"<<"nmbd") ? false : true; QProcess process; process.start("/usr/bin/bash",QStringList() << "-c" << "ps aux | grep nmbd | grep root | wc -l"); process.waitForFinished(); int ret = process.readAllStandardOutput().toInt(); QString error = process.readAllStandardError(); syslog(LOG_DEBUG, "nmbdIsActive ret:%d error:%s", ret, error.toLatin1().data()); if (ret - 1 > 1 && error.isEmpty()) { return true; } else { return false; } } bool SambaConfigPrivate::userInSamba() { if (mUserName.isEmpty() || -1 == mUserUid) { return false; } QString cmd = QString("/usr/bin/smbpasswd -e %1").arg(mUserName.toUtf8().constData()); QProcess process; process.start("/usr/bin/bash",QStringList() << "-c" << cmd); process.waitForFinished(); QString output = process.readAllStandardOutput(); QString error = process.readAllStandardError(); syslog(LOG_DEBUG, "userInSamba cmd:%s output:%s error:%s", cmd.toLatin1().data(), output.toLatin1().data(), error.toLatin1().data()); if (output.contains("Enabled user") && error.isEmpty()) { return true; } else { return false; } } bool SambaConfigPrivate::userInSambaGroup() { if (mUserName.isEmpty() || -1 == mUserUid) { return false; } QString cmd = QString("/usr/bin/groups %1 | grep sambashare").arg(mUserName.toUtf8().constData()); QProcess process; process.start("/usr/bin/bash",QStringList() << "-c" << cmd); process.waitForFinished(); QString output = process.readAllStandardOutput(); QString error = process.readAllStandardError(); syslog(LOG_DEBUG, "userInSambaGroup cmd:%s output:%s error:%s", cmd.toLatin1().data(), output.toLatin1().data(), error.toLatin1().data()); if (output.contains("sambashare") && error.isEmpty()) { return true; } else { return false; } } bool SambaConfigPrivate::addUserToSambaGroup() { if (mUserName.isEmpty() || -1 == mUserUid) { return false; } // QProcess p; // p.setProgram("usermod"); // p.setArguments(QStringList() << "-G" << "sambashare" << "-a" << mUserName); // p.start(); // p.waitForFinished(-1); QString usermod = "/usr/sbin/usermod"; QString gpasswd = "/usr/bin/gpasswd"; QString command; QString groupName = "sambashare"; QFile usermodFile(usermod); QFile gpasswdFile(gpasswd); QProcess p(0); QStringList args; if(!usermodFile.exists()){ syslog(LOG_DEBUG, "/usr/sbin/usermod file not exist \n"); if(!gpasswdFile.exists()){ syslog(LOG_DEBUG, "/usr/sbin/gpasswd file not exist \n"); return false; } command = gpasswd; args.append("-a"); args.append(mUserName); args.append(groupName); } else { command = usermod; args.append("-a"); args.append("-G"); args.append(groupName); args.append(mUserName); } p.execute(command,args);//command是要执行的命令,args是参数 p.waitForFinished(-1); QString output = p.readAllStandardOutput(); QString error = p.readAllStandardError(); syslog(LOG_DEBUG, "addUserToSambaGroup output:%s error:%s", output.toLatin1().data(), error.toLatin1().data()); if (error.isEmpty() && output.isEmpty()) { return true; } else { return false; } } bool SambaConfigPrivate::setUserPasswd(const QString &pass) { if (mUserName.isEmpty() || -1 == mUserUid || pass.isEmpty()) { return false; } qDebug() << "username:" << mUserName << " change passwd!"; // g_autofree gchar* cmd = g_strdup_printf ("spawn smbpasswd -a \"%s\";" // "expect \"*New SMB password*\"; send \"%s\\n\";" // "expect \"*Retype new SMB password*\"; send \"%s\\n\";" // "expect eof; exit", mUserName.toUtf8().constData(), // pass.toUtf8().constData(), pass.toUtf8().constData()); // //QProcess::execute("expect", QStringList() << "-c" << cmd); QProcess p2; p2.start(QString("/usr/bin/smbpasswd -a %1").arg(mUserName)); p2.waitForBytesWritten(); p2.write(pass.toUtf8().constData()); p2.write("\n"); p2.waitForBytesWritten(); p2.write(pass.toUtf8().constData()); p2.write("\n"); p2.waitForFinished(); QString err = p2.readAllStandardError(); if (!err.isEmpty()) { return false; } int ret = userInSamba(); return ret; } bool SambaConfigPrivate::checkAuthorization() { bool ret = false; g_autoptr(GError) error = nullptr; PolkitSubject* proj = polkit_unix_process_new_for_owner (mUserPid, 0, mUserUid); PolkitAuthorizationResult* res = polkit_authority_check_authorization_sync (mAuth, proj, "org.ukui.samba.share.config.authorization", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, nullptr, &error); if (error) { qWarning() << error->message; goto out; } if (polkit_authorization_result_get_is_authorized(res)) { ret = true; } out: if (proj) g_object_unref (proj); if (res) g_object_unref (res); return ret; } const SambaConfig *SambaConfig::getInstance() { static SambaConfig sc; return ≻ } SambaConfig::SambaConfig(QObject *parent) : QObject{parent}, d_ptr (new SambaConfigPrivate(this)) { } bool SambaConfig::init(QString name, int pid, int uid) { Q_D (SambaConfig); qDebug() << "init"; if (name.isEmpty() || pid < 0 || uid < 0) { return false; } // KVE-2023-1201 if (name.contains("|")) return false; QStringList allUsers; struct passwd *pw; setpwent(); while ((pw = getpwent()) != nullptr) { allUsers << pw->pw_name; } endpwent(); if (!allUsers.contains(name)) { return false; } if (d->mLock.tryLock(300)) { d->mUserName = name; d->mUserPid = pid; d->mUserUid = uid; bool ret = true; // smbd if (!isSmbdLaunched()) { ret = ret && launchSmbd(); } // nmbd if (!isNmbdLaunched()) { ret = ret && launchNmbd(); } // sambashare if (!userIsInSambaGroup()) { ret = ret && addUserInGroup (); } return ret; } return false; } void SambaConfig::finished() { Q_D (SambaConfig); d->mLock.unlock(); d->mUserName = ""; d->mUserPid = -1; d->mUserUid = -1; qDebug() << "finished"; QCoreApplication::exit(0); } bool SambaConfig::launchSmbd() { Q_D (SambaConfig); if (d->checkAuthorization()) { return d->launchSmbd(); } return false; } bool SambaConfig::isSmbdLaunched() { Q_D (SambaConfig); return d->smbdIsActive(); } bool SambaConfig::launchNmbd() { Q_D (SambaConfig); if (d->checkAuthorization()) { return d->launchNmbd(); } return false; } bool SambaConfig::isNmbdLaunched() { Q_D (SambaConfig); return d->nmbdIsActive(); } bool SambaConfig::userIsInSambaGroup() { Q_D (SambaConfig); return d->userInSambaGroup(); } bool SambaConfig::addUserInGroup() { Q_D (SambaConfig); if (d->checkAuthorization()) { return d->addUserToSambaGroup(); } return false; } bool SambaConfig::isContainLegitimacyChar(QString passwd) { //需要用'在shell解释中做强引用 if (passwd.contains("'")) return false; if (passwd.contains("&") || passwd.contains(" ")) { return false; } for (QChar ch : passwd){ if (int(ch.toLatin1() <= 0)){ return false; } } return true; } bool SambaConfig::hasPasswd() { Q_D (SambaConfig); if (d->checkAuthorization()) { return d->userInSamba(); } return false; } bool SambaConfig::setPasswd(QString passwd) { Q_D (SambaConfig); if (passwd.isEmpty() || !isContainLegitimacyChar(passwd)) { return false; } if (d->checkAuthorization()) { return d->setUserPasswd(passwd); } return false; } peony-extensions/peony-share/service/service.pro0000664000175000017500000000061615156143275021117 0ustar fengfengTEMPLATE = app TARGET = peony-samba-service QT += core dbus PKGCONFIG += gio-2.0 polkit-gobject-1 CONFIG += link_pkgconfig c++11 no_keywords DEFINES += DBUS_NAME=\\\"org.ukui.samba.share.config\\\" DEFINES += DBUS_PATH=\\\"/org/ukui/samba/share\\\" SOURCES += $$PWD/peony-samba-service.cpp $$PWD/samba-config.cpp HEADERS += $$PWD/samba-config.h target.path = /usr/libexec/ INSTALLS += target peony-extensions/peony-share/peony-share-menu-plugin/0000775000175000017500000000000015156143275021762 5ustar fengfengpeony-extensions/peony-share/peony-share-menu-plugin/share-menu-plugin.h0000664000175000017500000000424715156143275025502 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2024, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #ifndef SHAREMENUPLUGIN_H #define SHAREMENUPLUGIN_H #include "peony-share-menu-plugin_global.h" #include "menu-plugin-iface.h" #include namespace Peony { class PEONYSHAREMENUPLUGINSHARED_EXPORT ShareMenuPlugin : public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit ShareMenuPlugin(QObject *parent = nullptr); PluginInterface::PluginType pluginType() override { return PluginInterface::MenuPlugin; } const QString name() override { return tr("Peony-Qt Share Operation menu Extension"); } const QString description() override { return tr("File share operation menu Extension"); } const QIcon icon() override { return QIcon::fromTheme("emblem-link-symbolic"); } void setEnable(bool enable) override { m_enable = enable; } bool isEnable() override { return m_enable; } QString testPlugin() override { return "test share operation"; } QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; bool checkOriginPath(const QString& selectFilePath, const QString& configFilePath); bool isSupportUri(const QString& uri); private: bool m_enable = true; }; } #endif // SHAREMENUPLUGIN_H peony-extensions/peony-share/peony-share-menu-plugin/translations/0000775000175000017500000000000015156143275024503 5ustar fengfengpeony-extensions/peony-share/peony-share-menu-plugin/translations/peony-share-menu-plugin_mn.ts0000664000175000017500000000207415156143275032240 0ustar fengfeng Peony::ShareMenuPlugin Cancel sharing Хуваалцахыг цуцал Share folder Хуваалцсан хавтас Peony-Qt Share Operation menu Extension Хуваалцсан үйлдлийн цэсийн карт File share operation menu Extension Файл хуваалцах үйл ажиллагааны цэсийн карт peony-extensions/peony-share/peony-share-menu-plugin/translations/peony-share-menu-plugin_zh_HK.ts0000664000175000017500000000171615156143275032633 0ustar fengfeng Peony::ShareMenuPlugin Cancel sharing 取消共用 Share folder 共用資料夾 Peony-Qt Share Operation menu Extension Peony-Qt Share Operation 功能表擴展 File share operation menu Extension 檔共用作功能表擴展名 peony-extensions/peony-share/peony-share-menu-plugin/translations/peony-share-menu-plugin_bo_CN.ts0000664000175000017500000000260315156143275032604 0ustar fengfeng Peony::ShareMenuPlugin Cancel sharing མཉམ་སྤྱོད་བྱ་རྒྱུ་མེད་པར་བཟོ་དགོས། Share folder མཉམ་དུ་ཡིག་ཆ་མཉམ་སྤྱོད་བྱེད་པ། Peony-Qt Share Operation menu Extension མཉམ་སྤྱོད་ཟས་ཐོའི་བར་བཅུག་ལྷུ་ལག་མཉམ་སྤྱོད་བྱ་དགོས། File share operation menu Extension ཡིག་ཆ་མཉམ་སྤྱོད་ཀྱིས་ཟས་ཐོའི་བར་བཅུག་ལྷུ་ལག་མཉམ་སྤྱོད་བྱ་དགོས། peony-extensions/peony-share/peony-share-menu-plugin/translations/peony-share-menu-plugin_zh_CN.ts0000664000175000017500000000170115156143275032623 0ustar fengfeng Peony::ShareMenuPlugin Cancel sharing 取消共享 Share folder 共享文件夹 Peony-Qt Share Operation menu Extension 共享操作菜单插件 File share operation menu Extension 文件共享操作菜单插件 peony-extensions/peony-share/peony-share-menu-plugin/peony-share-menu-plugin.pro0000664000175000017500000000151715156143137027175 0ustar fengfengQT += widgets concurrent dbus include(../../common.pri) TEMPLATE = lib TARGET = peony-share-menu-plugin DEFINES += PEONYSHAREMENUPLUGIN_LIBRARY PKGCONFIG += peony gio-2.0 udisks2 glib-2.0 CONFIG += link_pkgconfig no_keywords c++11 plugin debug HEADERS += \ ../app/share-emblem-provider.h \ peony-share-menu-plugin_global.h \ share-menu-plugin.h SOURCES += \ ../app/share-emblem-provider.cpp \ share-menu-plugin.cpp TRANSLATIONS += translations/peony-share-menu-plugin_zh_CN.ts \ translations/peony-share-menu-plugin_bo_CN.ts \ translations/peony-share-menu-plugin_mn.ts \ translations/peony-share-menu-plugin_zh_HK.ts target.path = $$[QT_INSTALL_LIBS]/peony-extensions INSTALLS += target CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = /translations/ peony-extensions/peony-share/peony-share-menu-plugin/share-menu-plugin.cpp0000664000175000017500000001661415156143275026036 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2024, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #include "share-menu-plugin.h" #include "file-info.h" #include "usershare-manager.h" #include "properties-window.h" #include "peony-share/app/share-emblem-provider.h" #include "global-settings.h" #include #include #include #include #include using namespace Peony; const QString SHARECONFIGPATH = "/var/lib/samba/usershares/"; ShareMenuPlugin::ShareMenuPlugin(QObject *parent) : QObject(parent) { QTranslator *t = new QTranslator(this); qDebug()<<"system().name:"<load(":/translations/peony-share-menu-plugin_"+QLocale::system().name()); QApplication::installTranslator(t); } QList ShareMenuPlugin::menuActions(Peony::MenuPluginInterface::Types types, const QString &uri, const QStringList &selectionUris) { QList l; if (types == MenuPluginInterface::DesktopWindow || types == MenuPluginInterface::DirectoryView) { if (selectionUris.count() == 1) { qDebug() << __func__ << uri << selectionUris; if (!isSupportUri(selectionUris[0])) return l; QStringList disExtensions = GlobalSettings::getInstance()->getValue(DISABLED_EXTENSIONS).toStringList(); if (disExtensions.contains("libpeony-share.so")) return l; auto selectFileInfo = FileInfo::fromUri(selectionUris[0]); if (selectFileInfo->isVirtual()) { return l; } if (selectFileInfo->isDir()) { auto manager = UserShareInfoManager::getInstance(); QStringList userShareLists = manager->getUsershareLists(); bool isShared = false; for (auto &list : userShareLists) { if (QString::compare(list, selectFileInfo->displayName(), Qt::CaseInsensitive) == 0) { isShared = true; break; } } QFileInfo shareInfo(SHARECONFIGPATH + selectFileInfo->displayName().toLower()); QFileInfo fileInfo(selectFileInfo->filePath()); if (shareInfo.owner() != fileInfo.owner()) { isShared = false; } if (isShared) { isShared = checkOriginPath(selectFileInfo->filePath(), shareInfo.filePath()); } QAction* shareOperationAction = nullptr; if (isShared) { shareOperationAction = new QAction(tr("Cancel sharing"), nullptr); } else { shareOperationAction = new QAction(tr("Share folder"), nullptr); } connect(shareOperationAction, &QAction::triggered, [=](){ if (isShared) { auto fileInfo = FileXattrInfo::fromUri(selectFileInfo->uri()); if(fileInfo && !fileInfo->getXattrInfoString(SHARE_EMBLEMS).isEmpty()){ fileInfo->removeXattrInfo(SHARE_EMBLEMS); EmblemProviderManager::getInstance()->queryAsync(selectFileInfo->uri()); } QString name = selectFileInfo->displayName(); UserShareInfoManager::getInstance()->removeShareInfoAcl(name); bool ret; QStringList args; args << "/usr/bin/setfacl" << "-b" << QString("\"%1\"").arg(selectFileInfo->filePath()); UserShareInfoManager::exectueSetAclCommand(args, &ret); } else { if (GlobalSettings::getInstance()->isExist(SHOW_SHARE_PROPERTIES)) { GlobalSettings::getInstance()->setValue(SHOW_SHARE_PROPERTIES, true); } QUrl url = selectFileInfo->uri(); QProcess p; #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) p.setProgram("/usr/bin/peony"); p.setArguments(QStringList()<<"--show-properties"<displayName(); } } } return l; } bool ShareMenuPlugin::checkOriginPath(const QString &selectFilePath, const QString &configFilePath) { bool ret = true; QFile file(configFilePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "open share config failed:" << configFilePath; ret = false; return ret; } QTextStream stream(&file); QString result = stream.readAll(); QStringList lines = result.split('\n'); QString originPath; for (QString line : lines) { if (line.startsWith("path")) { originPath = line.split('=').last(); break; } } if (0 != originPath.compare(selectFilePath)) { ret = false; } return ret; } bool ShareMenuPlugin::isSupportUri(const QString &uri) { if (uri.startsWith("file:///box")) { return false; } auto info = FileInfo::fromUri(uri); if (!info->isDir() || info->isVirtual() || !info->uri().startsWith("file:///")) { return false; } // don't share user's home directory file:///home/xxx/ QStringList file = info->uri().split('/', Qt::SkipEmptyParts); if ((3 == file.size ()) && (file.at(1) == "home")) { return false; } // don't share directory that has no permission if (!info->canRead() || !info->canWrite() || !info->canExecute()) { return false; } g_autoptr(GFile) gFile = g_file_new_for_uri(info->uri().toUtf8().constData()); if(gFile) { g_autoptr(GFileInfo) gInfo = g_file_query_info(gFile, "owner::*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, nullptr); if (gInfo) { QString ownerUser = g_file_info_get_attribute_string(gInfo, G_FILE_ATTRIBUTE_OWNER_USER); QString userName = qgetenv("USER"); if (0 != QString::compare(ownerUser, userName)) { return false; } } } return true; } peony-extensions/peony-share/peony-share-menu-plugin/peony-share-menu-plugin_global.h0000664000175000017500000000213215156143137030136 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2024, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #ifndef PEONYSHAREMENUPLUGIN_GLOBAL_H #define PEONYSHAREMENUPLUGIN_GLOBAL_H #include #if defined(PEONYSHAREMENUPLUGIN_LIBRARY) # define PEONYSHAREMENUPLUGINSHARED_EXPORT Q_DECL_EXPORT #else # define PEONYSHAREMENUPLUGINSHARED_EXPORT Q_DECL_IMPORT #endif #endif // PEONYSHAREMENUPLUGIN_GLOBAL_H peony-extensions/peony-share/app/0000775000175000017500000000000015156143275016052 5ustar fengfengpeony-extensions/peony-share/app/translations/0000775000175000017500000000000015156143275020573 5ustar fengfengpeony-extensions/peony-share/app/translations/peony-share-extension_kk_KZ.ts0000664000175000017500000003271515156143275026510 0ustar fengfeng AdvancedSharePage Advanced share جوعارى دارەجەدەگى ھەمبەھرلەش Samba password: قۇپيا نومەرى Samba Samba set user password Samba الارمان قۇپيا نۇمىردى ورناتقان Warning ەسكەرتۋ Samba set password failed, Please re-enter! قۇپيا نومەر ورناتۋ جەڭىلىپ قالدى، قايتادان كىرگىزىڭىز Samba Tips ەسكەرتپەۋ The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager نۇ الارمان samba قۇپيا نۇمىردى ورناتپاعان، ٴسىز ٪1 الارمانقا كىرىشىڭىز كەرەك، حۇجات باسقارۋشنىڭ وڭ جاق ٷستٸندەگٸ تىزىمدىگىنە samba قۇپيا نۇمىردى ورناتساڭىز بولادٸ Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! ھەمبەھرلىنىش تولىقتاماسى قىزىمەت وتەۋى نورمال ەمەس، قازىر ھەمبەھرلىنىش جوبالاۋى يۈزگۈزىلىۋاتقان ياكي يۈزگۈزىلىۋاتمىغانلىقىنى انىقتاڭىز ياكي قايتادان ھەمبەھرلىنىڭ User الارمان مى Writable جازۋعا بولادٸ Readonly تەك قانا وقۋعا بولادٸ Reject رەت ەتۋ Share permission settings ھەمبەھرلىنىش ولشەمىن جٶبالاۋ delete حابار Add قوسۋ Save ساقتاۋ Cancel كۇشىنەن قالدىرۋ NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension ھەمبەھرلىنىش Allow user share folders حۇجات قىسقشتان ھەمبەھرلىنىشكە قوسىلۋ SharePage Share folder حۇجات قىسقشتان ھەمبەھرلىنىش Share: 共享: Warning ەسكەرتۋ The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. ورتاق پايدالانۋ بولۋ مى ٪1 نى ٶز ىشىنە السا بولمايدى ونىڭ ۇستىنە «-» ۋا بوستٸق مەنەن باشلىنىشقا ياكي بوستٸق مەنەن اياقتاتۋعا بولمايدى The share name cannot be the same as the current user name ھەمبەھر حۇجات مى قازىر بار بولعان سەستامانىڭ پايدالانۋشٸ مى ۇقساپ قالسا بولمايدى question سۇراستىرۋ The share name is already used by other users. You can rename the folder and then set the share. نۇ ھەمبەھر حۇجاتتىڭ مى باسقا ابونتتار جاقتارٸن ىستەتىلدى، ٴسىز حۇجات قىسۋاسپابىنىڭ ەسىمىن وزگەرتكەننەن كەيىن سونان قاتە ھەمبەھر ورىنداپ تەڭشەسەڭىز بولادٸ The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? نۇ حۇجات قىسقشتاعى كەزەكتەگى ھەمبەھر بولۋ فورماسى تەك وقۋ فورماسىلا ەمەس، كەزەكتەگى حۇجات قىسقىشىنى اۆتوماتتى كوبەيتىپ باسقالارعا جازۋ ۇقىعى بېرىلەمدۇ؟ Confirm adding permissions اسىرۋ ۇقىق كولەمىن بەكٸتۋ Cancel كۇشىنەن قالدىرۋ The folder is currently shared with anonymous access set, do I need to automatically add executable permissions for the current file and all other members of the parent directory? نۇ حۇجات قىسۋاسپابىنىڭ كەزەكتە اتسىز ھەمبەھرلىنىش قىزىمەتى تەڭشەلدٸ، وسى حۇجات ۋا باسقالاردٸڭ جۇرگىزۋ ولشەمىن اۆتوماتتى اسىرۋ كەرەكپە؟ The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! ھەمبەھرلىنىش تولىقتاماسى قىزىمەت وتەۋى نورمال ەمەس، قازىر ھەمبەھرلىنىش جوبالاۋى يۈزگۈزىلىۋاتقان ياكي يۈزگۈزىلىۋاتمىغانلىقىنى انىقتاڭىز ياكي قايتادان ھەمبەھرلىنىڭ Samba password: قۇپيا نومەرى Samba Samba set user password Samba الارمان قۇپيا نۇمىردى ورناتقان Samba set password failed, Please re-enter! قۇپيا نومەر ورناتۋ جەڭىلىپ قالدى، قايتادان كىرگىزىڭىز Samba usershare مىناۋ جابدىقدان ھەمبەھرلىنىش share this folder نۇ حۇجات قىسقىش ھەمبەھرلىنىش don`t share this folder نۇ حۇجات قىسقشتان ورتاق پايدالانۋ بولمايدى Share name: ورتاق ٴلاززاتتانۋ بولۋ Allow others to create and delete files باسقالاردٸڭ حۇجات ورناتۋىنا ۋا ئۆچۈرۈشىگە جول قويۋ Advanced Sharing جوعارى دارەجەلىك ھەمبەھرلىنىش Read Only Тек оқу Allow Anonymous ئىختىياري قاريدارلاردىڭ ۇسىنس قىلۋىنا جول قويۋ Comment: ەسكەرتپە peony-extensions/peony-share/app/translations/peony-share-extension_ug_CN.ts0000664000175000017500000002474015156143275026471 0ustar fengfeng AdvancedSharePage Advanced share Samba password: Samba set user password Warning Samba set password failed, Please re-enter! Tips The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! User Writable Readonly Reject Share permission settings delete Add Save Cancel NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension مودەني Qt پاي چېكىنى ئۇزارتىش Allow user share folders ئىشلەتكۈچى ئورتاق ھۆججەت قىسقۇچلىرىغا ئىجازەت بېرىش SharePage Share folder ھۆججەت قىسقۇچنى ئورتاقلاش Share: 共享: Warning The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. question The share name is already used by other users. You can rename the folder and then set the share. The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? Confirm adding permissions Cancel The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Samba password: Samba set user password Samba set password failed, Please re-enter! usershare usershare share this folder بۇ ھۆججەت قىسقۇچنى ئورتاقلاش don`t share this folder بۇ ھۆججەت قىسقۇچنى ئورتاق ئىشلەتمەڭ Share name: ھەمبەھىر نامى: Allow others to create and delete files Advanced Sharing Read Only پەقەت ئوقۇڭ Allow Anonymous نامسىز رۇخسەت Comment: باھا بېرىڭ: peony-extensions/peony-share/app/translations/peony-share-extension_cs.ts0000664000175000017500000002444715156143275026107 0ustar fengfeng AdvancedSharePage Advanced share Samba password: Samba set user password Warning Samba set password failed, Please re-enter! Tips The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! User Writable Readonly Reject Share permission settings delete Add Save Cancel NetUsershareHelper Peony-Qt-Share-Extension Peony-Qt-Share-Extension Peony::SharePropertiesPagePlugin Peony Qt Share Extension Rozšíření do Peony Qt pro správu sdílení Allow user share folders Umožnit uživateli sdílet složku SharePage Share folder Sdílet složku Share: Zdroj: Warning The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. question The share name is already used by other users. You can rename the folder and then set the share. The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? Confirm adding permissions Cancel The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Samba password: Samba set user password Samba set password failed, Please re-enter! usershare share this folder don`t share this folder Share name: Allow others to create and delete files Advanced Sharing Read Only Pouze pro čtení Allow Anonymous Umožnit anonymní Comment: Komentář: peony-extensions/peony-share/app/translations/peony-share-extension_bo_CN.ts0000664000175000017500000002525315156143275026456 0ustar fengfeng AdvancedSharePage Advanced share Samba password: Samba set user password Warning Samba set password failed, Please re-enter! Tips The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! User Writable Readonly Reject Share permission settings delete Add Save Cancel NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension མཉམ་སྤྱོད། Allow user share folders ཡིག་ཁུག་མཉམ་སྤྱོད་བྱེད་ཆོག SharePage Share folder ཡིག་ཁུག་མཉམ་སྤྱོད། Share: 共享: Warning The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. question The share name is already used by other users. You can rename the folder and then set the share. The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? Confirm adding permissions Cancel The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Samba password: Samba set user password Samba set password failed, Please re-enter! usershare གློག་ཀླད་འདིའི་མཉམ་སྤྱོད་བྱེད་པ། share this folder ཡིག་ཁུག་འདི་མཉམ་སྤྱོད། don`t share this folder ཡིག་ཁུག་འདི་མཉམ་སྤྱོད་མི་བྱེད་པ། Share name: མཉམ་སྤྱོད་ཀྱི་མིང་།: Allow others to create and delete files Advanced Sharing Read Only ཀློག་ཙམ། Allow Anonymous ཡུལ་སྐོར་བར་ལྟ་སྤྱོད་བྱས་ཆོག Comment: མཆན་འགྲེལ།: peony-extensions/peony-share/app/translations/peony-share-extension_ky_KG.ts0000664000175000017500000003334315156143275026501 0ustar fengfeng AdvancedSharePage Advanced share جوعورۇ چەگىندەكى ھەمبەھرلەش Samba password: جاشىرۇۇن نومۇرۇ Samba Samba set user password Samba كەرەكتۅۅچۉ جاشىرۇۇن نومۇرۇن ورنوتقون Warning ەسكەرتۉۉ Samba set password failed, Please re-enter! جاشىرۇۇن نومۇر ورنوتۇ جەڭىلۉۉ بولدۇ ، قايتادان كىرگىزىڭ Samba Tips ەسكەرتۉۉ The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager بۇل كەرەكتۅۅچۉ samba جاشىرۇۇن نومۇرۇن ورنوتبوعون، سىز ٪1 كەرەكتۅۅچۉقا كىرىشىڭىز كەرەك، ۅجۅت باشقارۇۇچۇنۇن وڭ تاراپ ۉستۉندۆكۉ تىزىمدىگىنە samba جاشىرۇۇن نومۇرۇن ورنوتسوڭۇز بولوت Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! ھەمبەھرلىنىش سەپتەمەسى سان قاينارى نورماال ەمەس ، ازىر ھەمبەھرلىنىش ماشعۇلاتى يۈزگۈزىلىۋاتقان كۅرۉنۉشتۅرۉ يۈزگۈزىلىۋاتمىغانلىقىنى ايقىنداڭ كۅرۉنۉشتۅرۉ قايتادان ھەمبەھرلىنىڭ User كەرەكتۅۅچۉ ناامى Writable جازىشقا بولوت Readonly جالاڭ عانا وقۇۇعا بولوت Reject قاتار جاسوو ،اتقارۇۇ Share permission settings ھەمبەھرلىنىش چەكتەمەسىن دولبوورلوش delete ۅچۉر Add قوشۇۇ Save ساقتوو Cancel ارعادان قالتىرىش NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension ھەمبەھرلىنىش Allow user share folders ۅجۅت قىسقىچتان ھەمبەھرلىنىشكە قوشۇلۇۇ SharePage Share folder ۅجۅت قىسقىچتان ھەمبەھرلىنىش Share: 共享: Warning ەسكەرتۉۉ The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. ورتوق وزۇرلانۇۇ بولۇش ناامى ٪1 نى ۅز ىچىنە السا بولبويت داعى «-» جانا بوشتۇق مەنەن باشلىنىشقا كۅرۉنۉشتۅرۉ بوشتۇق مەنەن اقىرلاشتىرۇۇعا بولبويت The share name cannot be the same as the current user name ھەمبەھر ۅجۅت ناامى ازىر بار بولعون سەستىمانىن ىشتەتۉۉچۉ ناامى وقشوش قالسا بولبويت question ىزىكتەش The share name is already used by other users. You can rename the folder and then set the share. بۇل ھەمبەھر ۅجۅتتۉن ناامى باشقا ابونتتار بىر جاعىنان ىشتەتىلدى، سىز ۅجۅت قىسقىچتىن اتىن ۅزگۅرتكۅندۅن كىيىن اندان قايرا ھەمبەھر جاساپ تەڭشەسەڭىز بولوت The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? بۇل ۅجۅت قىسقىچتاقى گەزەكتەكى ھەمبەھر بولۇش ۉلگۉسۉ جالاڭ عانا وقۇۇ ۉلگۉسۉلا ەمەس ، گەزەكتەكى ۅجۅت قىپچىعىچتى اپتوماتتىك كۅبۅيتۉپ باشقالارعا جازۇۇ ۇقۇعۇ بېرىلەمدۇ؟ Confirm adding permissions اشىرۇۇ ۇقۇق گۅۅلۅمۉن بەكىتۉۉ Cancel ارعادان قالتىرىش The folder is currently shared with anonymous access set, do I need to automatically add executable permissions for the current file and all other members of the parent directory? بۇل ۅجۅت قىسقىچتىن گەزەكتە اتى جوق ھەمبەھرلىنىش جۅندۅمۉ تەڭشەلدى، ۇشۇل ۅجۅت جانا باشقالاردىن جۉرگۅزۉ چەكتەمەسىن اپتوماتتىك اشىرۇۇ كەرەكبى؟ The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! ھەمبەھرلىنىش سەپتەمەسى سان قاينارى نورماال ەمەس ، ازىر ھەمبەھرلىنىش ماشعۇلاتى يۈزگۈزىلىۋاتقان كۅرۉنۉشتۅرۉ يۈزگۈزىلىۋاتمىغانلىقىنى ايقىنداڭ كۅرۉنۉشتۅرۉ قايتادان ھەمبەھرلىنىڭ Samba password: جاشىرۇۇن نومۇرۇ Samba Samba set user password Samba كەرەكتۅۅچۉ جاشىرۇۇن نومۇرۇن ورنوتقون Samba set password failed, Please re-enter! جاشىرۇۇن نومۇر ورنوتۇ جەڭىلۉۉ بولدۇ ، قايتادان كىرگىزىڭ Samba usershare بۇل ،ۇشۇل اسپاپتان ھەمبەھرلىنىش share this folder بۇل ۅجۅت قىپچىعىچ ھەمبەھرلىنىش don`t share this folder بۇل ۅجۅت قىسقىچتان ورتوق وزۇرلانۇۇ بولبويت Share name: ورتوق باارلانۇۇ بولۇش Allow others to create and delete files باشقالاردىن ۅجۅت قۇرۇۇعا جانا ئۆچۈرۈشىگە جول قويۇش Advanced Sharing جوعورۇ چەكتۉۉ ھەمبەھرلىنىش Read Only Окуу гана Allow Anonymous ئىختىياري ابونىتتاردىن زىيارات قىلۇۇعا جول قويۇش Comment: تۉشۉنۉك بەرۉۉ peony-extensions/peony-share/app/translations/peony-share-extension_de.ts0000664000175000017500000002757015156143275026072 0ustar fengfeng AdvancedSharePage Advanced share Erweiterte Freigabe Samba set user password Samba-Benutzerkennwort festlegen Samba password: Samba-Passwort: Warning Warnung Samba set password failed, Please re-enter! Samba Passwort gesetzt fehlgeschlagen, Bitte erneut eingeben! Tips Tipps The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager Der Benutzer hat das Samba-Passwort nicht festgelegt. Wenn Sie sich bei %1 anmelden müssen, können Sie dies im oberen rechten Menü des Dateimanagers festlegen Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Ausnahme für den freigegebenen Konfigurationsdienst, bitte bestätigen Sie, ob ein laufender gemeinsamer Konfigurationsvorgang vorhanden ist, oder setzen Sie die Freigabe zurück! User Benutzer Writable Beschreibbar Readonly Schreibgeschützt Reject Ablehnen Share permission settings Einstellungen für Freigabeberechtigungen delete löschen Add Hinzufügen Save Retten Cancel Abbrechen NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension Pfingstrose Qt-Freigabeerweiterung Allow user share folders Zulassen von Benutzerfreigabeordnern SharePage Share folder Ordner freigeben Share: 共享: The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? Der Ordner ist derzeit im nicht schreibgeschützten Modus freigegeben, und zum Einrichten der Freigabe müssen die Schreibberechtigungen anderer Personen für den aktuellen Ordner hinzugefügt werden. question Frage The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? Confirm adding permissions Bestätigen Sie das Hinzufügen von Berechtigungen Cancel Abbrechen Warning Warnung The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. Der Freigabename darf %1 nicht enthalten und darf nicht mit einem Bindestrich (-) oder Leerzeichen beginnen oder mit Leerzeichen enden. The share name cannot be the same as the current user name Der Freigabename darf nicht mit dem aktuellen Benutzernamen identisch sein The share name is already used by other users. You can rename the folder and then set the share. Der Freigabename wird bereits von anderen Benutzern verwendet. Sie können den Ordner umbenennen und dann die Freigabe festlegen. The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions of the current file and others in the ancestor directory? Der Ordner wird derzeit mit anonymem Zugriff freigegeben, muss ich die Berechtigungen für ausführbare Dateien der aktuellen Datei und anderer Dateien im Vorgängerverzeichnis automatisch erhöhen? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Ausnahme für den freigegebenen Konfigurationsdienst, bitte bestätigen Sie, ob ein laufender gemeinsamer Konfigurationsvorgang vorhanden ist, oder setzen Sie die Freigabe zurück! Samba set user password Samba-Benutzerkennwort festlegen Samba password: Samba-Passwort: The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Samba set password failed, Please re-enter! Samba Passwort gesetzt fehlgeschlagen, Bitte erneut eingeben! usershare usershare share this folder Diesen Ordner freigeben don`t share this folder Diesen Ordner nicht freigeben Share name: Name der Freigabe: Allow others to create and delete files Read Only Schreibgeschützt Allow Anonymous Anonym zulassen Advanced Sharing Erweiterte Freigabe Comment: Kommentar: peony-extensions/peony-share/app/translations/peony-share-extension_zh_HK.ts0000664000175000017500000002731615156143275026503 0ustar fengfeng AdvancedSharePage Advanced share 高級共用 Samba set user password Samba 設置用戶密碼 Samba password: Samba 密碼: Warning 警告 Samba set password failed, Please re-enter! Samba 設置密碼失敗,請重新輸入! Tips 技巧 The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager 使用者尚未設置 samba 密碼。如果需要登錄 %1,可以在檔案管理員的右上方功能表中進行設置 Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! 共用配置服務異常,請確認是否有正在進行的共用配置作,否則請重置共用! User 使用者 Writable Readonly 唯讀 Reject 拒絕 Share permission settings 共用許可權設置 delete 刪除 Add Save Cancel 取消 NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension Peony Qt 股票擴展 Allow user share folders 允許使用者共享資料夾 SharePage Share folder 共用資料夾 Share: 共享: The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? 資料夾目前以非唯讀模式共用,設置共用需要添加其他人對當前資料夾的寫入許可權? question 問題 Confirm adding permissions 確認添加許可權 Cancel 取消 Warning 警告 The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. 共用名稱不得包含 %1,不能以短劃線 (-) 或空格開頭,也不能以空格結尾。 The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions of the current file and others in the ancestor directory? 该文件夹当前共享设置了匿名访问,是否需要自动增加当前文件和其他人的可执行权限? The share name cannot be the same as the current user name 共用名稱不能與當前使用者名相同 The share name is already used by other users. You can rename the folder and then set the share. 共用名稱已被其他使用者使用。您可以重新命名資料夾,然後設置共用。 The folder is currently shared with anonymous access set, do I need to automatically add executable permissions for the current file and all other members of the parent directory? 資料夾目前已使用匿名訪問集共用,是否需要自動為當前檔和父目錄的所有其他成員添加可執行許可權? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! 共用配置服務異常,請確認是否有正在進行的共用配置作,否則請重置共用! Samba set user password Samba 設置用戶密碼 Samba password: Samba 密碼: The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? 共用名稱已存在,是否要替換原來的共享資料夾? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Samba set password failed, Please re-enter! Samba 設置密碼失敗,請重新輸入! usershare 用戶共用 share this folder 共用此資料夾 don`t share this folder 不共用此資料夾 Share name: 股票名稱: Allow others to create and delete files 允許其他人創建和刪除檔 Read Only 只读 Allow Anonymous 允許匿名 Advanced Sharing 高級共用 Comment: 評論: peony-extensions/peony-share/app/translations/peony-share-extension_fr.ts0000664000175000017500000002767415156143275026116 0ustar fengfeng AdvancedSharePage Advanced share Partage avancé Samba set user password Samba set le mot de passe de l’utilisateur Samba password: Mot de passe de la samba : Warning Avertissement Samba set password failed, Please re-enter! Échec de l’ensemble du mot de passe Samba, veuillez entrer à nouveau ! Tips Conseils The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager L’utilisateur n’a pas défini le mot de passe samba. Si vous avez besoin de vous connecter à %1, vous pouvez le définir dans le menu supérieur droit du gestionnaire de fichiers Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Exception au service de configuration partagée, veuillez confirmer s’il existe une opération de configuration partagée en cours, ou réinitialisez le partage ! User Utilisateur Writable Inscriptible Readonly Lecture seule Reject Rejeter Share permission settings Paramètres d’autorisation de partage delete supprimer Add Ajouter Save Sauvegarder Cancel Annuler NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension Extension de l’action Peony Qt Allow user share folders Autoriser les dossiers de partage d’utilisateurs SharePage Share folder Partager le dossier Share: 共享: The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? Le dossier est actuellement partagé en mode non lecture seule et la configuration du partage nécessite l’ajout des autorisations d’écriture d’autres personnes au dossier actuel ? question question The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? Confirm adding permissions Confirmer l’ajout d’autorisations Cancel Annuler Warning Avertissement The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. Le nom du partage ne doit pas contenir %1 et ne peut pas commencer par un tiret (-) ou un espace, ni se terminer par un espace. The share name cannot be the same as the current user name Le nom du partage ne peut pas être le même que le nom d’utilisateur actuel The share name is already used by other users. You can rename the folder and then set the share. Le nom du partage est déjà utilisé par d’autres utilisateurs. Vous pouvez renommer le dossier, puis définir le partage. The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions of the current file and others in the ancestor directory? Le dossier est actuellement partagé avec un accès anonyme défini, dois-je augmenter automatiquement les autorisations exécutables du fichier actuel et des autres fichiers du répertoire ancêtre ? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Exception au service de configuration partagée, veuillez confirmer s’il existe une opération de configuration partagée en cours, ou réinitialisez le partage ! Samba set user password Samba set le mot de passe de l’utilisateur Samba password: Mot de passe de la samba : The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Samba set password failed, Please re-enter! Échec de l’ensemble du mot de passe Samba, veuillez entrer à nouveau ! usershare Partage d’utilisateurs share this folder Partager ce dossier don`t share this folder Ne pas partager ce dossier Share name: Nom de l’action : Allow others to create and delete files Read Only Lecture seule Allow Anonymous Autoriser l’anonymat Advanced Sharing Partage avancé Comment: Commentaire: peony-extensions/peony-share/app/translations/peony-share-extension_tr.ts0000664000175000017500000002443215156143275026121 0ustar fengfeng AdvancedSharePage Advanced share Samba password: Samba set user password Warning Samba set password failed, Please re-enter! Tips The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! User Writable Readonly Reject Share permission settings delete Add Save Cancel NetUsershareHelper Peony-Qt-Share-Extension Peony-Qt-Share-Eklentisi Peony::SharePropertiesPagePlugin Peony Qt Share Extension Peony Qt Share Eklentisi Allow user share folders Kullanıcı paylaşım klasörlerine izin ver SharePage Share folder Klasör paylaş Share: Paylaş: Warning The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. question The share name is already used by other users. You can rename the folder and then set the share. The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? Confirm adding permissions Cancel The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Samba password: Samba set user password Samba set password failed, Please re-enter! usershare share this folder don`t share this folder Share name: Allow others to create and delete files Advanced Sharing Read Only Sadece Okunabilir Allow Anonymous Herkese İzin Ver Comment: Yorum yap: peony-extensions/peony-share/app/translations/peony-share-extension_es.ts0000664000175000017500000002767315156143275026115 0ustar fengfeng AdvancedSharePage Advanced share Recurso compartido avanzado Samba set user password Samba establece la contraseña de usuario Samba password: Contraseña de Samba: Warning Advertencia Samba set password failed, Please re-enter! Error en la configuración de la contraseña de Samba, ¡vuelva a ingresar! Tips Consejos The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager El usuario no ha establecido la contraseña de samba. Si necesita iniciar sesión en %1, puede configurarlo en el menú superior derecho del administrador de archivos Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Excepción del servicio de configuración compartida, confirme si hay una operación de configuración compartida en curso o restablezca el recurso compartido. User Usuario Writable Escritura Readonly Solo lectura Reject Rechazar Share permission settings Configuración de permisos de uso compartido delete borrar Add Agregar Save Salvar Cancel Cancelar NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension Extensión de acciones de Peony Qt Allow user share folders Permitir carpetas compartidas de usuario SharePage Share folder Compartir carpeta Share: 共享: The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? La carpeta se comparte actualmente en modo de solo lectura y la configuración del recurso compartido requiere agregar los permisos de escritura de otras personas a la carpeta actual. question pregunta The share name cannot be the same as an existing user name The share name already exists, do you want to replace the original share folder? Confirm adding permissions Confirmar la adición de permisos Cancel Cancelar Warning Advertencia The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. El nombre del recurso compartido no debe contener %1 y no puede comenzar con un guión (-) o un espacio en blanco, ni terminar con un espacio en blanco. The share name cannot be the same as the current user name El nombre del recurso compartido no puede ser el mismo que el nombre de usuario actual The share name is already used by other users. You can rename the folder and then set the share. El nombre del recurso compartido ya está siendo utilizado por otros usuarios. Puede cambiar el nombre de la carpeta y, a continuación, establecer el recurso compartido. The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions of the current file and others in the ancestor directory? La carpeta se comparte actualmente con el conjunto de acceso anónimo, ¿necesito aumentar automáticamente los permisos ejecutables del archivo actual y otros en el directorio antecesor? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! Excepción del servicio de configuración compartida, confirme si hay una operación de configuración compartida en curso o restablezca el recurso compartido. Samba set user password Samba establece la contraseña de usuario Samba password: Contraseña de Samba: The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Samba set password failed, Please re-enter! Error en la configuración de la contraseña de Samba, ¡vuelva a ingresar! usershare Compartir usuario share this folder Compartir esta carpeta don`t share this folder No compartas esta carpeta Share name: Nombre de la acción: Allow others to create and delete files Read Only Solo lectura Allow Anonymous Permitir anónimo Advanced Sharing Uso compartido avanzado Comment: Comentario: peony-extensions/peony-share/app/translations/peony-share-extension_zh_CN.ts0000664000175000017500000002747115156143275026503 0ustar fengfeng AdvancedSharePage Advanced share 高级共享 Samba set user password Samba设置用户密码 Samba password: Samba密码: Warning 警告 Samba set password failed, Please re-enter! Samba设置密码失败,请重新输入! Tips 提示 The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager 该用户没有设置samba密码。你需要登录%1用户,可以在文件管理器的右上菜单中进行设置samba密码 Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! 共享配置服务异常,请确认是否有正在进行的共享配置操作,或者请重新设置共享! User 用户 Writable 可写 Readonly 只读 Reject 拒绝 Share permission settings 共享权限设置 delete 删除 Add 增加 Save 保存 Cancel 取消 NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension 共享 Allow user share folders 允许共享文件夹 SharePage Share folder 共享文件夹 Share: 共享: The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? 该文件夹当前共享模式为非只读模式,是否需要自动增加当前文件夹增加其他人的写权限? question 询问 Confirm adding permissions 确定增加权限 Cancel 取消 Warning 警告 The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. 共享名不能包含%1,并且不能以-开头或者空格开头,或者空格结尾 The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions of the current file and others in the ancestor directory? 该文件夹当前共享设置了匿名访问,是否需要自动增加当前文件和其他人的可执行权限? The share name cannot be the same as the current user name 共享文件名不能和已有用系统户名一致 The share name is already used by other users. You can rename the folder and then set the share. 此共享文件名称已经被其它用户使用,您可以尝试重命名文件夹后再设置共享。 The folder is currently shared with anonymous access set, do I need to automatically add executable permissions for the current file and all other members of the parent directory? 该文件夹当前共享设置了匿名访问,是否需要自动增加当前文件和其他人的可执行权限? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! 共享配置服务异常,请确认是否有正在进行的共享配置操作,或者请重新设置共享! Samba set user password Samba设置用户密码 Samba password: Samba密码: The share name cannot be the same as an existing user name 共享名称不能与已有用户名相同 The share name already exists, do you want to replace the original share folder? 存在同名的共享,是否更改共享目录? The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? 该文件夹当前共享设置了匿名访问,是否需要自动增加当前文件和所有父目录的其他人的可执行权限? Samba set password failed, Please re-enter! Samba设置密码失败,请重新输入! usershare 本机共享 share this folder 共享此文件夹 don`t share this folder 不共享此文件夹 Share name: 共享名: Allow others to create and delete files 允许其它人创建和删除文件 Read Only 只读 Allow Anonymous 允许游客访问 Advanced Sharing 高级共享 Comment: 注释: peony-extensions/peony-share/app/translations/peony-share-extension_mn.ts0000664000175000017500000004030015156143275026076 0ustar fengfeng AdvancedSharePage Advanced share ᠳᠡᠭᠡᠳᠦ ᠵᠡᠷᠭᠡ ᠶᠢᠨ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠨ᠎ᠡ ᠃ Samba set user password ᠰᠠᠩᠪᠠ ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢᠳ ᠦᠨ ᠨᠢᠭᠤᠴᠠ ᠨᠣᠮᠧᠷ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠨ᠎ᠠ ᠃ Samba password: ᠰᠠᠩᠪᠠ ᠶᠢᠨ ᠨᠢᠭᠤᠴᠠ ᠨᠣᠮᠧᠷ Warning ᠠᠨᠭᠬᠠᠷᠠᠭᠤᠯᠬᠤ Samba set password failed, Please re-enter! ᠰᠠᠩᠪᠠ ᠨᠢᠭᠤᠴᠠ ᠨᠣᠮᠧᠷ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠵᠤ ᠢᠯᠠᠭᠳᠠᠯ ᠳᠤ ᠣᠷᠣᠭᠤᠯᠵᠠᠢ ᠂ ᠳᠠᠬᠢᠨ ᠣᠷᠣᠭᠤᠯᠬᠤ ᠪᠣᠯᠪᠠᠤ ! Tips ᠮᠡᠷᠭᠡᠵᠢᠯ ᠃ The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢᠳ Sammb ᠨᠢᠭᠤᠴᠠ ᠨᠣᠮᠧᠷ ᠲᠠᠯᠪᠢᠭᠠᠳᠤᠢ ᠪᠠᠶᠢᠭᠰᠠᠭᠠᠷ ᠃ ᠬᠡᠷᠪᠡ 1 ᠳᠤ ᠭᠠᠷᠬᠤ ᠴᠢᠬᠤᠯᠠ ᠲᠠᠢ ᠪᠣᠯ ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠯᠲᠠ ᠶᠢᠨ ᠪᠠᠭᠠᠵᠢ ᠶᠢᠨ ᠪᠠᠷᠠᠭᠤᠨ ᠥᠨᠴᠥᠭ ᠳᠡᠭᠡᠷ᠎ᠡ ᠪᠠᠶᠢᠷᠢᠯᠠᠭᠤᠯᠵᠤ ᠪᠣᠯᠣᠨ᠎ᠠ ᠃ Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳᠯᠡᠵᠦ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠬᠤ ᠦᠢᠯᠡᠴᠢᠯᠡᠭᠡ ᠨᠢ ᠬᠡᠪ ᠦᠨ ᠪᠤᠰᠤ ᠂ ᠶᠠᠭ ᠬᠢᠵᠦ ᠪᠠᠶᠢᠭ᠎ᠠ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳᠯᠡᠬᠦ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠯᠲᠠ ᠶᠢᠨ ᠠᠵᠢᠯ ᠪᠠᠶᠢᠬᠤ ᠡᠰᠡᠬᠦ ᠶᠢ ᠨᠤᠲᠠᠯᠠᠬᠤ ᠪᠤᠶᠤ ᠳᠠᠬᠢᠨ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠵᠤ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠶᠢ ᠨᠤᠲᠠᠯᠠᠭᠠᠷᠠᠢ ! User ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠵᠢᠨ ᠨᠡᠷ᠎ᠡ Writable ᠪᠢᠴᠢᠬᠦ Readonly ᠵᠦᠪᠬᠡᠨ ᠤᠩᠰᠢᠬᠤ Reject ᠲᠡᠪᠴᠢᠬᠦ ᠡᠴᠡ ᠲᠠᠲᠠᠭᠠᠯᠵᠠᠪᠠ ᠃ Share permission settings ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠡᠷᠬᠡ ᠮᠡᠳᠡᠯ ᠦᠨ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠯᠲᠠ ᠃ delete ᠬᠠᠰᠤᠬᠤ Add ᠨᠡᠮᠡᠬᠦ Save ᠪᠠᠳᠤᠯᠠᠬᠤ Cancel ᠦᠭᠡᠢᠰᠬᠡᠬᠦ NetUsershareHelper Peony-Qt-Share-Extension 共享 Peony::SharePropertiesPagePlugin Peony Qt Share Extension ᠬᠠᠮᠳᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ Allow user share folders ᠹᠠᠢᠯ ᠤ᠋ᠨ ᠬᠠᠪᠳᠠᠰᠤ ᠵᠢ ᠬᠠᠮᠳᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠵᠢ ᠵᠦᠪᠰᠢᠶᠡᠷᠡᠬᠦ SharePage Share folder ᠹᠠᠢᠯ ᠤ᠋ᠨ ᠬᠠᠪᠳᠠᠰᠤ ᠵᠢ ᠬᠠᠮᠳᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ Share: 共享: Warning ᠠᠨᠭᠬᠠᠷᠠᠭᠤᠯᠬᠤ The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace. ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠨᠡᠷᠡᠶᠢᠳᠦᠯ ᠨᠢ 1 ᠪᠠᠭᠲᠠᠵᠤ ᠪᠣᠯᠬᠤ ᠦᠭᠡᠶ ᠮᠥᠷᠲᠡᠭᠡᠨ ᠣᠬᠣᠷ ᠱᠤᠭᠤᠮ ( — ) ᠪᠤᠶᠤ ᠬᠣᠭᠣᠰᠣᠨ ᠵᠠᠶ ᠪᠠᠷ ᠡᠬᠢᠯᠡᠵᠦ ᠪᠣᠯᠬᠤ ᠦᠭᠡᠶ ᠂ ᠬᠣᠭᠣᠰᠣᠨ ᠵᠠᠩ ᠢᠶᠠᠷ ᠲᠡᠭᠦᠰᠬᠡᠵᠦ ᠪᠣᠯᠬᠤ ᠦᠭᠡᠶ ᠃ The share name cannot be the same as the current user name ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠨᠡᠷᠡᠶᠢᠳᠦᠯ ᠨᠢ ᠣᠳᠣᠬᠠᠨ ᠤ ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠶᠢᠨ ᠨᠡᠷ᠎ᠡ ᠲᠡᠶ ᠠᠳᠠᠯᠢ ᠪᠠᠶᠢᠵᠤ ᠪᠣᠯᠬᠤ ᠦᠭᠡᠶ᠃ The share name cannot be the same as an existing user name question ᠠᠰᠠᠭᠤᠳᠠᠯ ᠃ The share name is already used by other users. You can rename the folder and then set the share. ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠨᠡᠷᠡᠶᠢᠳᠦᠯ ᠢ ᠪᠤᠰᠤᠳ ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠳᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠳᠡᠵᠡᠢ ᠃ ᠲᠠ ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠬᠠᠪᠤᠳᠠᠷ ᠢ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠯᠡᠭᠦᠯᠵᠦ ᠂ ᠳᠠᠷᠠᠭ᠎ᠠ ᠨᠢ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠪᠡᠷ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠵᠤ ᠪᠣᠯᠣᠨ᠎ᠠ᠃ The share name already exists, do you want to replace the original share folder? The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder? ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠸᠷᠢᠶᠠᠯ ᠢ ᠤᠲᠤᠬᠠᠨ ᠳᠤ ᠵᠦᠪᠭᠡᠨ ᠤᠩᠰᠢᠬᠤ ᠪᠤᠰᠤ ᠵᠠᠭᠪᠤᠷ ᠢᠶᠡᠷ ᠬᠠᠮᠲᠦ ᠪᠡᠷ ᠡᠳᠯᠡᠵᠦ ᠂ ᠬᠠᠮᠲᠦ ᠪᠡᠷ ᠡᠳᠯᠡᠭᠦ ᠳᠤ ᠪᠤᠰᠤᠳ ᠬᠦᠮᠦᠨ ᠤ ᠪᠢᠴᠢᠵᠦ ᠤᠷᠤᠭᠤᠯᠬᠤ ᠡᠷᠬᠡ ᠶᠢᠨ ᠬᠡᠪᠴᠢᠶ᠎ᠠ ᠶᠢ ᠤᠲᠤᠬᠠᠨ ᠤ ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠸᠷᠢᠶᠠᠯ ᠤᠨ ᠬᠠᠪᠴᠢᠭᠤᠯᠤᠯ ᠳᠤ ᠨᠡᠮᠡᠭᠦ ᠬᠡᠷᠡᠭᠲᠡᠢ ᠤᠤ ソ Confirm adding permissions ᠨᠡᠮᠡᠯᠲᠡ ᠡᠳ᠋ ᠦᠨ ᠡᠷᠬᠡ ᠮᠡᠳᠡᠯ ᠢ ᠨᠤᠲᠠᠯᠠᠨ ᠲᠣᠭᠲᠠᠭᠠᠬᠤ ᠬᠡᠷᠡᠭᠲᠡᠶ᠃ Cancel ᠦᠭᠡᠢᠰᠬᠡᠬᠦ The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory? Allow others to create and delete files The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions of the current file and others in the ancestor directory? ᠲᠤᠰ ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠢ ᠣᠳᠣᠬᠠᠨ ᠳᠤ ᠨᠡᠷ᠎ᠡ ᠪᠡᠨ ᠲᠣᠳᠣᠷᠬᠠᠶᠢᠯᠠᠨ ᠠᠶᠢᠯᠴᠢᠯᠠᠵᠤ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠳᠦ ᠣᠳᠣᠬᠠᠨ ᠤ ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠪᠣᠯᠤᠨ ᠡᠪᠦᠭᠡ ᠽᠦ ᠰᠢᠶᠠᠨ ᠤ ᠭᠠᠷᠴᠠᠭ ᠲᠠᠬᠢ ᠪᠤᠰᠤᠳ ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠬᠡᠷᠡᠭᠵᠢᠭᠦᠯᠬᠦ ᠡᠷᠬᠡ ᠮᠡᠳᠡᠯ ᠢ ᠠᠦ᠋ᠲ᠋ᠣ᠋ ᠪᠠᠷ ᠨᠡᠮᠡᠭᠳᠡᠭᠦᠯᠬᠦ ᠴᠢᠬᠤᠯᠠᠲᠠᠶ ᠤᠤ ? Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share! ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳᠯᠡᠵᠦ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠬᠤ ᠦᠢᠯᠡᠴᠢᠯᠡᠭᠡ ᠨᠢ ᠬᠡᠪ ᠦᠨ ᠪᠤᠰᠤ ᠂ ᠶᠠᠭ ᠬᠢᠵᠦ ᠪᠠᠶᠢᠭ᠎ᠠ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳᠯᠡᠬᠦ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠯᠲᠠ ᠶᠢᠨ ᠠᠵᠢᠯ ᠪᠠᠶᠢᠬᠤ ᠡᠰᠡᠬᠦ ᠶᠢ ᠨᠤᠲᠠᠯᠠᠬᠤ ᠪᠤᠶᠤ ᠳᠠᠬᠢᠨ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠵᠤ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠶᠢ ᠨᠤᠲᠠᠯᠠᠭᠠᠷᠠᠢ ! Samba set user password ᠰᠠᠩᠪᠠ ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢᠳ ᠦᠨ ᠨᠢᠭᠤᠴᠠ ᠨᠣᠮᠧᠷ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠨ᠎ᠠ ᠃ Samba password: ᠰᠠᠩᠪᠠ ᠶᠢᠨ ᠨᠢᠭᠤᠴᠠ ᠨᠣᠮᠧᠷ Samba set password failed, Please re-enter! ᠰᠠᠩᠪᠠ ᠨᠢᠭᠤᠴᠠ ᠨᠣᠮᠧᠷ ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠵᠤ ᠢᠯᠠᠭᠳᠠᠯ ᠳᠤ ᠣᠷᠣᠭᠤᠯᠵᠠᠢ ᠂ ᠳᠠᠬᠢᠨ ᠣᠷᠣᠭᠤᠯᠬᠤ ᠪᠣᠯᠪᠠᠤ ! usershare ᠳᠤᠰ ᠮᠠᠰᠢᠨ ᠤ᠋ ᠬᠠᠮᠳᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ share this folder ᠳᠤᠰ ᠹᠠᠢᠯ ᠤ᠋ᠨ ᠬᠠᠪᠳᠠᠰᠤ ᠵᠢ ᠬᠠᠮᠳᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ don`t share this folder ᠳᠤᠰ ᠹᠠᠢᠯ ᠤ᠋ᠨ ᠬᠠᠪᠳᠠᠰᠤ ᠵᠢ ᠬᠠᠮᠳᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠦᠬᠡᠢ Share name: ᠬᠠᠮᠳᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠬᠦ ᠨᠡᠷ᠎ᠡ: Read Only ᠵᠦᠪᠬᠡᠨ ᠤᠩᠰᠢᠬᠤ Allow Anonymous ᠵᠤᠷᠴᠢᠭᠴᠢ ᠠᠢᠯᠴᠢᠯᠠᠬᠤ ᠵᠢ ᠵᠦᠪᠰᠢᠶᠡᠷᠡᠬᠦ Advanced Sharing ᠳᠡᠭᠡᠳᠦ ᠵᠡᠷᠭᠡ ᠶᠢᠨ ᠬᠠᠮᠲᠤᠪᠠᠷ ᠡᠳ᠋ᠯᠡᠨ᠎ᠡ ᠃ Comment: ᠳᠠᠢᠯᠪᠤᠷᠢ: peony-extensions/peony-share/app/samba-config-interface.cpp0000664000175000017500000000212715156143137023041 0ustar fengfeng/* * Copyright (C) 2022, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Ding Jing * */ #include "samba-config-interface.h" SambaConfigInterface::SambaConfigInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) { } SambaConfigInterface::~SambaConfigInterface() { } peony-extensions/peony-share/app/samba-config-interface.h0000664000175000017500000000472015156143137022507 0ustar fengfeng/* * Copyright (C) 2022, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Ding Jing * */ #ifndef SAMBACONFIGINTERFACE_H #define SAMBACONFIGINTERFACE_H #include #include #include #include #include #include #include #include /* * Proxy class for interface org.ukui.samba.config.share */ class SambaConfigInterface: public QDBusAbstractInterface { Q_OBJECT public: static inline const char *staticInterfaceName() { return "org.ukui.samba.share.config"; } public: SambaConfigInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr); ~SambaConfigInterface(); public Q_SLOTS: // METHODS inline QDBusPendingReply<> finished() { QList argumentList; return callWithArgumentList(QDBus::Block, QStringLiteral("finished"), argumentList); } inline QDBusPendingReply hasPasswd() { QList argumentList; return callWithArgumentList(QDBus::Block, QStringLiteral("hasPasswd"), argumentList); } inline QDBusPendingReply init(const QString &name, int pid, int uid) { QList argumentList; argumentList << QVariant::fromValue(name) << QVariant::fromValue(pid) << QVariant::fromValue(uid); return callWithArgumentList(QDBus::Block, QStringLiteral("init"), argumentList); } inline QDBusPendingReply setPasswd(const QString &passwd) { QList argumentList; argumentList << QVariant::fromValue(passwd); return callWithArgumentList(QDBus::Block, QStringLiteral("setPasswd"), argumentList); } Q_SIGNALS: // SIGNALS }; #endif peony-extensions/peony-share/app/app.pro0000664000175000017500000000346615156143137017362 0ustar fengfengQT += core gui widgets concurrent dbus TARGET = peony-share TEMPLATE = lib DEFINES += PEONYSHARE_LIBRARY DEFINES += DBUS_NAME=\\\"org.ukui.samba.share.config\\\" DEFINES += DBUS_PATH=\\\"/org/ukui/samba/share\\\" include(../../common.pri) PKGCONFIG += peony kysdk-qtwidgets polkit-gobject-1 CONFIG += link_pkgconfig \ c++11 \ plugin \ debug SOURCES += share-page.cpp \ advanced-share-page.cpp \ share-emblem-provider.cpp \ samba-config-thread.cpp \ share-properties-page-plugin.cpp \ samba-config-interface.cpp \ systemd-bus-accounts.cpp HEADERS += share-page.h \ advanced-share-page.h \ share-emblem-provider.h \ samba-config-thread.h \ share-properties-page-plugin.h \ samba-config-interface.h \ systemd-bus-accounts.h TRANSLATIONS += translations/peony-share-extension_cs.ts \ translations/peony-share-extension_de.ts \ translations/peony-share-extension_es.ts \ translations/peony-share-extension_fr.ts \ translations/peony-share-extension_kk_KZ.ts \ translations/peony-share-extension_ug_CN.ts \ translations/peony-share-extension_ky_KG.ts \ translations/peony-share-extension_tr.ts \ translations/peony-share-extension_zh_CN.ts \ translations/peony-share-extension_bo_CN.ts \ translations/peony-share-extension_mn.ts \ translations/peony-share-extension_zh_HK.ts #RESOURCES += resources.qrc CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = /translations/ target.path = $$[QT_INSTALL_LIBS]/peony-extensions INSTALLS += target DISTFILES += \ translations/peony-share-extension_bo_CN.ts peony-extensions/peony-share/app/systemd-bus-accounts.cpp0000664000175000017500000000604415156143137022653 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2022, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #include "systemd-bus-accounts.h" #include SystemDbusAccounts::SystemDbusAccounts(QObject *parent) : QObject(parent) { m_systemInterface = new QDBusInterface("org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.Accounts", QDBusConnection::systemBus()); // connect(m_systemInterface, SIGNAL(UserAdded(QDBusObjectPath)), this, SLOT(create_user_success(QDBusObjectPath))); // connect(m_systemInterface, SIGNAL(UserDeleted(QDBusObjectPath)), this, SLOT(delete_user_success(QDBusObjectPath))); } SystemDbusAccounts::~SystemDbusAccounts() { delete m_systemInterface; m_systemInterface = NULL; } QStringList SystemDbusAccounts::getListCachedUsers() { QStringList userLists; QDBusReply> reply = m_systemInterface->call("ListCachedUsers"); if (reply.isValid()) { for (QDBusObjectPath op : reply.value()) { userLists << op.path(); } } return userLists; } QStringList SystemDbusAccounts::getAllUserNames() { QStringList userNames; QStringList objectPaths = getListCachedUsers(); if (!objectPaths.isEmpty()) { for (auto objectPath : objectPaths) { userNames << getUserName(objectPath); } } return userNames; } QString SystemDbusAccounts::getUserName(QString objectPath) { QString userName; QDBusInterface *interFace = new QDBusInterface("org.freedesktop.Accounts", objectPath, "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); QDBusReply reply = interFace->call("Get", "org.freedesktop.Accounts.User", "UserName"); if (reply.isValid()) { userName = reply.value().variant().toString(); } else { qDebug() << __func__ << "reply failed!"; } return userName; } void SystemDbusAccounts::createUserSuccess(QDBusObjectPath objectPath) { emit createUserDone(objectPath.path()); } void SystemDbusAccounts::deleteUserSuccess(QDBusObjectPath objectPath) { emit deleteUserDone(objectPath.path()); } peony-extensions/peony-share/app/share-emblem-provider.h0000664000175000017500000000340715156143137022415 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2021, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #ifndef SHAREEMBLEMPROVIDER_H #define SHAREEMBLEMPROVIDER_H #include #include #include #include #include "peony-core_global.h" #include "emblem-provider.h" #define SHARE_EMBLEMS "share-emblems" namespace Peony { class ShareEmblemProvider : public EmblemProvider { Q_OBJECT public: static ShareEmblemProvider *getInstance(); const QString emblemKey() override; QStringList getFileEmblemIcons(const QString &uri) override; private: explicit ShareEmblemProvider(QObject *parent = nullptr); }; class FileXattrInfo { public: static std::shared_ptr fromUri(const QString &uri); FileXattrInfo(const QString &uri); void setXattrInfoString(const QString &key, const QString &value, bool syncToFile = true); const QString getXattrInfoString(const QString &key); void removeXattrInfo(const QString &key); private: QString m_uri; QHash m_hash; }; } #endif // SHAREEMBLEMPROVIDER_H peony-extensions/peony-share/app/samba-config-thread.cpp0000664000175000017500000000467515156143137022362 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2021, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #include "samba-config-thread.h" #include SambaConfigThread::SambaConfigThread(const Peony::ShareInfo shareInfo, const QString uri, const QString acl) { m_shareInfoTmp = new Peony::ShareInfo(); m_shareInfoTmp->allowGuest = shareInfo.allowGuest; m_shareInfoTmp->comment = shareInfo.comment; m_shareInfoTmp->isShared = shareInfo.isShared; m_shareInfoTmp->name = shareInfo.name; m_shareInfoTmp->originalPath = shareInfo.originalPath; m_shareInfoTmp->readOnly = shareInfo.readOnly; m_uri = uri; m_acl = acl; } Peony::ShareInfo *SambaConfigThread::getShareInfo() { return m_shareInfoTmp; } QString SambaConfigThread::getUri() { return m_uri; } QString SambaConfigThread::getAcl() { return m_acl; } void SambaConfigThread::run() { SambaConfigInterface si(DBUS_NAME, DBUS_PATH, QDBusConnection::systemBus()); // struct timeval tpstart,tpend; // float timeuse; // gettimeofday(&tpstart, NULL); bool initRet = si.init(g_get_user_name(), getpid(), getuid()); // gettimeofday(&tpend, NULL); // timeuse = (1000000*(tpend.tv_sec-tpstart.tv_sec) + tpend.tv_usec-tpstart.tv_usec)/1000000.0; Q_EMIT isInitSignal(initRet); if (initRet) { bool hasPasswdRet = si.hasPasswd(); QString passwd = ""; qDebug()<< __func__ << __LINE__ << hasPasswdRet; Q_EMIT isHasPasswdSignal(hasPasswdRet, passwd); if (!passwd.isEmpty()) { qDebug()<< __func__ << __LINE__ << passwd; bool setPasswdRet = si.setPasswd(passwd); Q_EMIT isSetPasswdSignal(setPasswdRet); } } si.finished(); qDebug()<< __func__ << __LINE__ << "samba finished"; } peony-extensions/peony-share/app/samba-config-thread.h0000664000175000017500000000300415156143137022010 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2021, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #ifndef SAMBACONFIGTHREAD_H #define SAMBACONFIGTHREAD_H #include #include "samba-config-interface.h" #include "usershare-manager.h" class SambaConfigThread : public QThread { Q_OBJECT public: SambaConfigThread(const Peony::ShareInfo shareInfo, const QString uri, const QString acl); Peony::ShareInfo* getShareInfo(); QString getUri(); QString getAcl(); Q_SIGNALS: void isInitSignal(bool ret); void isFinishedSignal(bool ret); void isHasPasswdSignal(bool ret, QString &passwd); void isSetPasswdSignal(bool ret); void getPasswd(QString &passwd); protected: void run(); private: Peony::ShareInfo *m_shareInfoTmp; QString m_uri; QString m_acl; }; #endif // SAMBACONFIGTHREAD_H peony-extensions/peony-share/app/share-page.h0000664000175000017500000000657215156143275020251 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef SHAREPAGE_H #define SHAREPAGE_H #include #include #include #include #include #include #include #include #include #include #include #include #include "kswitchbutton.h" using namespace kdk; //#include "libpeony-qt/usershare-manager.h" using namespace Peony; class SharePage : public Peony::PropertiesWindowTabIface { Q_OBJECT public: explicit SharePage(const QString &uri, QWidget *parent = nullptr); void addSeparate() { QFrame * separate = new QFrame(this); separate->setContentsMargins(0, 0, 0, 0); separate->setFrameShape(QFrame::HLine); QPalette palette = separate->palette(); palette.setColor(QPalette::WindowText,QColor("#F0F0F0FF")); separate->setPalette(palette); m_layout->addWidget(separate); } void init(); void initFloorOne(); void initFloorTwo(); void initFloorThree(); /*! * 响应确定按钮 * \brief */ void saveAllChange() override; bool checkAuthorization(); bool checkSpecialSymbols(const QString& displayName); void delayExit(); void recursiveAddOtherExecute(const QString &uri, const bool &runbackend); bool addOtherWrite(const QString &uri, const bool &runbackend); void removeShareEmblem(const QString &uri); void removeShareInfo(const QString &name); void clearAclInfo(const QString &filePath); bool addShareInfo(ShareInfo& shareInfo, const QString usershareAcl); void addShareEmblem(const QString &uri); private: QFutureWatcher *m_theFutureWatcher = nullptr; Peony::ShareInfo m_shareInfo; QVBoxLayout* m_layout = nullptr; std::shared_ptr m_fileInfo = nullptr; //floor1 QLabel *m_iconLabel = nullptr; QLabel *m_folderName = nullptr; QLabel *m_sharedState = nullptr; //floor2 KSwitchButton *m_switchButton = nullptr; //floor3 QFrame *m_floor3 = nullptr; QLineEdit *m_shareNameEdit = nullptr; QLineEdit *m_commentEdit = nullptr; QPushButton *m_advanceShareButton = nullptr; QCheckBox *m_allowGuestModify = nullptr; QCheckBox *m_shareAllowGuestCheckBox = nullptr; bool m_ret = false; bool m_has_unix_mode = false; QString m_usershareAcl; QMap m_userInfoCache; QString m_originalPath; QString m_name; const ShareInfo *m_stmp = nullptr; }; #endif // SHAREPAGE_H peony-extensions/peony-share/app/advanced-share-page.h0000664000175000017500000000522415156143137022002 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2022, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #ifndef ADVANCEDSHAREPAGE_H #define ADVANCEDSHAREPAGE_H #include #include #include #include #include #include #include #include #include #include #include #include "file-utils.h" #include "systemd-bus-accounts.h" using namespace Peony; class AdvancedSharePage : public QWidget { Q_OBJECT public: explicit AdvancedSharePage(const QString &uri, const QMap &userInfo, QWidget *parent = nullptr); ~AdvancedSharePage(); void init(); void initTableWidget(); void initListWidget(); void initFloorOne(); void initFloorTwo(); void initFloorThree(); void initFloorFour(); void addSeparate(); void updateShareUserInfo(); void parseUserShareAcl(QString strAcl); bool updateCheckBox(int col, QString &name); void updateUserInfo(int row, int col, bool checked); QString parseSetAcl(QMap &userInfo); QString converPermission(QString &usershareAcl); void updateLabelShow(QLabel *label, const QString &str); protected Q_SLOTS: void updateDelBtnState(int row, int col); Q_SIGNALS: void getUserInfo(QString &acl, QMap &userInfo); private: QString m_uri; QMap m_userInfo; QMutex m_mutex; SystemDbusAccounts *m_sysAccounts = nullptr; QStringList m_userNames; bool m_thisPageChanged = false; QLabel *m_label = nullptr; QLabel *m_tabLabel = nullptr; QLabel *m_listLabel = nullptr; QTableWidget *m_tabWidget = nullptr; QListWidget *m_listWidget = nullptr; QPushButton *m_saveBtn = nullptr; QPushButton *m_cancelBtn = nullptr; QPushButton *m_addShareUserBtn = nullptr; QPushButton *m_delShareUserBtn = nullptr; QVBoxLayout *m_layout = nullptr; }; #endif // ADVANCEDSHAREPAGE_H peony-extensions/peony-share/app/systemd-bus-accounts.h0000664000175000017500000000277715156143137022331 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2022, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #ifndef SYSTEMDBUSACCOUNTS_H #define SYSTEMDBUSACCOUNTS_H #include #include #include #include class SystemDbusAccounts : public QObject { Q_OBJECT public: explicit SystemDbusAccounts(QObject *parent = nullptr); ~SystemDbusAccounts(); QStringList getListCachedUsers(); QStringList getAllUserNames(); QString getUserName(QString objectPath); public Q_SLOTS: void createUserSuccess(QDBusObjectPath objectPath); void deleteUserSuccess(QDBusObjectPath objectPath); Q_SIGNALS: void createUserDone(QString path); void deleteUserDone(QString path); private: QDBusInterface *m_systemInterface = nullptr; }; #endif // SYSTEMDBUSACCOUNTS_H peony-extensions/peony-share/app/share-emblem-provider.cpp0000664000175000017500000001103715156143275022751 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2021, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #include "share-emblem-provider.h" #include #include using namespace Peony; static ShareEmblemProvider *global_instance = nullptr; ShareEmblemProvider *ShareEmblemProvider::getInstance() { if(!global_instance) global_instance = new ShareEmblemProvider; return global_instance; } const QString ShareEmblemProvider::emblemKey() { return SHARE_EMBLEMS; } QStringList ShareEmblemProvider::getFileEmblemIcons(const QString &uri) { QStringList lists; if (uri.startsWith("file:///")) { auto fileInfo = FileXattrInfo::fromUri(uri); if(fileInfo && !fileInfo->getXattrInfoString(SHARE_EMBLEMS).isEmpty()){ lists.append(fileInfo->getXattrInfoString(SHARE_EMBLEMS)); } } return lists; } ShareEmblemProvider::ShareEmblemProvider(QObject *parent) { Q_UNUSED(parent); } std::shared_ptr FileXattrInfo::fromUri(const QString &uri) { return std::make_shared(uri); } FileXattrInfo::FileXattrInfo(const QString &uri) { m_uri = uri; g_autoptr(GFile) file = g_file_new_for_uri(m_uri.toUtf8().constData()); if (file) { g_autoptr(GFileInfo) fileInfo = g_file_query_info(file, "xattr::*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, nullptr); if (fileInfo) { char **xattrinfo_attributes = g_file_info_list_attributes(fileInfo, "xattr"); if(xattrinfo_attributes){ for (int i = 0; xattrinfo_attributes[i] != nullptr; i++) { auto type = g_file_info_get_attribute_type(fileInfo, xattrinfo_attributes[i]); switch (type) { case G_FILE_ATTRIBUTE_TYPE_STRING: { char *string = g_file_info_get_attribute_as_string(fileInfo, xattrinfo_attributes[i]); if (string) { auto var = QVariant(QString::fromUtf8(string)); this->setXattrInfoString(xattrinfo_attributes[i], var.toString(), false); //qDebug()<<"xattrinfo_attributes======"<m_uri.toUtf8().constData()); GError *err = nullptr; g_file_set_attribute(file, realKey.toLatin1().data(), G_FILE_ATTRIBUTE_TYPE_STRING, (gpointer)value.toUtf8().data(), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, &err); if (err) { qWarning() << err->message; g_error_free(err); } else { m_hash.remove(realKey); m_hash.insert(realKey, value); } } else { m_hash.remove(realKey); m_hash.insert(realKey, value); } qDebug() << "setXattrInfoString" << m_hash; } const QString FileXattrInfo::getXattrInfoString(const QString &key) { QString realKey = key; if (!key.startsWith("xattr::")) realKey = "xattr::" + key; if (m_hash.value(realKey).isValid()) return m_hash.value(realKey).toString(); //FIXME: should i use gio query meta here? return QString(); } void FileXattrInfo::removeXattrInfo(const QString &key) { //FIXME: Failed to set invalid type this->setXattrInfoString(key, " ", true); } peony-extensions/peony-share/app/advanced-share-page.cpp0000664000175000017500000005001015156143275022331 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2022, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "advanced-share-page.h" #include "usershare-manager.h" #include "samba-config-interface.h" #include "global-settings.h" static const QString everyone = "Everyone"; static const int labelWidth = 310; AdvancedSharePage::AdvancedSharePage(const QString &uri, const QMap &userInfo, QWidget *parent) : QWidget(parent) { m_userInfo = userInfo; m_uri = uri; this->init(); } AdvancedSharePage::~AdvancedSharePage() { } void AdvancedSharePage::init() { this->setWindowTitle(tr("Advanced share")); this->setWindowIcon(QIcon::fromTheme("system-file-manager")); this->setContextMenuPolicy(Qt::CustomContextMenu); this->setAttribute(Qt::WA_DeleteOnClose); this->setFixedSize(460, 600); this->setContentsMargins(0, 10, 0, 0); this->setWindowFlags(this->windowFlags() & ~Qt::WindowMinMaxButtonsHint & ~Qt::WindowSystemMenuHint); this->setWindowModality(Qt::ApplicationModal); m_layout = new QVBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); this->updateShareUserInfo(); this->initFloorOne(); this->initFloorTwo(); this->addSeparate(); this->initFloorThree(); this->addSeparate(); this->initFloorFour(); connect(m_listWidget, &QListWidget::currentTextChanged, this, [=](QString currentText){ if (!m_userInfo.contains(currentText)) { m_addShareUserBtn->setEnabled(true); } else { m_addShareUserBtn->setEnabled(false); } m_listLabel->setText(currentText); updateLabelShow(m_listLabel, currentText); }); connect(m_addShareUserBtn, &QPushButton::clicked, this, [=](){ QListWidgetItem *item = m_listWidget->currentItem(); if (nullptr != item) { QString name = item->text(); QString username = g_get_user_name(); auto settings = GlobalSettings::getInstance(); bool runbackend = settings->getInstance()->getValue(RESIDENT_IN_BACKEND).toBool(); qApp->setQuitOnLastWindowClosed(false); //没有设置samba密码:1)设置当前用户samba密码 2)提示其他用户需要设置samba密码 SambaConfigInterface si(DBUS_NAME, DBUS_PATH, QDBusConnection::systemBus()); bool initRet = si.init(name, getpid(), getuid()); bool ret = false; if (initRet) { if (!si.hasPasswd() && username == name) { bool ok = false; QInputDialog dlg; dlg.setLabelText(tr("Samba password:")); dlg.setTextEchoMode(QLineEdit::Password); dlg.setWindowTitle(tr("Samba set user password")); dlg.setWindowIcon(QIcon::fromTheme("system-file-manager")); dlg.setFixedSize(470,150); ok = dlg.exec(); QString text = dlg.textValue(); if (ok && !text.isNull() && !text.isEmpty()) { if (!si.setPasswd(text)) { qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), tr("Samba set password failed, Please re-enter!")); ret = true; } } else { qApp->setQuitOnLastWindowClosed(!runbackend); ret = true; } } else if (!si.hasPasswd() && username != name) { QMessageBox::information(nullptr, tr("Tips"), tr("The user has not set the samba password. If you need to log in to %1, you can set it in the upper right menu of the file manager").arg(name)); } } else { qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), tr("Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share!"), QMessageBox::Ok); ret = true; } si.finished(); if (ret) { return; } //增加用户默认设置 m_mutex.lock(); m_userInfo.insert(name, "r"); m_mutex.unlock(); m_addShareUserBtn->setEnabled(false); int rowCount = m_tabWidget->rowCount(); m_tabWidget->insertRow(rowCount); QTableWidgetItem* itemC = new QTableWidgetItem(name); itemC->setFlags(itemC->flags() | Qt::ItemIsSelectable); itemC->setToolTip(name); m_tabWidget->setItem(rowCount, 0, itemC); QButtonGroup *group = new QButtonGroup; for (int i = 1; i < 4; ++i) { m_tabWidget->setCellWidget(rowCount, i, nullptr); QWidget *w = new QWidget(m_tabWidget); QHBoxLayout *l = new QHBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); w->setLayout(l); l->setAlignment(Qt::AlignCenter); auto checkbox = new QCheckBox(w); bool check = updateCheckBox(i, name); checkbox->setChecked(check); group->addButton(checkbox); l->addWidget(checkbox); m_tabWidget->setCellWidget(rowCount, i, w); } } }); connect(m_delShareUserBtn, &QPushButton::clicked, this, [=](){ QTableWidgetItem *item = m_tabWidget->currentItem(); if (nullptr != item) { QString name = item->text(); if (m_userInfo.contains(name)) { m_mutex.lock(); m_userInfo.remove(name); m_mutex.unlock(); int currentRow = m_tabWidget->currentRow(); m_tabWidget->removeRow(currentRow); if (m_tabWidget->rowCount() <= 1) { m_delShareUserBtn->setEnabled(false); } } } }); connect(m_saveBtn, &QPushButton::clicked, this, [=](){ for (int row = 0; row < m_tabWidget->rowCount(); ++row) { for (int col = 0; col < m_tabWidget->columnCount(); ++col) { if (col == 0) { continue; } QWidget *w = m_tabWidget->cellWidget(row, col); QCheckBox *box = w->findChild(); updateUserInfo(row, col, box->isChecked()); } } QString acl; QString rejectStr; QString otherStr; QMap::iterator iter; for(iter = m_userInfo.begin();iter != m_userInfo.end();iter++){ QString str = iter.key() + ":" + iter.value() + ","; if (iter.value().compare("d", Qt::CaseInsensitive) == 0) { rejectStr.append(str); } else { otherStr.append(str); } } acl.append(rejectStr); acl.append(otherStr); QString aclPerms = parseSetAcl(m_userInfo); auto info = FileInfo::fromUri(m_uri); //Clear previous acl permissions bool retb; bool retm; QStringList args; args << "/usr/bin/setfacl" << "-b" << QString("\"%1\"").arg(info->filePath()); QString result = UserShareInfoManager::exectueSetAclCommand(args, &retb); if (!retb && !result.isEmpty()) { return; } args.clear(); args << "/usr/bin/setfacl" << "-m" << aclPerms << QString("\"%1\"").arg(info->filePath()); result = UserShareInfoManager::exectueSetAclCommand(args, &retm); if (!retm && !result.isEmpty()) { return; } acl.chop(1); Q_EMIT getUserInfo(acl, m_userInfo); this->close(); }); connect(m_cancelBtn, &QPushButton::clicked, this, [=](){ m_userInfo.clear(); this->close(); }); connect(m_tabWidget, &QTableWidget::cellEntered, this, &AdvancedSharePage::updateDelBtnState); connect(m_tabWidget, &QTableWidget::cellClicked, this, &AdvancedSharePage::updateDelBtnState); connect(m_tabWidget, &QTableWidget::itemClicked, this, [=](QTableWidgetItem *item){ if (nullptr != item) { m_tabLabel->setText(item->text()); updateLabelShow(m_tabLabel, item->text()); } }); } void AdvancedSharePage::initTableWidget() { m_tabWidget = new QTableWidget(this); m_tabWidget->setColumnCount(4); m_tabWidget->verticalHeader()->setVisible(false); m_tabWidget->verticalHeader()->setMinimumSectionSize(12); m_tabWidget->horizontalHeader()->setFrameShape(QFrame::NoFrame); m_tabWidget->setFrameShape(QFrame::NoFrame); m_tabWidget->horizontalHeader()->setSelectionMode(QTableWidget::NoSelection); m_tabWidget->verticalHeader()->setSelectionMode(QTableWidget::NoSelection); m_tabWidget->setSelectionMode(QTableWidget::SingleSelection); m_tabWidget->setSelectionBehavior(QAbstractItemView::SelectItems); m_tabWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); m_tabWidget->setShowGrid(false); m_tabWidget->horizontalHeader()->setMinimumHeight(34); m_tabWidget->rowHeight(34); m_tabWidget->setAlternatingRowColors(true); auto l = QStringList(); l<setHorizontalHeaderLabels(l); m_tabWidget->setEditTriggers(QTableWidget::NoEditTriggers); m_tabWidget->horizontalHeader()->setMinimumSectionSize(30); m_tabWidget->horizontalHeader()->setMaximumSectionSize(400); m_tabWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive); m_tabWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); m_tabWidget->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); m_tabWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); //m_tabWidget->horizontalHeaderItem(0)->setTextAlignment(Qt::AlignLeft); m_tabWidget->setColumnWidth(0, 100); m_tabWidget->setColumnWidth(3, 80); m_layout->addWidget(m_tabWidget); if (m_userInfo.isEmpty()) { m_userInfo.insert(everyone, "r"); } int rowCount = m_userInfo.count(); m_tabWidget->setRowCount(rowCount); //Add Everyone QTableWidgetItem* itemR0C0 = new QTableWidgetItem(everyone); itemR0C0->setFlags(itemR0C0->flags() | Qt::ItemIsSelectable); itemR0C0->setToolTip(everyone); m_tabWidget->setItem(0, 0, itemR0C0); QButtonGroup *group = new QButtonGroup; for (int i = 1; i < 4; ++i) { m_tabWidget->setCellWidget(0, i, nullptr); QWidget *w = new QWidget(m_tabWidget); QHBoxLayout *l = new QHBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); w->setLayout(l); l->setAlignment(Qt::AlignCenter); auto checkbox = new QCheckBox(w); bool check = updateCheckBox(i, const_cast(everyone)); checkbox->setChecked(check); group->addButton(checkbox); l->addWidget(checkbox); m_tabWidget->setCellWidget(0, i, w); } //All usershare QMap::iterator iter; int row = 1; for(iter = m_userInfo.begin();iter != m_userInfo.end();iter++){ QString key = iter.key(); if (iter.key() == everyone) { continue; } QTableWidgetItem* itemC0 = new QTableWidgetItem(key); itemC0->setFlags(itemC0->flags() | Qt::ItemIsSelectable); itemC0->setToolTip(key); m_tabWidget->setItem(row, 0, itemC0); QButtonGroup *group = new QButtonGroup; for (int j = 1; j < 4; ++j) { m_tabWidget->setCellWidget(row, j, nullptr); QWidget *w = new QWidget(m_tabWidget); QHBoxLayout *l = new QHBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); w->setLayout(l); l->setAlignment(Qt::AlignCenter); auto checkbox = new QCheckBox(w); bool check = updateCheckBox(j, key); checkbox->setChecked(check); group->addButton(checkbox); l->addWidget(checkbox); m_tabWidget->setCellWidget(row, j, w); } ++row; } } void AdvancedSharePage::initListWidget() { QVBoxLayout *vBoxLayout = new QVBoxLayout; vBoxLayout->setContentsMargins(22, 0, 22, 0); m_listWidget = new QListWidget(this); m_listWidget->setUniformItemSizes(true); m_listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_sysAccounts = new SystemDbusAccounts(this); QStringList m_userNames = m_sysAccounts->getAllUserNames(); if (!m_userNames.isEmpty()) { for (QString user : m_userNames) { auto item = new QListWidgetItem(user, m_listWidget); m_listWidget->addItem(item); } } vBoxLayout->addWidget(m_listWidget); m_layout->addLayout(vBoxLayout); } void AdvancedSharePage::initFloorOne() { m_label = new QLabel(tr("Share permission settings"), this); m_label->setContentsMargins(22, 0, 22, 0); m_layout->addWidget(m_label); } void AdvancedSharePage::initFloorTwo() { this->initTableWidget(); m_layout->addSpacing(10); QHBoxLayout *hBoxLayout = new QHBoxLayout; hBoxLayout->setContentsMargins(22, 0, 22, 0); hBoxLayout->setSpacing(0); m_delShareUserBtn = new QPushButton(tr("delete")); m_tabLabel = new QLabel; m_delShareUserBtn->setEnabled(false); hBoxLayout->addWidget(m_tabLabel, 3); hBoxLayout->addSpacing(10); hBoxLayout->addWidget(m_delShareUserBtn, 1); m_layout->addLayout(hBoxLayout); m_layout->addSpacing(10); } void AdvancedSharePage::initFloorThree() { m_layout->addSpacing(10); this->initListWidget(); QHBoxLayout *hBoxLayout = new QHBoxLayout; hBoxLayout->setContentsMargins(22, 0, 22, 0); hBoxLayout->setSpacing(0); m_addShareUserBtn = new QPushButton(tr("Add")); m_listLabel = new QLabel; m_addShareUserBtn->setEnabled(false); hBoxLayout->addWidget(m_listLabel, 3); hBoxLayout->addSpacing(10); hBoxLayout->addWidget(m_addShareUserBtn, 1); m_layout->addSpacing(10); m_layout->addLayout(hBoxLayout); m_layout->addSpacing(10); } void AdvancedSharePage::initFloorFour() { m_layout->addSpacing(10); QHBoxLayout *hBoxLayout2 = new QHBoxLayout; hBoxLayout2->setContentsMargins(22, 0, 22, 0); hBoxLayout2->setSpacing(0); m_saveBtn = new QPushButton(tr("Save")); m_cancelBtn = new QPushButton(tr("Cancel")); hBoxLayout2->addStretch(1); hBoxLayout2->addWidget(m_saveBtn); hBoxLayout2->addSpacing(10); hBoxLayout2->addWidget(m_cancelBtn); m_layout->addLayout(hBoxLayout2); m_layout->addSpacing(16); } void AdvancedSharePage::updateShareUserInfo() { if (m_userInfo.isEmpty()) { auto info = FileInfo::fromUri(m_uri); auto manager = UserShareInfoManager::getInstance(); QString displayName = info->displayName(); QString acl = manager->getUserShareAcl(displayName); parseUserShareAcl(acl); } } void AdvancedSharePage::parseUserShareAcl(QString strAcl) { //传参格式为:XWJ-PC\\xwj:D,Everyone:F,S-1-5-21-120585688-1972774468-4199965856-1003:F,Unix User\\samba:D, //或者为:xwj:D,Everyone:F,kylin:F,samba:D, if (!strAcl.isEmpty()) { QStringList strAcls = strAcl.split(','); if (strAcls.last().isEmpty()) { strAcls.removeLast(); } for (QString acl : strAcls) { QStringList users = acl.split(':'); QString name = users.at(0); QString usershareAcl = users.at(1); if (name.contains("\\")) { //处理用户为:Unix User\\samba:D的情况 int index = name.indexOf("\\"); name.remove(0, index + 1); } //统一处理用户 m_mutex.lock(); m_userInfo.insert(name, usershareAcl); m_mutex.unlock(); } } } bool AdvancedSharePage::updateCheckBox(int col, QString &name) { bool ret = false; if (name.isEmpty() || col < 0) { return ret; } if (col == 1 && m_userInfo.value(name).compare("f", Qt::CaseInsensitive) == 0) { ret = true; } else if (col == 2 && m_userInfo.value(name).compare("r", Qt::CaseInsensitive) == 0) { ret = true; } else if (col == 3 && m_userInfo.value(name).compare("d", Qt::CaseInsensitive) == 0) { ret = true; } return ret; } void AdvancedSharePage::updateUserInfo(int row, int col, bool checked) { if (checked && col >= 1) { QTableWidgetItem *item = m_tabWidget->item(row, 0); if (nullptr != item && m_userInfo.contains(item->text())) { if (col == 1) { m_userInfo.remove(item->text()); m_userInfo.insert(item->text(), "f"); } else if (col == 2) { m_userInfo.remove(item->text()); m_userInfo.insert(item->text(), "r"); } else if (col == 3) { m_userInfo.remove(item->text()); m_userInfo.insert(item->text(), "d"); } } } } QString AdvancedSharePage::parseSetAcl(QMap &userInfo) { QString aclPerms; QMap::iterator iter; for(iter = userInfo.begin();iter != userInfo.end();iter++){ QString tmp; QString tmp2; QString perm = converPermission(iter.value()); if (iter.key() == everyone) { tmp = "d:u:nobody:" + perm; tmp2 = "u:nobody:" + perm; } else { tmp = "d:u:" + iter.key() + ":" + perm; tmp2 = "u:" + iter.key() + ":" + perm; } aclPerms.append(tmp + ","); aclPerms.append(tmp2 + ","); } return aclPerms; } QString AdvancedSharePage::converPermission(QString &usershareAcl) { QString acls; if (!usershareAcl.isEmpty()) { if (usershareAcl.compare("f", Qt::CaseInsensitive) == 0) { acls = "rwx"; } else if (usershareAcl.compare("r", Qt::CaseInsensitive) == 0) { acls = "r-x"; } else if (usershareAcl.compare("d", Qt::CaseInsensitive) == 0) { acls = "--x"; } } return acls; } void AdvancedSharePage::updateLabelShow(QLabel *label, const QString &str) { int fontSize = label->fontMetrics().horizontalAdvance(str); QString tmp = str; if(fontSize > labelWidth) { label->setToolTip(str); tmp = label->fontMetrics().elidedText(str, Qt::ElideMiddle, labelWidth); } label->setText(tmp); } void AdvancedSharePage::addSeparate() { QPushButton *separate = new QPushButton; separate->setFixedHeight(1); separate->setFocusPolicy(Qt::NoFocus); separate->setEnabled(false); m_layout->addWidget(separate); } void AdvancedSharePage::updateDelBtnState(int row, int col) { if (col >= 1) { m_delShareUserBtn->setEnabled(false); } else if (col == 0 && row != 0) { m_delShareUserBtn->setEnabled(true); } else if (col == 0 && row == 0) { m_delShareUserBtn->setEnabled(false); } if (col >= 1) { m_tabLabel->setText(""); } } peony-extensions/peony-share/app/share-page.cpp0000664000175000017500000006044115156143275020577 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "share-page.h" #include "share-properties-page-plugin.h" #include "emblem-provider.h" #include "share-emblem-provider.h" #include "samba-config-interface.h" #include "samba-config-thread.h" #include "global-settings.h" #include "advanced-share-page.h" #include "pwd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NAME_LABEL_WIDTH 350 SharePage::SharePage(const QString &uri, QWidget *parent) : PropertiesWindowTabIface(parent) { qDebug() << "=========="<< __func__ < initFileInfoJob = QtConcurrent::run([=]() { m_fileInfo = Peony::FileInfo::fromUri(uri); Peony::FileInfoJob *fileInfoJob = new Peony::FileInfoJob(m_fileInfo); fileInfoJob->setAutoDelete(true); fileInfoJob->querySync(); m_name = m_fileInfo->displayName(); m_stmp = UserShareInfoManager::getInstance()->getShareInfo(m_name); }); m_theFutureWatcher = new QFutureWatcher(); m_theFutureWatcher->setFuture(initFileInfoJob); connect(m_theFutureWatcher, &QFutureWatcher::finished, this, &SharePage::init); } void SharePage::init() { if (m_theFutureWatcher) delete m_theFutureWatcher; m_shareInfo.name = m_fileInfo->displayName(); m_shareInfo.originalPath = m_fileInfo->filePath(); if (nullptr != m_stmp) { if (m_stmp->originalPath.compare(m_shareInfo.originalPath) == 0) { m_shareInfo = m_stmp; m_shareInfo.isShared = true; } else { m_originalPath = m_stmp->originalPath; } } this->setContentsMargins(0, 0, 0, 0); m_layout = new QVBoxLayout(this); m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); this->initFloorOne(); this->addSeparate(); this->initFloorTwo(); this->initFloorThree(); m_layout->addStretch(1); } void SharePage::saveAllChange() { if (!m_thisPageChanged) return; bool checked = m_switchButton->isChecked(); if (checked) { //Check specail symbols bool isSpecial = checkSpecialSymbols(m_fileInfo->displayName()); if (isSpecial) { QMessageBox::warning(nullptr, tr("Warning"), tr("The share name must not contain %1, and cannot start with a dash (-) or whitespace, or end with whitespace.").arg("%<>*?|/\\+=;:,\""), QMessageBox::Ok); return; } //Check username and share name if (getpwnam(m_fileInfo->displayName().toLower().toUtf8().constData())) { QMessageBox::warning(nullptr, tr("Warning"), tr("The share name cannot be the same as an existing user name"), QMessageBox::Ok); return; } QDir userDir("/var/lib/samba/usershares"); QFileInfoList fileInfoList = userDir.entryInfoList(QDir::Files); for (auto fileInfo : fileInfoList) { if (m_fileInfo->displayName().toLower() == fileInfo.fileName()) { QFileInfo shareInfo(m_fileInfo->filePath()); if (fileInfo.owner() != shareInfo.owner()) { QMessageBox::question(nullptr, tr("question"), tr("The share name is already used by other users. You can rename the folder and then set the share."), QMessageBox::Ok); return; } else { if (!m_originalPath.isEmpty()) { int ret = QMessageBox::question(nullptr, tr("question"), tr("The share name already exists, do you want to replace the original share folder?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (QMessageBox::Yes == ret) { //取消之前的共享文件夹信息跟角标信息 QString tmpUri = "file://" + m_originalPath; removeShareEmblem(tmpUri); removeShareInfo(m_shareInfo.name); clearAclInfo(m_originalPath); } else { return; } } } } } auto settings = GlobalSettings::getInstance(); bool runbackend = settings->getInstance()->getValue(RESIDENT_IN_BACKEND).toBool(); qApp->setQuitOnLastWindowClosed(false); //FIXME:暂时不支持自定义共享名 - Currently does not support custom shared name m_shareInfo.name = m_fileInfo->displayName(); m_shareInfo.readOnly = !m_allowGuestModify->isChecked(); m_shareInfo.allowGuest = m_shareAllowGuestCheckBox->isChecked(); m_shareInfo.isShared = true; QString uri = m_fileInfo->uri(); if (!m_shareInfo.readOnly || m_usershareAcl.compare("Everyone:F", Qt::CaseInsensitive) == 0) { QMessageBox messageBox(this); messageBox.setText(tr("The folder is currently shared in non-read-only mode, and setting up the share requires adding other people's write permissions to the current folder?")); messageBox.setWindowTitle(tr("question")); messageBox.setIcon(QMessageBox::Question); QPushButton *addModeButton = messageBox.addButton(tr("Confirm adding permissions"), QMessageBox::YesRole); messageBox.addButton(tr("Cancel"), QMessageBox::NoRole); messageBox.exec(); if (messageBox.clickedButton() == addModeButton) { if (!addOtherWrite(uri, runbackend)) { return; } } else { return; } } if (m_shareInfo.allowGuest) { QMessageBox mBox(this); mBox.setText(tr("The folder is currently shared with anonymous access set, do I need to automatically increase the executable permissions for the current file and all others in the parent directory?")); mBox.setWindowTitle(tr("question")); mBox.setIcon(QMessageBox::Question); QPushButton *addModeBtn = mBox.addButton(tr("Confirm adding permissions"), QMessageBox::YesRole); mBox.addButton(tr("Cancel"), QMessageBox::NoRole); mBox.exec(); if (mBox.clickedButton() == addModeBtn) { this->recursiveAddOtherExecute(uri, runbackend); } else { return; } } //防止祖宗目录没有其他可执行权限 if (!m_usershareAcl.isEmpty()) { this->recursiveAddOtherExecute(uri, runbackend); } //handle: // SambaConfigInterface si(DBUS_NAME, DBUS_PATH, QDBusConnection::systemBus()); // if (si.init(g_get_user_name(), getpid(), getuid())) { // if (!si.hasPasswd()) { // bool ok = false; // QString text = QInputDialog::getText(nullptr, tr("Set Password"), tr("Password:"), QLineEdit::Normal, "", &ok); // if (ok && !text.isNull() && !text.isEmpty()) { // if (!si.setPasswd(text)) { // QMessageBox::warning(nullptr, tr("Warning"), tr("Samba set password failed, Please re-enter!")); // m_ret = true; // } // } // } // } else { // QMessageBox::warning(nullptr, tr("Warning"), tr("Samba service init failed!"), QMessageBox::Ok); // m_ret = true; // } // si.finished(); // if (m_ret) { // return; // } // UserShareInfoManager::getInstance()->updateShareInfo(m_shareInfo); if (!this->checkAuthorization()) return; qDebug()<< __func__ << __LINE__ << m_shareInfo.name << m_shareInfo.isShared << m_shareInfo.originalPath << m_shareInfo.readOnly; SambaConfigThread* thread = new SambaConfigThread(m_shareInfo, m_fileInfo->uri(), m_usershareAcl); connect(thread, &SambaConfigThread::isInitSignal, UserShareInfoManager::getInstance(), [=](bool initRet){ qDebug()<< __func__ << __LINE__ << initRet; if (!initRet) { qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), tr("Shared configuration service exception, please confirm if there is an ongoing shared configuration operation, or please reset the share!"), QMessageBox::Ok); return; } }, Qt::BlockingQueuedConnection); connect(thread, &SambaConfigThread::isHasPasswdSignal, UserShareInfoManager::getInstance(), [=](bool hasPasswdRet, QString &passwd){ qDebug()<< __func__ << __LINE__ << hasPasswdRet; if (!hasPasswdRet) { bool ok = false; QInputDialog dlg; dlg.setLabelText(tr("Samba password:")); dlg.setTextEchoMode(QLineEdit::Password); dlg.setWindowTitle(tr("Samba set user password")); dlg.setWindowIcon(QIcon::fromTheme("system-file-manager")); dlg.setFixedSize(470,150); ok = dlg.exec(); QString text = dlg.textValue(); if (ok && !text.isNull() && !text.isEmpty()) { passwd = text; } else { qApp->setQuitOnLastWindowClosed(!runbackend); } } else { if (addShareInfo(*(thread->getShareInfo()), thread->getAcl())) { addShareEmblem(thread->getUri()); qApp->setQuitOnLastWindowClosed(!runbackend); } } if (qApp->quitOnLastWindowClosed()) { delayExit(); } }, Qt::BlockingQueuedConnection); connect(thread, &SambaConfigThread::isSetPasswdSignal, UserShareInfoManager::getInstance(), [=](bool setPasswdRet){ qDebug()<< __func__ << __LINE__ << setPasswdRet; if (!setPasswdRet) { qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), tr("Samba set password failed, Please re-enter!")); return; } else { if (UserShareInfoManager::getInstance()->updateShareInfo(*(thread->getShareInfo()), thread->getAcl())) { addShareEmblem(thread->getUri()); qApp->setQuitOnLastWindowClosed(!runbackend); } } if (qApp->quitOnLastWindowClosed()) { delayExit(); } }, Qt::BlockingQueuedConnection); connect(thread, &SambaConfigThread::finished, thread, &SambaConfigThread::deleteLater); thread->start(); } else { removeShareEmblem(m_fileInfo->uri()); //FIXME:需要先判断存在共享吗 - Need to determine whether there is sharing first removeShareInfo(m_shareInfo.name); clearAclInfo(m_fileInfo->filePath()); } } bool SharePage::checkAuthorization() { bool ret = false; g_autoptr(GError) error = NULL; PolkitAuthority* mAuth = polkit_authority_get_sync(NULL, &error); if (error) { qWarning() << error->message; return ret; } int pid = getpid(); int uid = getuid(); PolkitSubject* proj = polkit_unix_process_new_for_owner (pid, 0, uid); PolkitAuthorizationResult* res = polkit_authority_check_authorization_sync (mAuth, proj, "org.ukui.samba.share.config.authorization", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, nullptr, &error); if (error) { qWarning() << error->message; goto out; } if (polkit_authorization_result_get_is_authorized(res)) { ret = true; } out: if (proj) g_object_unref (proj); if (res) g_object_unref (res); return ret; } bool SharePage::checkSpecialSymbols(const QString &displayName) { if (displayName.startsWith(" ") || displayName.startsWith("-") || displayName.endsWith(" ")) { return true; } QRegularExpression gSpecialSymbols("[\\\\/:\\*\\?\\<>\\|\\+\\=\\,\\;\\%]"); if (displayName.contains(gSpecialSymbols)) { return true; } return false; } void SharePage::delayExit() { QTimer::singleShot(1000, [](){ qDebug() << __LINE__ << __func__; bool allWindowsClosed = true; QList topLevelWidget = QApplication::topLevelWidgets(); for (auto w : topLevelWidget) { if (w->isVisible()) { allWindowsClosed = false; } } if (allWindowsClosed) { qApp->quit(); } }); } void SharePage::recursiveAddOtherExecute(const QString &uri, const bool &runbackend) { QString tmpUri = uri; while (!tmpUri.isEmpty() && tmpUri != "") { g_autoptr(GFile) file = g_file_new_for_uri(tmpUri.toUtf8().constData()); if (file) { g_autoptr(GError) error = NULL; g_autoptr(GFileInfo) info = g_file_query_info(file, "unix::mode", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, &error); if (info) { guint32 mode = 0; m_has_unix_mode = g_file_info_has_attribute(info, G_FILE_ATTRIBUTE_UNIX_MODE); if (m_has_unix_mode) { mode = g_file_info_get_attribute_uint32(info, G_FILE_ATTRIBUTE_UNIX_MODE); auto other_executable = mode & S_IXOTH; if (!other_executable) { mode |= S_IXOTH; g_autoptr(GError) err = NULL; g_file_set_attribute_uint32(file, G_FILE_ATTRIBUTE_UNIX_MODE, (guint32)mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, &err); if (err) { qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), err->message, QMessageBox::Ok); return; } } tmpUri = FileUtils::getParentUri(tmpUri); } else { return; } } else if (error) { qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), error->message, QMessageBox::Ok); return; } } else { return; } } } bool SharePage::addOtherWrite(const QString &uri, const bool &runbackend) { bool ret = false; g_autoptr(GFile) gFile = g_file_new_for_uri(uri.toUtf8().constData()); if (gFile) { g_autoptr(GError) gError = NULL; g_autoptr(GFileInfo) gInfo = g_file_query_info(gFile, "unix::mode", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, &gError); if (gInfo) { guint32 mode = 0; bool has_unix_mode = g_file_info_has_attribute(gInfo, G_FILE_ATTRIBUTE_UNIX_MODE); if (has_unix_mode) { mode = g_file_info_get_attribute_uint32(gInfo, G_FILE_ATTRIBUTE_UNIX_MODE); auto other_writeable = mode & S_IWOTH; if (!other_writeable) { mode |= S_IWOTH; g_autoptr(GError) gErr = NULL; g_file_set_attribute_uint32(gFile, G_FILE_ATTRIBUTE_UNIX_MODE, (guint32)mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, &gErr); if (gErr) { qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), gErr->message, QMessageBox::Ok); return ret; } ret = true; } ret = true; return ret; } else { return ret; } } else if (gError){ qApp->setQuitOnLastWindowClosed(!runbackend); QMessageBox::warning(nullptr, tr("Warning"), gError->message, QMessageBox::Ok); return ret; } } return ret; } void SharePage::removeShareEmblem(const QString &uri) { auto info = Peony::FileXattrInfo::fromUri(uri); if (info && !info->getXattrInfoString(SHARE_EMBLEMS).isEmpty()) { info->removeXattrInfo(SHARE_EMBLEMS); EmblemProviderManager::getInstance()->queryAsync(uri); } } void SharePage::removeShareInfo(const QString &name) { UserShareInfoManager::getInstance()->removeShareInfoAcl(const_cast(name)); } void SharePage::clearAclInfo(const QString &filePath) { bool ret; QStringList args; args << "/usr/bin/setfacl" << "-b" << QString("\"%1\"").arg(filePath); UserShareInfoManager::exectueSetAclCommand(args, &ret); } bool SharePage::addShareInfo(ShareInfo &shareInfo, const QString usershareAcl) { return UserShareInfoManager::getInstance()->updateShareInfo(shareInfo, usershareAcl); } void SharePage::addShareEmblem(const QString &uri) { auto fileInfo = Peony::FileXattrInfo::fromUri(uri); if(fileInfo){ fileInfo->setXattrInfoString(SHARE_EMBLEMS,"emblem-shared", true); EmblemProviderManager::getInstance()->queryAsync(uri); } } void SharePage::initFloorOne() { QFrame * floor1 = new QFrame(this); floor1->setContentsMargins(0, 0, 0, 0); floor1->setMinimumHeight(90); QHBoxLayout * layout1 = new QHBoxLayout(floor1); layout1->setContentsMargins(24, 24, 24, 16); layout1->setSpacing(16); layout1->setAlignment(Qt::AlignTop | Qt::AlignLeft); floor1->setLayout(layout1); m_iconLabel = new QLabel(floor1); QIcon icon = QIcon::fromTheme(m_fileInfo.get()->iconName()); m_iconLabel->setFixedSize(QSize(64, 64)); m_iconLabel->setPixmap(icon.pixmap(QSize(64, 64))); layout1->addWidget(m_iconLabel); QVBoxLayout * vBoxLayout = new QVBoxLayout(floor1); m_folderName = new QLabel(floor1); m_sharedState = new QLabel(floor1); //fix bug:#82320 QRegularExpression regex("^file:///data/usershare(/{,1})$"); if (regex.match(m_fileInfo->uri()).hasMatch()) { m_folderName->setText(tr("usershare")); } else { QFontMetrics fontWidth(m_folderName->font()); QString elideNote = fontWidth.elidedText(m_fileInfo.get()->displayName(), Qt::ElideMiddle, NAME_LABEL_WIDTH); m_folderName->setText(elideNote); m_folderName->setToolTip(m_fileInfo.get()->displayName()); } m_sharedState->setText(m_shareInfo.isShared ? tr("share this folder") : tr("don`t share this folder")); vBoxLayout->addStretch(1); vBoxLayout->addWidget(m_folderName); vBoxLayout->addWidget(m_sharedState); vBoxLayout->addStretch(1); layout1->addLayout(vBoxLayout); layout1->addStretch(1); m_layout->addWidget(floor1); } void SharePage::initFloorTwo() { QFrame * floor2 = new QFrame(this); QHBoxLayout *layout2 = new QHBoxLayout(floor2); layout2->setAlignment(Qt::AlignLeft); layout2->setContentsMargins(24, 16, 24, 0); layout2->setSpacing(16); floor2->setLayout(layout2); bool shared = m_shareInfo.isShared; m_switchButton = new kdk::KSwitchButton(floor2); m_switchButton->setChecked(shared); layout2->addWidget(new QLabel(tr("Share folder"), floor2)); layout2->addWidget(m_switchButton); m_layout->addWidget(floor2); connect(m_switchButton, &kdk::KSwitchButton::stateChanged, this, [=](bool checked) { this->thisPageChanged(); if (checked) { //FIXME:暂时不支持自定义共享名 - Currently does not support custom shared name m_shareInfo.name = m_fileInfo->displayName(); m_shareInfo.readOnly = !m_allowGuestModify->isChecked(); m_shareInfo.allowGuest = m_shareAllowGuestCheckBox->isChecked(); if (m_floor3) m_floor3->setVisible(true); m_sharedState->setText(tr("share this folder")); m_shareInfo.isShared = true; } else { if (m_floor3) m_floor3->setVisible(false); m_sharedState->setText(tr("don`t share this folder")); m_shareInfo.isShared = false; } }); } void SharePage::initFloorThree() { m_floor3 = new QFrame(this); QVBoxLayout * layout3 = new QVBoxLayout(m_floor3); layout3->setContentsMargins(24, 24, 24, 0); layout3->setSpacing(0); m_floor3->setLayout(layout3); QFormLayout *formLayout = new QFormLayout(m_floor3); formLayout->setVerticalSpacing(24); m_shareNameEdit = new QLineEdit(m_floor3); m_shareNameEdit->setMaxLength(32); m_shareNameEdit->setEnabled(false); m_shareNameEdit->setText(m_shareInfo.name); m_commentEdit = new QLineEdit(m_floor3); m_commentEdit->setMaxLength(64); m_commentEdit->setText(m_shareInfo.comment); formLayout->addRow(tr("Share name:"), m_shareNameEdit); formLayout->addRow(tr("Comment:"), m_commentEdit); layout3->addLayout(formLayout); m_allowGuestModify = new QCheckBox(tr("Allow others to create and delete files"), this); m_allowGuestModify->setChecked(!m_shareInfo.readOnly); m_shareAllowGuestCheckBox = new QCheckBox(tr("Allow Anonymous")); m_shareAllowGuestCheckBox->setChecked(m_shareInfo.allowGuest); layout3->addSpacing(24); layout3->addWidget(m_allowGuestModify); layout3->addWidget(m_shareAllowGuestCheckBox); QHBoxLayout *hBoxLayout = new QHBoxLayout(m_floor3); m_advanceShareButton = new QPushButton(tr("Advanced Sharing"), this); hBoxLayout->addWidget(m_advanceShareButton); hBoxLayout->addStretch(2); layout3->addLayout(hBoxLayout); m_floor3->setVisible(m_shareInfo.isShared); m_layout->addWidget(m_floor3); connect(m_commentEdit, &QLineEdit::textChanged, this, [=]() { m_shareInfo.comment = m_commentEdit->text(); this->thisPageChanged(); }); connect(m_allowGuestModify, &QCheckBox::clicked, this, [=](bool checked) { m_shareInfo.readOnly = checked; this->thisPageChanged(); }); connect(m_shareAllowGuestCheckBox, &QCheckBox::clicked, this, [=](bool checked) { m_shareInfo.allowGuest = checked; this->thisPageChanged(); }); connect(m_advanceShareButton, &QPushButton::clicked, this, [=](){ if(checkAuthorization()) { this->thisPageChanged(); AdvancedSharePage *page = new AdvancedSharePage(m_fileInfo->uri(), m_userInfoCache); connect(page, &AdvancedSharePage::getUserInfo, this, [=](QString acl, QMap& userInfo){ m_usershareAcl = acl; m_userInfoCache = userInfo; }); page->show(); } }); } peony-extensions/peony-share/app/share-properties-page-plugin.h0000664000175000017500000000410215156143137023717 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef SHAREMENUPLUGIN_H #define SHAREMENUPLUGIN_H #include #include #include "usershare-manager.h" namespace Peony { class SharePropertiesPagePlugin : public QObject, public PropertiesWindowTabPagePluginIface { Q_OBJECT Q_PLUGIN_METADATA(IID PropertiesWindowTabPagePluginIface_iid FILE "common.json") Q_INTERFACES(Peony::PropertiesWindowTabPagePluginIface) public: explicit SharePropertiesPagePlugin(QObject *parent = nullptr); static SharePropertiesPagePlugin *getInstance(); PluginInterface::PluginType pluginType() override {return PluginInterface::PropertiesWindowPlugin;} const QString name() override {return tr("Peony Qt Share Extension");} const QString description() override {return tr("Allow user share folders");} const QIcon icon() override {return QIcon::fromTheme("emblem-shared");} void setEnable(bool enable) override {m_enable = enable;} bool isEnable() override {return m_enable;} int tabOrder() override {return 99;} bool supportUris(const QStringList &uris) override; PropertiesWindowTabIface * createTabPage(const QStringList &uris) override; void closeFactory() {deleteLater();} private: bool m_enable; }; } #endif // SHAREMENUPLUGIN_H peony-extensions/peony-share/app/share-properties-page-plugin.cpp0000664000175000017500000000721015156143275024260 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * Authors: Meihong * */ #include "share-properties-page-plugin.h" #include "share-page.h" #include "emblem-provider.h" #include "share-emblem-provider.h" #include #include #include #include #include #include #include using namespace Peony; SharePropertiesPagePlugin *global_instance = nullptr; SharePropertiesPagePlugin *SharePropertiesPagePlugin::getInstance() { return global_instance; } SharePropertiesPagePlugin::SharePropertiesPagePlugin(QObject *parent) : QObject(parent) { qDebug()<<"init"; QTranslator *t = new QTranslator(this); qDebug()<<"\n\n\n\n\n\n\n SharePropertiesPagePlugin translate:"<load(":/translations/peony-share-extension_"+QLocale::system().name()); QFile file(":/translations/peony-share-extension_"+QLocale::system().name()+".ts"); qDebug()<<"file:"<registerProvider(ShareEmblemProvider::getInstance()); global_instance = this; } bool SharePropertiesPagePlugin::supportUris(const QStringList &uris) { if (uris.count() != 1) { return false; } //fix kylin security tool box-manager path can be shared issue if (uris.first().startsWith("file:///box")) { return false; } auto info = FileInfo::fromUri(uris.first()); if (info->displayName().isNull()) { FileInfoJob j(info); j.querySync(); } if (!info->isDir() || info->isVirtual() || !info->uri().startsWith("file:///")) { return false; } // don't share user's home directory file:///home/xxx/ QStringList file = info->uri().split('/', Qt::SkipEmptyParts); if ((3 == file.size ()) && (file.at(1) == "home")) { return false; } // don't share directory that has no permission if (!info->canRead() || !info->canWrite() || !info->canExecute()) { return false; } g_autoptr(GFile) gFile = g_file_new_for_uri(info->uri().toUtf8().constData()); if(gFile) { g_autoptr(GFileInfo) gInfo = g_file_query_info(gFile, "owner::*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, nullptr); if (gInfo) { QString ownerUser = g_file_info_get_attribute_string(gInfo, G_FILE_ATTRIBUTE_OWNER_USER); QString userName = qgetenv("USER"); if (0 != QString::compare(ownerUser, userName)) { return false; } } } return true; } PropertiesWindowTabIface * SharePropertiesPagePlugin::createTabPage(const QStringList &uris) { return new SharePage(uris.first()); } peony-extensions/peony-share/test/0000775000175000017500000000000015156143137016246 5ustar fengfengpeony-extensions/peony-share/test/test-auth.cpp0000664000175000017500000000264515156143137020677 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #include #include #include #include #include "../app/samba-config-interface.h" #include // 用d-feet测试,这个例子还有点问题 int main (int argc, char* argv[]) { Q_UNUSED(argc) Q_UNUSED(argv) QApplication app(argc, argv); SambaConfigInterface si(DBUS_NAME, DBUS_PATH, QDBusConnection::systemBus()); qDebug() << g_get_user_name() << " " << getpid() << " " << getuid(); sleep(1); qDebug() << si.init(g_get_user_name(), getpid(), getuid()); sleep(1); qDebug() << si.hasPasswd(); sleep(1); qDebug() << si.setPasswd("qwer1234"); si.finished(); return app.exec(); } peony-extensions/peony-share/test/test.pro0000664000175000017500000000056415156143137017754 0ustar fengfengTARGET = test-auth TEMPLATE = app QT += core dbus widgets PKGCONFIG += gio-2.0 polkit-gobject-1 CONFIG += link_pkgconfig c++11 no_keywords DEFINES += DBUS_NAME=\\\"org.ukui.samba.share.config\\\" DEFINES += DBUS_PATH=\\\"/org/ukui/samba/share\\\" SOURCES += $$PWD/../app/samba-config-interface.cpp $$PWD/test-auth.cpp HEADERS += $$PWD/../app/samba-config-interface.h peony-extensions/peony-share/data/0000775000175000017500000000000015156143275016203 5ustar fengfengpeony-extensions/peony-share/data/org.ukui.samba.share.config.conf0000664000175000017500000000171515156143137024245 0ustar fengfeng peony-extensions/peony-share/data/org.ukui.samba.share.config.service0000664000175000017500000000014115156143275024753 0ustar fengfeng[D-BUS Service] Name=org.ukui.samba.share.config User=root Exec=/usr/libexec/peony-samba-service peony-extensions/peony-share/data/org.ukui.samba.share.config.policy0000664000175000017500000000230515156143275024616 0ustar fengfeng Apply for the samba configuration permission Confirm whether you want to modify the Samba configuration 请确认是否允许修改samba配置 請確認是否允許修改samba配置 sambaཡི་སྒྲིག་གཞི་བསྒྱུར་ན་ཆོག་མིན་ཐག་གཅོད་བྱེད་རོགས། samba ᠲᠣᠬᠢᠷᠠᠭᠤᠯᠤᠯᠲᠠ ᠶ᠋ᠢ ᠵᠠᠰᠠᠬᠤ ᠶ᠋ᠢ ᠵᠥᠪᠱᠢᠶᠡᠷᠡᠬᠦ ᠡᠰᠡᠬᠦ ᠪᠡᠨ ᠲᠣᠳᠣᠷᠬᠠᠢᠯᠠᠭᠠᠷᠠᠢ no auth_self_keep peony-extensions/peony-drive-rename/0000775000175000017500000000000015156143275016546 5ustar fengfengpeony-extensions/peony-drive-rename/peony-drive-rename.pro0000664000175000017500000000263115156143275023000 0ustar fengfengQT += core gui widgets concurrent include(../common.pri) TARGET = peony-drive-rename TEMPLATE = lib DEFINES += PEONYADMIN_LIBRARY PKGCONFIG += peony gio-2.0 udisks2 glib-2.0 CONFIG += link_pkgconfig no_keywords c++11 plugin debug SOURCES += \ drive-rename.cpp \ driverenameplugin.cpp HEADERS += \ drive-rename.h \ driverenameplugin.h TRANSLATIONS += translations/peony-drive-rename_zh_CN.ts \ translations/peony-drive-rename_de.ts \ translations/peony-drive-rename_es.ts \ translations/peony-drive-rename_fr.ts \ translations/peony-drive-rename_kk_KZ.ts \ translations/peony-drive-rename_ug_CN.ts \ translations/peony-drive-rename_ky_KG.ts \ translations/peony-drive-rename_tr.ts \ translations/peony-drive-rename_cs.ts \ translations/peony-drive-rename_bo_CN.ts \ translations/peony-drive-rename_mn.ts \ translations/peony-drive-rename_zh_HK.ts #RESOURCES += \ # resource.qrc CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = /translations/ target.path = $$[QT_INSTALL_LIBS]/peony-extensions INSTALLS += target SKIP_TEST = $$(EXTENSIONS_SKIP_TEST) isEmpty(SKIP_TEST) { message("build with tests") QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage LIBS += -lgcov } peony-extensions/peony-drive-rename/translations/0000775000175000017500000000000015156143275021267 5ustar fengfengpeony-extensions/peony-drive-rename/translations/peony-drive-rename_cs.ts0000664000175000017500000000433515156143275026037 0ustar fengfeng Peony::DriveRename Rename Device name: Warning Renaming cannot start with a decimal point, Please re-enter! The device name exceeds the character limit, rename failed! Renaming will unmount the device. Do you want to continue? Peony::DriveRenamePlugin Drive rename QObject Warning The device may not support the rename operation, rename failed! peony-extensions/peony-drive-rename/translations/peony-drive-rename_tr.ts0000664000175000017500000000433515156143275026057 0ustar fengfeng Peony::DriveRename Rename Device name: Warning Renaming cannot start with a decimal point, Please re-enter! The device name exceeds the character limit, rename failed! Renaming will unmount the device. Do you want to continue? Peony::DriveRenamePlugin Drive rename QObject Warning The device may not support the rename operation, rename failed! peony-extensions/peony-drive-rename/translations/peony-drive-rename_bo_CN.ts0000664000175000017500000000757615156143275026424 0ustar fengfeng Peony::DriveRename Rename མིང་བསྒྱུར་བ། Device name: སྒྲིག་ཆས་ཀྱི་མིང་གཤམ་གསལ། Warning ཐ་ཚིག་སྒྲོག་པ། Renaming cannot start with a decimal point, Please re-enter! མིང་བསྒྱུར་བ་ནི་ཆེས་ཆུང་བ་ནས་འགོ་རྩོམ་མི་ཐུབ་པས། ཡང་བསྐྱར་ནང་དུ་ཞུགས་རོགས། The device name exceeds the character limit, rename failed! སྒྲིག་ཆས་ཀྱི་མིང་དེ་ཡི་གེའི་ཚད་ལས་བརྒལ་ནས་མིང་བསྒྱུར་མ་ཐུབ་པ་རེད། Renaming will unmount the device. Do you want to continue? མིང་བསྒྱུར་ན་སྒྲིག་ཆས་འདི་མེད་པར་བཟོ་སྲིད། ཁྱོད་ཀྱིས་ད་དུང་མུ་མཐུད་དུ་རྒྱུན་འཁྱོངས་བྱེད་དགོས་སམ། The device may not support the rename operation, rename failed! སྒྲིག་ཆས་འདིས་མིང་བསྒྱུར་བའི་བཀོལ་སྤྱོད་ལ་རྒྱབ་སྐྱོར་བྱེད་མི་སྲིད། མིང་བསྒྱུར་མ་ཐུབ་པ་རེད། Peony::DriveRenamePlugin drive rename སྒྲིག་ཆས་ཀྱི་མིང་བསྒྱུར་བ། Drive rename QObject Warning ཐ་ཚིག་སྒྲོག་པ། The device may not support the rename operation, rename failed! སྒྲིག་ཆས་འདིས་མིང་བསྒྱུར་བའི་བཀོལ་སྤྱོད་ལ་རྒྱབ་སྐྱོར་བྱེད་མི་སྲིད། མིང་བསྒྱུར་མ་ཐུབ་པ་རེད། peony-extensions/peony-drive-rename/translations/peony-drive-rename_ug_CN.ts0000664000175000017500000000765715156143275026437 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename قايتا ئىسىم فامىلە قىلىش Device name: ئۈسكۈنە نامى: Warning دىققەت Renaming cannot start with a decimal point, Please re-enter! نامىنى ئۆزگەرتىشتە دېسكىلىق نۇقتىدىن باشلىنالمايدۇ، قايتا كىرىڭ! The device name exceeds the character limit, rename failed! ئۈسكۈنە نامى پېرسوناژ چەكلىمىدىن ئېشىپ كەتتى، قايتا ئىسىم قويۇش مەغلۇپ بولدى! Renaming will unmount the device. Do you want to continue? نامى ئۆزگەرتكەندە ئۈسكۈنىنى تىندۇرىدۇ. داۋام قىلغۇڭىز بارمۇ؟ The device may not support the rename operation, rename failed! ئۈسكۈنە بەلكىم قايتا ئىسىم ئۆزگەرتىش مەشخۇلاتى قوللىماسلىقى مۇمكىن، قايتا ئىسىم بېرىش مەغلۇپ بولدى! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename قوزغىتىش نامى Drive rename Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning دىققەت The device may not support the rename operation, rename failed! ئۈسكۈنە بەلكىم قايتا ئىسىم ئۆزگەرتىش مەشخۇلاتى قوللىماسلىقى مۇمكىن، قايتا ئىسىم بېرىش مەغلۇپ بولدى! peony-extensions/peony-drive-rename/translations/peony-drive-rename_fr.ts0000664000175000017500000000677315156143275026051 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename Renommer Device name: Nom de l’appareil : Warning Avertissement Renaming cannot start with a decimal point, Please re-enter! Le renommage ne peut pas commencer par une virgule, veuillez entrer à nouveau ! The device name exceeds the character limit, rename failed! Le nom de l’appareil dépasse la limite de caractères, le renommage a échoué ! Renaming will unmount the device. Do you want to continue? Le changement de nom démontera l’appareil. Voulez-vous continuer ? The device may not support the rename operation, rename failed! 可能设备不支持重命名操作,重命名失败! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename Renommer le lecteur Drive rename Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning Avertissement The device may not support the rename operation, rename failed! L’appareil peut ne pas prendre en charge l’opération de renommage, le renommage a échoué ! peony-extensions/peony-drive-rename/translations/peony-drive-rename_zh_CN.ts0000664000175000017500000000654415156143275026437 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename 重命名 Device name: 设备名: Warning 警告 Renaming cannot start with a decimal point, Please re-enter! 重命名时不支持首字符为小数点,请重新输入! The device name exceeds the character limit, rename failed! 设备名超过字符限制,重命名失败! Renaming will unmount the device. Do you want to continue? 重命名将会卸载设备,是否继续? The device may not support the rename operation, rename failed! 可能设备不支持重命名操作,重命名失败! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename 设备重命名 Drive rename 设备重命名 Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning 警告 The device may not support the rename operation, rename failed! 可能设备不支持重命名操作,重命名失败! peony-extensions/peony-drive-rename/translations/peony-drive-rename_es.ts0000664000175000017500000000702415156143275026037 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename Rebautizar Device name: Nombre del dispositivo: Warning Advertencia Renaming cannot start with a decimal point, Please re-enter! El cambio de nombre no puede comenzar con un punto decimal, ¡vuelva a ingresar! The device name exceeds the character limit, rename failed! El nombre del dispositivo supera el límite de caracteres, ¡error en el cambio de nombre! Renaming will unmount the device. Do you want to continue? Al cambiar el nombre, se desmontará el dispositivo. ¿Quieres continuar? The device may not support the rename operation, rename failed! 可能设备不支持重命名操作,重命名失败! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename Cambio de nombre de la unidad Drive rename Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning Advertencia The device may not support the rename operation, rename failed! Es posible que el dispositivo no admita la operación de cambio de nombre, ¡error en el cambio de nombre! peony-extensions/peony-drive-rename/translations/peony-drive-rename_ky_KG.ts0000664000175000017500000000753615156143275026444 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename Атын алмаштыруу Device name: Түзмөк аты: Warning Эскертүү Renaming cannot start with a decimal point, Please re-enter! Атын алмаштыруу бузуку пункттан баштала албайт, сураныч, кайра киргиле! The device name exceeds the character limit, rename failed! Аппараттын аты мүнөздүн чегинен ашып түшөт, атын алмаштыруу ишке ашпады! Renaming will unmount the device. Do you want to continue? Атын алмаштыруу аппаратты ачат. Улантууну каалайсызбы? The device may not support the rename operation, rename failed! Аппарат атын өзгөртүү операциясын колдобошу мүмкүн, атын алмаштыруу ишке ашпады! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename дисктин атын өзгөртүү Drive rename Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning Эскертүү The device may not support the rename operation, rename failed! Аппарат атын өзгөртүү операциясын колдобошу мүмкүн, атын алмаштыруу ишке ашпады! peony-extensions/peony-drive-rename/translations/peony-drive-rename_mn.ts0000664000175000017500000001074215156143275026043 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠬᠦ Device name: ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢᠨ ᠨᠡᠷᠡᠢᠳᠦᠯ: Warning ᠰᠡᠷᠡᠮᠵᠢᠯᠡᠬᠦᠯᠦᠯ Renaming cannot start with a decimal point, Please re-enter! ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠦᠯ ᠪᠤᠳᠠᠷᠬᠠᠢ ᠲᠤᠭ᠎ᠠ ᠪᠡᠷ ᠡᠬᠢᠯᠡᠵᠤ ᠪᠤᠯᠬᠤ ᠦᠬᠡᠢ᠂ ᠳᠠᠬᠢᠵᠤ ᠤᠷᠤᠭᠤᠯᠤᠭᠠᠷᠠᠢ! The device name exceeds the character limit, rename failed! ᠲᠦᠬᠦᠬᠡᠷᠡᠮᠵᠢ ᠵᠢᠨ ᠨᠡᠷᠡᠢᠳᠦᠯ ᠦᠰᠦᠭ ᠤ᠋ᠨ ᠲᠤᠭᠠᠨ ᠤᠤ ᠬᠢᠵᠠᠭᠠᠷᠯᠠᠯ ᠡᠴᠡ ᠬᠡᠳᠦᠷᠡᠭᠰᠡᠨ᠂ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠴᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ! Renaming will unmount the device. Do you want to continue? ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠬᠦ ᠳ᠋ᠤ᠌ ᠬᠦᠷᠪᠡᠯ ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢ ᠪᠠᠭᠤᠯᠭᠠᠵᠤ ᠮᠡᠳᠡᠨ᠎ᠡ᠂ ᠦᠷᠬᠦᠯᠵᠢᠯᠡᠨ ᠬᠦᠢᠴᠡᠳᠬᠡᠬᠦ ᠤᠤ? The device may not support the rename operation, rename failed! ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠬᠦ ᠵᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠬᠡᠢ ᠪᠠᠢᠵᠤ ᠮᠡᠳᠡᠨ᠎ᠡ᠂ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠴᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢᠨ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠦᠯ Drive rename Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning ᠰᠡᠷᠡᠮᠵᠢ ᠥᠭ᠍ᠬᠦ The device may not support the rename operation, rename failed! ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠬᠦᠨᠳᠦ ᠨᠡᠷᠡᠶᠢᠳᠦᠨ ᠠᠵᠢᠯᠯᠠᠬᠤ ᠶᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠭᠡᠢ ᠮᠠᠭᠠᠳ ᠂ ᠬᠦᠨᠳᠦ ᠪᠡᠷ ᠨᠡᠷᠡᠶᠢᠳᠦᠯ ᠢᠯᠠᠭᠳᠠᠵᠠᠢ ! peony-extensions/peony-drive-rename/translations/peony-drive-rename_de.ts0000664000175000017500000000700015156143275026012 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename Umbenennen Device name: Name des Geräts: Warning Warnung Renaming cannot start with a decimal point, Please re-enter! Die Umbenennung darf nicht mit einem Komma beginnen, bitte erneut eingeben! The device name exceeds the character limit, rename failed! Der Gerätename überschreitet die Zeichenbegrenzung, Umbenennung fehlgeschlagen! Renaming will unmount the device. Do you want to continue? Durch das Umbenennen wird die Bereitstellung des Geräts aufgehoben. Möchten Sie fortfahren? The device may not support the rename operation, rename failed! 可能设备不支持重命名操作,重命名失败! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename Umbenennen von Laufwerken Drive rename Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning Warnung The device may not support the rename operation, rename failed! Das Gerät unterstützt den Umbenennungsvorgang möglicherweise nicht, Umbenennung fehlgeschlagen! peony-extensions/peony-drive-rename/translations/peony-drive-rename_kk_KZ.ts0000664000175000017500000000757715156143275026456 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename Атын өзгерту Device name: Құрылғының атауы: Warning Ескерту Renaming cannot start with a decimal point, Please re-enter! Атауын өзгерту ондық нүктеден басталмауы мүмкін, Қайта енгізуіңізді сұраймын! The device name exceeds the character limit, rename failed! Құрылғының атауы таңба шегінен асып түседі, атын өзгерту сәтсіз аяқталды! Renaming will unmount the device. Do you want to continue? Атын өзгерту құрылғыны қайта атауға мүмкіндік береді. Жалғастырғыңыз келе ме? The device may not support the rename operation, rename failed! Құрылғы атын өзгерту операциясын қолдамауы мүмкін, атын өзгерту жаңылысы! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename дискінің атын өзгерту Drive rename Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning Ескерту The device may not support the rename operation, rename failed! Құрылғы атын өзгерту операциясын қолдамауы мүмкін, атын өзгерту жаңылысы! peony-extensions/peony-drive-rename/translations/peony-drive-rename_zh_HK.ts0000664000175000017500000000654515156143275026442 0ustar fengfeng Peony::DriveRename drive rename 设备重命名 Rename 重新命名 Device name: 裝置名稱: Warning 警告 Renaming cannot start with a decimal point, Please re-enter! 重命名不能以小數點開頭,請重新輸入! The device name exceeds the character limit, rename failed! 設備名稱超出字元限制,重命名失敗! Renaming will unmount the device. Do you want to continue? 重命名將卸載設備。是否要繼續? The device may not support the rename operation, rename failed! 可能设备不支持重命名操作,重命名失败! Failed to rename! 重命名失败! Peony::DriveRenamePlugin drive rename 设备重命名 Drive rename 驅動器重命名 Peony::DriverAction Send to a removable device 发送到移动设备 Peony::SendToPlugin Send to a removable device 发送到移动设备 QObject Warning 警告 The device may not support the rename operation, rename failed! 設備可能不支援重命名作,重命名失敗! peony-extensions/peony-drive-rename/driverenameplugin.cpp0000664000175000017500000000233615156143137022773 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #include "driverenameplugin.h" #include "drive-rename.h" #include using namespace Peony; DriveRenamePlugin::DriveRenamePlugin(QObject *parent) : QObject(parent) { m_internalPlugin = new DriveRename(this); qApp->setProperty("deviceRenamePluginLoaded", true); } QList DriveRenamePlugin::menuActions(Peony::MenuPluginInterface::Types types, const QString &uri, const QStringList &selectionUris) { return m_internalPlugin->menuActions(types, uri, selectionUris); } peony-extensions/peony-drive-rename/driverenameplugin.h0000664000175000017500000000350515156143137022437 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #ifndef DRIVERENAMEPLUGIN_H #define DRIVERENAMEPLUGIN_H #include #include #include "drive-rename.h" namespace Peony { class DriveRenamePlugin : public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit DriveRenamePlugin(QObject *parent = nullptr); QString testPlugin() override {return "";} bool isEnable() override {return mEnable;} void setEnable(bool enable) override {mEnable = enable;} const QString description() override {return tr("Drive rename");} const QString name() override {return "Peony Qt drive rename";} const QIcon icon() override {return QIcon::fromTheme("drive-harddisk-symbolic");} PluginInterface::PluginType pluginType() override {return PluginInterface::MenuPlugin;} QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; private: DriveRename *m_internalPlugin = nullptr; bool mEnable = true; }; } #endif // DRIVERENAMEPLUGIN_H peony-extensions/peony-drive-rename/drive-rename.h0000664000175000017500000000246415156143137021300 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2021, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: deng hao * */ #ifndef DRIVERENAME_H #define DRIVERENAME_H #include #include #include "volumeManager.h" #include namespace Peony { class DriveRename : public QObject { Q_OBJECT public: explicit DriveRename(QObject *parent = nullptr); QList menuActions(Peony::MenuPluginInterface::Types types, const QString &uri, const QStringList &selectionUris); private: bool mEnable; QString mDevName; }; } #endif // DRIVERENAME_H peony-extensions/peony-drive-rename/drive-rename.cpp0000664000175000017500000004141415156143275021634 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2021, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: deng hao * */ #include "drive-rename.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct _DeviceRenameData DeviceRenameData; void device_rename(const char* devName, const char* name); static void udisk_umounted (GMount* mount, GAsyncResult *res, gpointer udata); static void udisk_filesystem_unmounted (GObject *source_object, GAsyncResult *res, gpointer udata); UDisksObject* getObjectFromBlockDevice(UDisksClient* client, const gchar* bdevice); static void udisk_setLabel_callback(UDisksFilesystem *diskFilesystem, GAsyncResult *res, gpointer udata); bool is_block_device_encrypted (UDisksClient *client, const char* device_name) { struct stat statbuf; g_autoptr (UDisksBlock) block = NULL; g_return_val_if_fail(stat(device_name, &statbuf) == 0, false); block = udisks_client_get_block_for_dev (client, statbuf.st_rdev); g_return_val_if_fail(block != NULL, false); return strcmp(udisks_block_get_id_usage(block), "crypto") == 0; } struct _DeviceRenameData { QString devName; QString rename; Peony::DriveRename* pThis; }; Peony::DriveRename::DriveRename(QObject *parent) : QObject(parent), mEnable(true) { QTranslator *t = new QTranslator(this); t->load(":/translations/peony-drive-rename_"+QLocale::system().name()); QApplication::installTranslator(t); } QList Peony::DriveRename::menuActions(Peony::MenuPluginInterface::Types types, const QString &uri, const QStringList &selectionUris) { QList l; if (selectionUris.count() != 1 || types != Peony::MenuPluginInterface::Type::SideBar) { return l; } QString suri = selectionUris.first(); g_autoptr(GFile) file = g_file_new_for_uri(suri.toUtf8().constData()); g_return_val_if_fail(file, l); // check is mount point and get dev name g_autoptr (GError) error = NULL; g_autoptr (GFileInfo) fileInfo = g_file_query_info (file, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT "," G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT "," G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE "," G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error); if (error) qDebug() << error->message; g_return_val_if_fail (G_IS_FILE_INFO (fileInfo) && !error, l); gboolean canUmount = g_file_info_get_attribute_boolean (fileInfo, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT); gboolean isMountPoint = g_file_info_get_attribute_boolean (fileInfo, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT); g_autofree char* devName = g_file_info_get_attribute_as_string (fileInfo, G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE); bool canRename = (isMountPoint || canUmount || devName); qDebug() << "uri: " << uri << " " << (canRename ? "can rename" : "can not rename"); g_return_val_if_fail (canRename, l); QString type = Peony::FileUtils::getFileSystemType(suri); // if device has mounted GMount* mount = NULL; if (isMountPoint || canUmount) { if (uri.startsWith ("computer://")) { g_autofree char* targetUri = g_file_info_get_attribute_as_string (fileInfo, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); if (targetUri) { // related to: #105070 // bool isWayland = qApp->property("isWayland").toBool(); // if (!isWayland) { // QString tmp = targetUri; // if (tmp == "file:///data") { // // 如果是data盘,则跳过 // return l; // } // } bool isWayland = qApp->property("isWayland").toBool(); if (isWayland) { QString tmp = targetUri; if (tmp == "file:///data") { return l; } } g_autoptr (GFile) tfile = g_file_new_for_uri (targetUri); if (G_IS_FILE (tfile)) { mount = g_file_find_enclosing_mount (tfile, NULL, &error); if (error) qDebug() << error->message; } } } else { mount = g_file_find_enclosing_mount (file, NULL, &error); if (error) qDebug() << error->message; } g_return_val_if_fail (G_IS_MOUNT (mount), l); if(type.startsWith("iso")){ g_autoptr (GFile) root = g_mount_get_root (mount); g_autoptr (GFileInfo) rootInfo = g_file_query_info (root, "access::", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error); if (error) qDebug() << "error: " << error->message; bool canRename = true; if (G_IS_FILE_INFO (rootInfo)) { bool write = true; if (g_file_info_has_attribute (rootInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { write = g_file_info_get_attribute_boolean (rootInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); } bool execute = true; if (g_file_info_has_attribute (rootInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) { execute = g_file_info_get_attribute_boolean (rootInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE); } if (!write || !execute) canRename = false; } g_return_val_if_fail (canRename, l); } FileEnumerator e; e.setEnumerateDirectory(suri); e.enumerateSync(); QStringList uris = e.getChildrenUris(); int count = 0; for (QString uri1 : uris) { QString lastPart = uri1.section('/', -1); // 截取URI的最后部分 if (lastPart.compare("boot", Qt::CaseSensitive) == 0 || lastPart.compare("EFI", Qt::CaseSensitive) == 0) { ++count; } if (count >= 2) { return l; } } if (!devName) { g_autoptr (GVolume) volume = g_mount_get_volume (mount); g_return_val_if_fail (G_IS_VOLUME (volume), l); devName = g_volume_get_identifier (volume, G_DRIVE_IDENTIFIER_KIND_UNIX_DEVICE); } } g_return_val_if_fail (devName, l); // 注意要筛选的设备,块设备、非根设备 // 判断是否为光盘设备(光盘设备不允许重命名) if (suri.startsWith("computer:///") && suri.endsWith(".drive") && 0 != g_ascii_strncasecmp ("/dev/cd", devName, 7) && 0 != g_ascii_strncasecmp ("/dev/sr", devName, 7) && 0 != g_ascii_strncasecmp ("/dev/loop", devName, 9) && !type.startsWith("iso")) { mDevName = devName; g_autoptr (UDisksClient) udisks_client = udisks_client_new_sync(nullptr, nullptr); bool isEncryptedDevice = false; if (!mDevName.startsWith("/dev/dm")) { // 未解密设备不允许重命名 isEncryptedDevice = is_block_device_encrypted(udisks_client, devName); } QAction* action = new QAction(tr("Rename")); if (action && !isEncryptedDevice) { l << action; } // 这里 action 弹框并执行重命名函数 connect(action, &QAction::triggered, this, [=] () { // 输入框出来,提示用户U盘重命名为 ... // 获取到用户的输入 // 执行重命名 bool ok = false; QString text = QInputDialog::getText(nullptr, tr("Rename"), tr("Device name:"), QLineEdit::Normal, "", &ok); if (ok && !text.isNull() && !text.isEmpty()) { //首字符是.提示非法 bug#93280 if(text.at(0) == '.'){ QMessageBox::warning(nullptr, tr("Warning"), tr("Renaming cannot start with a decimal point, Please re-enter!"), QMessageBox::Ok); return; } QString type = Peony::FileUtils::getFileSystemType(suri); if((type.compare(QString::fromLocal8Bit("vfat")) == 0 && text.toUtf8().length() > 11) || (type.compare(QString::fromLocal8Bit("exfat")) == 0 && text.toUtf8().length() > 15) || (type.compare(QString::fromLocal8Bit("ext4")) == 0 && text.toUtf8().length() > 16)){ QMessageBox::warning(nullptr, tr("Warning"), tr("The device name exceeds the character limit, rename failed!"), QMessageBox::Ok); return; } // 修改名字 if (mount && !mDevName.startsWith("/dev/dm")) { qDebug() << "[DriveRename] normal device unmount then rename:" << mDevName; int ret = QMessageBox::warning (nullptr, tr("Warning"), tr("Renaming will unmount the device. Do you want to continue?"), QMessageBox::Ok | QMessageBox::Cancel); if (QMessageBox::Cancel == ret) return; // 释放、释放 GMount DeviceRenameData* data = new DeviceRenameData; data->devName = mDevName; data->rename = text; data->pThis = this; g_autoptr (GMountOperation) mount_op = g_mount_operation_new(); g_mount_unmount_with_operation (mount, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, (GAsyncReadyCallback) udisk_umounted, data); } else if (mount && mDevName.startsWith("/dev/dm")) { qDebug() << "[DriveRename] encrypted device udisks unmount then rename:" << mDevName; g_autoptr (UDisksClient) client = udisks_client_new_sync(NULL, NULL); g_autoptr (UDisksObject) udiskObj = getObjectFromBlockDevice(client, mDevName.toUtf8().constData()); if (!udiskObj) return; g_autoptr (UDisksFilesystem) diskFilesystem = udisks_object_get_filesystem(udiskObj); if (!diskFilesystem) return; GVariantBuilder options; g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT); DeviceRenameData* data = new DeviceRenameData; data->devName = mDevName; data->rename = text; data->pThis = this; udisks_filesystem_call_unmount(diskFilesystem, g_variant_builder_end(&options), NULL, (GAsyncReadyCallback)udisk_filesystem_unmounted, data); } else { qDebug() << "[DriveRename] direct rename (not mounted):" << mDevName; device_rename(mDevName.toUtf8().constData(), text.toUtf8().constData()); } } }); } Q_UNUSED(uri) Q_UNUSED(types) return l; } void device_rename(const char* devName, const char* name) { //判断参数个数是否合法 g_return_if_fail(devName && name); g_autoptr (UDisksClient) client = udisks_client_new_sync(NULL, NULL); g_return_if_fail(client); g_autoptr (UDisksObject) udiskObj = getObjectFromBlockDevice(client, devName); g_return_if_fail(udiskObj); //从设备名获取文件系统类型 g_autoptr (UDisksFilesystem) diskFilesystem = udisks_object_get_filesystem(udiskObj); g_return_if_fail(diskFilesystem); GVariantBuilder optionsBuilder; g_variant_builder_init(&optionsBuilder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&optionsBuilder, "{sv}", "label", g_variant_new_string (devName)); g_variant_builder_add (&optionsBuilder, "{sv}", "take-ownership", g_variant_new_boolean (TRUE)); // g_autoptr (GError) error = NULL; // gboolean ret = udisks_filesystem_call_set_label_sync (diskFilesystem, name, g_variant_builder_end(&optionsBuilder), NULL, &error); // if (error) { // qDebug() << error->message; // } // return ret ? 0 : -1; QList *volumeLists; Experimental_Peony::Volume *volume = nullptr; volumeLists = Experimental_Peony::VolumeManager::getInstance()->allVaildVolumes(); int volumeCount = volumeLists->count(); for (int i = 0; i < volumeCount; ++i) { auto tmpVolume = volumeLists->at(i); if (tmpVolume.device() == devName) { volume = new Experimental_Peony::Volume(tmpVolume); break; } } udisks_filesystem_call_set_label(diskFilesystem, name, g_variant_builder_end(&optionsBuilder), NULL, GAsyncReadyCallback(udisk_setLabel_callback), volume); } UDisksObject* getObjectFromBlockDevice(UDisksClient* client, const gchar* bdevice) { struct stat statbuf; UDisksBlock* block = NULL; UDisksObject* object = NULL; UDisksObject* cryptoBackingObject = NULL; g_autofree const gchar* cryptoBackingDevice = NULL; g_return_val_if_fail(stat(bdevice, &statbuf) == 0, object); block = udisks_client_get_block_for_dev (client, statbuf.st_rdev); g_return_val_if_fail(block != NULL, object); object = UDISKS_OBJECT (g_dbus_interface_dup_object (G_DBUS_INTERFACE (block))); return object; cryptoBackingDevice = udisks_block_get_crypto_backing_device ((udisks_object_peek_block (object))); cryptoBackingObject = udisks_client_get_object (client, cryptoBackingDevice); if (cryptoBackingObject != NULL) { g_object_unref (object); object = cryptoBackingObject; } g_object_unref (block); return object; } static void udisk_umounted (GMount* mount, GAsyncResult *res, gpointer udata) { g_autoptr (GError) error = NULL; DeviceRenameData* data = (DeviceRenameData*) udata; qDebug() << "[udisk_umounted] called for device:" << data->devName; bool ok = g_mount_unmount_with_operation_finish (G_MOUNT (mount), res, &error); if (ok) { qDebug() << "[udisk_umounted] unmount successful, proceeding with rename"; device_rename(data->devName.toUtf8().constData(), data->rename.toUtf8().constData()); } else { if (error) { if (strcmp(error->message, "Not authorized to perform operation")) {/* gmountOperation会弹出授权框,防止二次弹框,linkto bug#101075 */ QMessageBox::warning(nullptr, data->pThis->tr("Warning"), error->message, QMessageBox::Ok); } qDebug() << "[udisk_umounted]" << error->message; } } if (data) delete data; if (mount) g_object_unref (mount); } static void udisk_filesystem_unmounted (GObject *source_object, GAsyncResult *res, gpointer udata) { g_autoptr (GError) error = NULL; DeviceRenameData* data = (DeviceRenameData*) udata; UDisksFilesystem *filesystem = UDISKS_FILESYSTEM(source_object); qDebug() << "[udisk_filesystem_unmounted] called for device:" << data->devName; bool ok = udisks_filesystem_call_unmount_finish(filesystem, res, &error); if (ok) { qDebug() << "[udisk_filesystem_unmounted] unmount successful, proceeding with rename"; device_rename(data->devName.toUtf8().constData(), data->rename.toUtf8().constData()); } else { if (error) { if (strcmp(error->message, "Not authorized to perform operation")) { QMessageBox::warning(nullptr, data->pThis->tr("Warning"), error->message, QMessageBox::Ok); } qDebug() << "[udisk_filesystem_unmounted]" << error->message; } } if (data) delete data; } static void udisk_setLabel_callback(UDisksFilesystem *diskFilesystem, GAsyncResult *res, gpointer udata) { Q_UNUSED(udata); g_autoptr (GError) error = NULL; Experimental_Peony::Volume *volume = static_cast(udata); gboolean ret = udisks_filesystem_call_set_label_finish(diskFilesystem, res, &error); if (!ret && error) { qDebug() << "udisk_setLabel_callback error:" << error->code << error->message; QMessageBox::warning (nullptr, QObject::tr("Warning"), QObject::tr("The device may not support the rename operation, rename failed!"), QMessageBox::Ok); } else if (!error) { volume->mount(); } if (volume) { delete volume; } } peony-extensions/peony-extension-computer-view/0000775000175000017500000000000015156143275021010 5ustar fengfengpeony-extensions/peony-extension-computer-view/login-remote-filesystem.ui0000664000175000017500000003440615156143137026136 0ustar fengfeng LoginRemoteFilesystem true 0 0 430 532 0 0 430 532 430 532 Noto Sans CJK SC 14 Connect to Sever .. Qt::LeftToRight width:430px; height:532px; border:0; border-radius:6px; false true 28 48 188 23 0 0 width:118px; height:23px; font-size:20px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; server information 28 260 188 23 width:143px; height:23px; font-size:20px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; user information 28 400 56 18 Noto Sans CJK SC -1 50 false width:56px; height:13px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; tag 96 312 300 30 Noto Sans CJK SC 11 width:300px; height:30px; padding:3px 5px; opacity:0.04; border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; 28 318 40 14 Noto Sans CJK SC -1 50 false width:40px; height:14px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; user 96 351 300 30 Noto Sans Mono CJK SC 11 width:300px; height:30px; padding:3px 5px; opacity:0.04; border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; 28 358 66 18 Noto Sans CJK SC -1 50 false width:28px; height:13px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; password 96 390 300 30 Noto Sans Mono CJK SC 11 width:300px; height:30px; padding:3px 5px; opacity:0.04; border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; 28 145 60 14 Noto Sans CJK SC -1 50 false width:28px; height:13px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; protocol 28 105 42 14 42 14 999999 999999 Noto Sans CJK SC -1 50 false width:42px; height:14px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; server 28 185 60 18 Noto Sans CJK SC -1 50 false width:42px; height:14px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; directory 96 138 301 32 Noto Sans Mono CJK SC 11 width:300px; height:30px; padding:3px 5px; opacity:0.04; border-radius:4px; border:1px solid rgba(62, 108, 229, 0.3); SAMBA FTP 96 178 300 30 Noto Sans Mono CJK SC 11 width:300px; height:30px; padding:3px 5px; opacity:0.04; border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; / 296 105 27 14 Noto Sans CJK SC -1 50 false width:27px; height:12px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; border:0; port 336 98 61 30 Noto Sans Mono CJK SC 11 width:60px; height:30px; padding:3px; opacity:0.04; border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; true 20 21 137 138 139 445 96 98 182 30 width:182px; height:30px; padding:3px 5px; opacity:0.04; border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; 316 471 80 30 Noto Sans CJK SC -1 50 false width:80px; height:30px; background:rgba(62,108,229,1); border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; color:rgba(255,255,255,1); line-height:30px; ok 228 471 80 30 Noto Sans CJK SC -1 50 false width:80px; height:30px; border:1px solid rgba(62, 108, 229, 0.3); border-radius:4px; font-size:14px; font-family:Noto Sans CJK SC; font-weight:400; line-height:30px; cancel ip_edit port_comboBox type_comboBox file_lineEdit user_lineEdit pwd_lineEdit add_lineEdit cancel_pb ok_pb cancel_pb clicked() LoginRemoteFilesystem close() 259 478 193 466 ok_pb clicked() LoginRemoteFilesystem accept() 366 490 412 443 help() peony-extensions/peony-extension-computer-view/peony-extension-computer-view_bo_CN.ts0000664000175000017500000005452715156143275030405 0ustar fengfeng ComputerItemDelegate You should mount volume first ཐོག་མར་ཁུལ་བགོས་འགེལ་དགོས། ComputerNetworkItem Network Neighborhood དྲ་ཐོག་གི་ཁྱིམ་མཚེས། ComputerRemoteVolumeItem Remote རྒྱང་རིང་གི་དཀར་ཆག ComputerUserShareItem User Share གཞི་གྲངས་མཉམ་སྤྱོད། Data གཞི་གྲངས་སྡེར། ComputerVolumeItem Volume རང་སའི་ཁུལ་བགོས། System Disk File System ཡིག་ཆའི་མ་ལག Data གཞི་གྲངས་སྡེར། Intel::ComputerItemDelegate You should mount volume first ཐོག་མར་ཁུལ་བགོས་འགེལ་དགོས། Intel::ComputerNetworkItem Network Neighborhood དྲ་ཐོག་གི་ཁྱིམ་མཚེས། Intel::ComputerRemoteVolumeItem Remote རྒྱང་རིང་གི་དཀར་ཆག Intel::ComputerViewContainer Connect a server རྒྱང་རིང་ཞབས་ཞུའི་ཆས་སྦྲེལ་མཐུད་བྱ་དགོས། Unmount ལེན་པ། Eject འཕར་ཐོན། Property གཏོགས་གཤིས། You have to mount this volume first ཐོག་མར་ཁུལ་བགོས་འགེལ་དགོས། Intel::ComputerVolumeItem Volume རང་སའི་ཁུལ་བགོས། System Disk User Disk LoginRemoteFilesystem Connect to Sever ཞབས་ཞུའི་ཆས་དང་སྦྲེལ་ཡོད། server information ཞབས་ཞུ་ཆས་ཀྱི་ཆ་འཕྲིན། user information སྤྱོད་མཁན་གྱི་ཆ་འཕྲིན། tag གདོང་འཛར། user སྤྱོད་མཁན་གྱི་མིང་། password གསང་ཨང་། protocol གྲོས་མཐུན། server ཞབས་ཞུ་སྣེའི་ཤག་གནས། directory དཀར་ཆག SAMBA SAMBA FTP FTP / / port མཐུད་སྣེ། 20 20 21 21 137 137 138 138 139 139 445 445 ok གཏན་འཁེལ། cancel འདོར་བ། Peony::ComputerViewContainer Connect a server རྒྱང་རིང་ཞབས་ཞུའི་ཆས་སྦྲེལ་མཐུད་བྱ་དགོས། sftp://, etc... 如sftp://... Cancel 取消 OK 确定 Unmount ལེན་པ། Eject འཕར་ཐོན། Format Open In New Window Open In New Tab format རྣམ་པར་འཇོག་པ། Property གཏོགས་གཤིས། You have to mount this volume first ཐོག་མར་ཁུལ་བགོས་འགེལ་དགོས། Peony::DriveRename Rename མིང་བསྒྱུར་བ། Device name: སྒྲིག་ཆས་ཀྱི་མིང་གཤམ་གསལ། Warning ཐ་ཚིག་སྒྲོག་པ། Renaming cannot start with a decimal point, Please re-enter! མིང་བསྒྱུར་བ་ནི་ཆེས་ཆུང་བ་ནས་འགོ་རྩོམ་མི་ཐུབ་པས། ཡང་བསྐྱར་ནང་དུ་ཞུགས་རོགས། The device name exceeds the character limit, rename failed! སྒྲིག་ཆས་ཀྱི་མིང་དེ་ཡི་གེའི་ཚད་ལས་བརྒལ་ནས་མིང་བསྒྱུར་མ་ཐུབ་པ་རེད། Renaming will unmount the device. Do you want to continue? མིང་བསྒྱུར་ན་སྒྲིག་ཆས་འདི་མེད་པར་བཟོ་སྲིད། ཁྱོད་ཀྱིས་ད་དུང་མུ་མཐུད་དུ་རྒྱུན་འཁྱོངས་བྱེད་དགོས་སམ། The device may not support the rename operation, rename failed! སྒྲིག་ཆས་འདིས་མིང་བསྒྱུར་བའི་བཀོལ་སྤྱོད་ལ་རྒྱབ་སྐྱོར་བྱེད་མི་སྲིད། མིང་བསྒྱུར་མ་ཐུབ་པ་རེད། QObject Computer View གློག་ཀླད་མཐོང་རིས། Show drives, network and personal directories Show drives, network and personal directories. སྒྲིག་ཆས་ཁུལ་བགོས་དང་།དྲ་རྒྱའི་དཀར་ཆག་དང་མི་སྒེར་གྱི་དཀར་ཆག་འཆར་མངོན་བྱེད་པ། One or more programs prevented the unmount operation. བྱ་རིམ་གཅིག་གམ་མང་པོས་བཤིག་འདོན་བཀོལ་སྤྱོད་བྱེད་པར་བཀག་འགོག་བྱས། Unmount failed བཤིག་འདོན་ཕམ་པ། Error: %1 Do you want to unmount forcely? ནོར་འཁྲུལ།:%1 བཙན་ཤེད་ཀྱིས་བཤིག་འདོན་བྱེད་དམ། It need to synchronize before operating the device,place wait! དེས་སྒྲིག་ཆས་བཀོལ་སྤྱོད་མ་བྱས་གོང་ལ་དུས་མཉམ་དུ་སྒུག་དགོས། The device has been mount successfully! སྒྲིག་ཆས་འདི་བདེ་བླག་ངང་སྒྲིག་སྦྱོར་བྱས་པ་རེད། Data synchronization is complete,the device has been unmount successfully! གཞི་གྲངས་དུས་གཅིག་ཏུ་ལེགས་འགྲུབ་བྱུང་བ་དང་། སྒྲིག་ཆས་འདི་ལེགས་འགྲུབ་བྱུང་མེད། Error: %1 ནོར་འཁྲུལ། %1\n Eject device failed, the reason may be that the device has been removed, etc. Unable to unmount it, you may need to close some programs, such as: GParted etc. བཤིག་འདོན་བྱེད་ཐབས་བྲལ་བས་ཁྱེད་ཀྱིས་སྔོན་ལ་བྱ་རིམ་ཁ་ཤས་ཁ་རྒྱག་དགོས།དཔེར་ན་ཁུལ་བགོས་ཀྱི་རྩོམ་སྒྲིག་ཆས་སོགས། %1 %1 Eject failed འཕར་ཐོན་ཕམ་པ། Cancel འདོར་བ། Eject Anyway གང་ལྟར་ཀྱང་འཕར་ཐོན་བྱེད་དགོས། Warning ཐ་ཚིག་སྒྲོག་པ། The device may not support the rename operation, rename failed! སྒྲིག་ཆས་འདིས་མིང་བསྒྱུར་བའི་བཀོལ་སྤྱོད་ལ་རྒྱབ་སྐྱོར་བྱེད་མི་སྲིད། མིང་བསྒྱུར་མ་ཐུབ་པ་རེད། Message recipient disconnected from message bus without replying! log remote error peony-extensions/peony-extension-computer-view/computer-view/0000775000175000017500000000000015156143275023616 5ustar fengfengpeony-extensions/peony-extension-computer-view/computer-view/abstract-computer-item.cpp0000664000175000017500000000235715156143275030724 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "abstract-computer-item.h" #include AbstractComputerItem::AbstractComputerItem(ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : QObject(parent) { m_model = model; m_parentNode = parentNode; } AbstractComputerItem::~AbstractComputerItem() { for (auto child : m_children) { child->deleteLater(); } } QModelIndex AbstractComputerItem::itemIndex() { return QModelIndex(); } peony-extensions/peony-extension-computer-view/computer-view/computer-remote-volume-item.cpp0000664000175000017500000002340015156143137031706 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "computer-remote-volume-item.h" #include "computer-model.h" #include "file-utils.h" #include #include #include QString queryTargetUri(const QString &uri) { if (uri.startsWith("computer:///")) { g_autoptr (GFile) gfile = g_file_new_for_uri(uri.toUtf8().constData()); g_autoptr (GFileInfo) gfileinfo = g_file_query_info(gfile, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, 0); g_autofree gchar* target_uri = g_file_info_get_attribute_as_string(gfileinfo, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); return target_uri; } else { return uri; } } ComputerRemoteVolumeItem::ComputerRemoteVolumeItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { m_uri = uri; m_cancellable = g_cancellable_new(); updateInfo(); auto targetUri = queryTargetUri(uri); m_model->m_volumeTargetMap.insert(uri, targetUri); m_model->addRealUri(targetUri); bool isKydrive = targetUri.startsWith("file://") && targetUri.contains("kydrive"); setProperty("isKydrive", isKydrive); if (uri == "computer:///") { m_isHidden = false; } else { //fix dock show in remote item issue, link to bug#82398 if(targetUri.startsWith("ftp://") || targetUri.startsWith("sftp://") || targetUri.startsWith("smb://") || targetUri.startsWith("file:///")) m_isHidden = false; else m_isHidden = true; } qDebug()<<"create remote volume item:"<m_volumeTargetMap.remove(m_uri); m_model->removeRealUri(m_uri); } const QString ComputerRemoteVolumeItem::displayName() { if (m_uri == "computer:///") return tr("Remote"); return m_displayName; } const QIcon ComputerRemoteVolumeItem::icon() { return m_icon; } void ComputerRemoteVolumeItem::findChildren() { if (m_uri != "computer:///") return; GFile *file = g_file_new_for_uri("computer:///"); g_file_enumerate_children_async(file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, m_cancellable, GAsyncReadyCallback(enumerate_async_callback), this); g_object_unref(file); } void ComputerRemoteVolumeItem::updateInfo() { GFile *file = g_file_new_for_uri(m_uri.toUtf8().constData()); g_file_query_info_async(file, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, m_cancellable, GAsyncReadyCallback(query_info_async_callback), this); g_object_unref(file); } void ComputerRemoteVolumeItem::unmount(GMountUnmountFlags unmountFlag) { QString targetUri = queryTargetUri(m_uri); GFile *file = g_file_new_for_uri(targetUri.toUtf8().constData()); //g_file_unmount_mountable_with_operation(file, unmountFlag, nullptr, nullptr, nullptr, nullptr); GMount *gMount = g_file_find_enclosing_mount(file, nullptr, nullptr); g_mount_unmount_with_operation(gMount, G_MOUNT_UNMOUNT_NONE, nullptr, nullptr, nullptr, nullptr); g_object_unref(file); g_object_unref(gMount); } QModelIndex ComputerRemoteVolumeItem::itemIndex() { if (!m_parentNode) return m_model->createItemIndex(0, this); return m_model->createItemIndex(m_parentNode->m_children.indexOf(this), this); } bool ComputerRemoteVolumeItem::isHidden() { // return m_isUnixDevice || m_uri == "computer:///root.link"; //fix bug#82398, hide unknow devices return m_isHidden; } void ComputerRemoteVolumeItem::onFileAdded(const QString &uri) { if (!uri.endsWith(".mount")) { return; } //not include udisk、mobile-disk、local-partition etc. QString targetUri; targetUri = queryTargetUri(uri); m_model->m_volumeTargetMap.insert(uri, targetUri); m_model->addRealUri(uri); // if(!targetUri.isEmpty() && targetUri.contains("file:///")) // return; for (auto item : m_children) { //qDebug() << "ComputerRemoteVolumeItem onFileAdded uri:"<uri(); if (item->uri() == uri) return; } m_model->beginInsertItem(itemIndex(), m_children.count()); auto item = new ComputerRemoteVolumeItem(uri, m_model, this); m_children<endInsterItem(); m_model->updateRequest(); m_model->invalidateRequest(); } void ComputerRemoteVolumeItem::onFileRemoved(const QString &uri) { int row = -1; for (auto item : m_children) { //qDebug() << "ComputerRemoteVolumeItem onFileRemoved uri:"<uri(); if (item->uri() == uri) { row = m_children.indexOf(item); break; } } if (row < 0) return; m_model->beginRemoveItem(itemIndex(), row); auto item = m_children.takeAt(row); item->deleteLater(); m_model->endRemoveItem(); m_model->updateRequest(); m_model->invalidateRequest(); } void ComputerRemoteVolumeItem::onFileChanged(const QString &uri) { for (auto item : m_children) { if (item->uri() == uri) { item->updateInfo(); return; } } } void ComputerRemoteVolumeItem::enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerRemoteVolumeItem *p_this) { GError *err = nullptr; auto enumerator = g_file_enumerate_children_finish(file, res, &err); if (enumerator) { g_file_enumerator_next_files_async(enumerator, 9999, 0, p_this->m_cancellable, GAsyncReadyCallback(find_children_async_callback), p_this); } if (err) { p_this->m_isUnixDevice = true; //hide computer:/// //QMessageBox::critical(0, 0, err->message); g_error_free(err); } } void ComputerRemoteVolumeItem::find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerRemoteVolumeItem *p_this) { GError *err = nullptr; auto infos = g_file_enumerator_next_files_finish(enumerator, res, &err); GList *l = infos; while (l) { auto info = G_FILE_INFO(l->data); l = l->next; if (!info) continue; auto file = g_file_enumerator_get_child(enumerator, info); if (!file) continue; auto uri = g_file_get_uri(file); if (!uri) continue; QString tmp = uri; if (!tmp.endsWith(".mount")) { continue; } //not include udisk、mobile-disk、local-partition etc. QString targetUri; targetUri = queryTargetUri(uri); p_this->m_model->m_volumeTargetMap.insert(uri, targetUri); p_this->m_model->addRealUri(uri); p_this->m_model->beginInsertItem(p_this->itemIndex(), p_this->m_children.count()); auto item = new ComputerRemoteVolumeItem(uri, p_this->m_model, p_this); p_this->m_children<m_model->endInsterItem(); p_this->m_model->updateRequest(); g_free(uri); g_object_unref(file); } if (infos) g_list_free_full(infos, g_object_unref); if (enumerator) { g_file_enumerator_close(enumerator, nullptr, nullptr); g_object_unref(enumerator); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } else { p_this->m_watcher = new Peony::FileWatcher("computer:///", p_this); connect(p_this->m_watcher, &Peony::FileWatcher::fileCreated, p_this, &ComputerRemoteVolumeItem::onFileAdded); connect(p_this->m_watcher, &Peony::FileWatcher::fileDeleted, p_this, &ComputerRemoteVolumeItem::onFileRemoved); connect(p_this->m_watcher, &Peony::FileWatcher::fileChanged, p_this, &ComputerRemoteVolumeItem::onFileChanged); p_this->m_watcher->startMonitor(); } } void ComputerRemoteVolumeItem::query_info_async_callback(GFile *file, GAsyncResult *res, ComputerRemoteVolumeItem *p_this) { GError *err = nullptr; GFileInfo *info = g_file_query_info_finish(file, res, &err); if (info) { p_this->m_isUnixDevice = g_file_info_has_attribute(info, G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE_FILE); p_this->m_displayName = g_file_info_get_attribute_string(info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); GIcon *icon = g_file_info_get_icon(info); const gchar * const * names = g_themed_icon_get_names(G_THEMED_ICON(icon)); if (names) { const char *name = *names; if (name) { p_this->m_icon = QIcon::fromTheme(name); } } p_this->m_model->dataChanged(p_this->itemIndex(), p_this->itemIndex()); qDebug()<<"query_info_async_callback:"<m_uri<m_isUnixDevice; g_object_unref(info); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } } peony-extensions/peony-extension-computer-view/computer-view/abstract-computer-item.h0000664000175000017500000000452415156143137030364 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef ABSTRACTCOMPUTERITEM_H #define ABSTRACTCOMPUTERITEM_H #include #include #include class ComputerModel; class AbstractComputerItem : public QObject { Q_OBJECT public: enum Type { Invalid, Personal, Volume, RemoteVolume, Network }; Q_ENUM(Type) explicit AbstractComputerItem(ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); ~AbstractComputerItem(); virtual Type itemType() {return Invalid;} virtual const QString uri() {return nullptr;} virtual const QString displayName() {return nullptr;} virtual const QIcon icon() {return QIcon::fromTheme("text-plain");} virtual bool hasChildren() {return false;} virtual void findChildren() {} virtual void clearChildren() {} virtual void updateInfo() {} virtual void check() {} virtual bool isMount () {return false;} //for volumn virtual qint64 totalSpace() {return 0;} virtual qint64 usedSpace() {return 0;} virtual bool canUnmount() {return false;} virtual void unmount(GMountUnmountFlags unmountFlag) {} virtual void mount() {} virtual bool canEject() {return false;} virtual void eject(GMountUnmountFlags ejectFlag) {} //for remote volume virtual bool isHidden() {return false;} virtual QModelIndex itemIndex(); public: ComputerModel *m_model; AbstractComputerItem *m_parentNode; QList m_children; QString m_unixDeviceName; }; #endif // ABSTRACTCOMPUTERITEM_H peony-extensions/peony-extension-computer-view/computer-view/computer-proxy-model.h0000664000175000017500000000344415156143137030104 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERPROXYMODEL_H #define COMPUTERPROXYMODEL_H #include #include #include class ComputerModel; class AbstractComputerItem; class ComputerProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit ComputerProxyModel(QObject *parent = nullptr); static ComputerProxyModel *globalInstance(); AbstractComputerItem *itemFromIndex(const QModelIndex &proxyIndex); QString tryGetVolumeUriFromMountTarget(const QString &mountTargetUri); QString tryGetVolumeRealUriFromUri(const QString &uri); void refresh(); Q_SIGNALS: void updateLocationRequest(const QString &uri); void updateRequest(); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private: ComputerModel *m_model; QLocale m_locale; QCollator m_collator; }; #endif // COMPUTERPROXYMODEL_H peony-extensions/peony-extension-computer-view/computer-view/computer-personal-item.h0000664000175000017500000000266615156143137030411 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERPERSONALITEM_H #define COMPUTERPERSONALITEM_H #include "abstract-computer-item.h" class ComputerPersonalItem : public AbstractComputerItem { Q_OBJECT public: explicit ComputerPersonalItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); Type itemType() override {return Personal;} const QString uri() override {return m_uri;} const QString displayName() override; bool hasChildren() override {return !m_parentNode;} void findChildren() override; void clearChildren() override; private: QString m_uri; }; #endif // COMPUTERPERSONALITEM_H peony-extensions/peony-extension-computer-view/computer-view/computer-user-share-item.h0000664000175000017500000000530215156143137030632 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: dingjing * */ #ifndef COMPUTERUSERSHAREITEM_H #define COMPUTERUSERSHAREITEM_H #include #include "abstract-computer-item.h" #include #include class ComputerUserShareItem : public AbstractComputerItem { Q_OBJECT friend void query_file_info_async_callback(GFile *file, GAsyncResult *res, ComputerUserShareItem* p_this); public: explicit ComputerUserShareItem(GVolume *volume, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); ~ComputerUserShareItem(); void updateInfoAsync(); Type itemType() override {return Volume;} const QString uri() override {return m_uri;} const QString displayName() override {return m_displayName;}; const QIcon icon() override {return m_icon;}; bool isMount() override {return true;}; void findChildren() override {}; void updateInfo() override {updateInfoAsync();} void check() override; bool canEject() override {return false;}; void eject(GMountUnmountFlags) override {}; bool canUnmount() override {return false;}; void unmount(GMountUnmountFlags) override {}; void mount() override {}; QModelIndex itemIndex() override; qint64 usedSpace() override {return m_usedSpace;}; qint64 totalSpace() override {return m_totalSpace;} bool isHidden() override {return m_isHidden;}; protected: private: QString m_uri; QString m_vfs_uri; GFile* m_file; QString m_displayName; QIcon m_icon; qint64 m_totalSpace = 0; qint64 m_usedSpace = 0; bool m_isHidden = false; GCancellable *m_cancellable = nullptr; }; #endif // COMPUTERUSERSHAREITEM_H peony-extensions/peony-extension-computer-view/computer-view/computer-personal-item.cpp0000664000175000017500000000266315156143137030741 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "computer-personal-item.h" #include "computer-model.h" #include ComputerPersonalItem::ComputerPersonalItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { if (!parentNode) { m_uri = "file://" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation); } else { m_uri = uri; } } const QString ComputerPersonalItem::displayName() { //FIXME: return nullptr; } void ComputerPersonalItem::findChildren() { //FIXME: } void ComputerPersonalItem::clearChildren() { //FIXME: } peony-extensions/peony-extension-computer-view/computer-view/computer-network-item.cpp0000664000175000017500000001521415156143137030603 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "computer-network-item.h" #include "computer-model.h" #include ComputerNetworkItem::ComputerNetworkItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { m_cancellable = g_cancellable_new(); m_uri = uri; updateInfo(); } ComputerNetworkItem::~ComputerNetworkItem() { g_cancellable_cancel(m_cancellable); g_object_unref(m_cancellable); } const QString ComputerNetworkItem::displayName() { if (m_uri == "network:///") return tr("Network Neighborhood"); return m_displayName; } void ComputerNetworkItem::findChildren() { if (m_uri != "network:///") return; GFile *file = g_file_new_for_uri("network:///"); g_file_enumerate_children_async(file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, m_cancellable, GAsyncReadyCallback(enumerate_async_callback), this); g_object_unref(file); } void ComputerNetworkItem::updateInfo() { GFile *file = g_file_new_for_uri(m_uri.toUtf8().constData()); g_file_query_info_async(file, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, m_cancellable, GAsyncReadyCallback(query_info_async_callback), this); g_object_unref(file); } QModelIndex ComputerNetworkItem::itemIndex() { if (!m_parentNode) return m_model->createItemIndex(0, this); return m_model->createItemIndex(m_parentNode->m_children.indexOf(this), this); } void ComputerNetworkItem::reloadDirectory(const QString &uri) { if (m_uri != "network:///") return; m_model->beginResetModel(); for (auto item : m_children) { item->deleteLater(); } m_children.clear(); findChildren(); m_model->endResetModel(); } void ComputerNetworkItem::onFileAdded(const QString &uri) { for (auto item : m_children) { if (item->uri() == uri) return; } m_model->beginInsertItem(itemIndex(), m_children.count()); auto item = new ComputerNetworkItem(uri, m_model, this); m_children<endInsterItem(); } void ComputerNetworkItem::onFileRemoved(const QString &uri) { int row = -1; for (auto item : m_children) { if (item->uri() == uri) { row = m_children.indexOf(item); break; } } if (row < 0) return; m_model->beginRemoveItem(itemIndex(), row); auto item = m_children.takeAt(row); item->deleteLater(); m_model->endRemoveItem(); } void ComputerNetworkItem::onFileChanged(const QString &uri) { for (auto item : m_children) { if (item->uri() == uri) { item->updateInfo(); return; } } } void ComputerNetworkItem::enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this) { GError *err = nullptr; auto enumerator = g_file_enumerate_children_finish(file, res, &err); if (enumerator) { g_file_enumerator_next_files_async(enumerator, 9999, 0, p_this->m_cancellable, GAsyncReadyCallback(find_children_async_callback), p_this); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } } void ComputerNetworkItem::find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerNetworkItem *p_this) { GError *err = nullptr; auto infos = g_file_enumerator_next_files_finish(enumerator, res, &err); GList *l = infos; while (l) { auto info = G_FILE_INFO(l->data); l = l->next; if (!info) continue; auto file = g_file_enumerator_get_child(enumerator, info); if (!file) continue; auto uri = g_file_get_uri(file); if (!uri) continue; p_this->m_model->beginInsertItem(p_this->itemIndex(), p_this->m_children.count()); auto item = new ComputerNetworkItem(uri, p_this->m_model, p_this); p_this->m_children<m_model->endInsterItem(); g_free(uri); g_object_unref(file); } if (infos) g_list_free_full(infos, g_object_unref); if (enumerator) { g_file_enumerator_close(enumerator, nullptr, nullptr); g_object_unref(enumerator); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } if (p_this->m_watcher) { p_this->m_watcher->deleteLater(); } p_this->m_watcher = new Peony::FileWatcher("network:///", p_this); connect(p_this->m_watcher, &Peony::FileWatcher::directoryDeleted, p_this, &ComputerNetworkItem::reloadDirectory); connect(p_this->m_watcher, &Peony::FileWatcher::fileCreated, p_this, &ComputerNetworkItem::onFileAdded); connect(p_this->m_watcher, &Peony::FileWatcher::fileDeleted, p_this, &ComputerNetworkItem::onFileRemoved); connect(p_this->m_watcher, &Peony::FileWatcher::fileChanged, p_this, &ComputerNetworkItem::onFileChanged); p_this->m_watcher->startMonitor(); } void ComputerNetworkItem::query_info_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this) { GError *err = nullptr; GFileInfo *info = g_file_query_info_finish(file, res, &err); if (info) { p_this->m_displayName = g_file_info_get_attribute_string(info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); GIcon *icon = g_file_info_get_icon(info); const gchar * const * names = g_themed_icon_get_names(G_THEMED_ICON(icon)); if (names) { const char *name = *names; if (name) { p_this->m_icon = QIcon::fromTheme(name); } } p_this->m_model->dataChanged(p_this->itemIndex(), p_this->itemIndex()); g_object_unref(info); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } } peony-extensions/peony-extension-computer-view/computer-view/computer-volume-item.cpp0000664000175000017500000014156615156143275030436 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "computer-volume-item.h" #include #include //#include #ifndef KY_UDF_BURN #include #else #include using namespace UdfBurn; #endif #include #include #include #include #include #include "computer-model.h" #include "computer-proxy-model.h" #include "computer-user-share-item.h" #include #include #include #include #include #include #include #include #include #include #ifdef signals #undef signals #endif #ifdef KY_SDK_DISKINFO #include #endif #include ComputerVolumeItem::ComputerVolumeItem(GVolume *volume, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { m_model->beginInsertItem(parentNode->itemIndex(), parentNode->m_children.count()); parentNode->m_children<itemType() != Volume) { m_displayName = tr("Volume"); m_model->endInsterItem(); return; } m_cancellable = g_cancellable_new(); if (!volume) { setProperty("isFileSystem", true); if (qApp->property("isSystemEncrypted").toBool()) { setProperty("emblemIconName", "emblem-unlocked"); } m_icon = QIcon::fromTheme("drive-harddisk-system"); m_uri = "file:///"; m_displayName = tr("System Disk"); auto file = g_file_new_for_uri("file:///"); g_file_query_filesystem_info_async(file, "*", 0, m_cancellable, GAsyncReadyCallback(query_root_info_async_callback), this); m_model->endInsterItem(); return; } m_volume = std::make_shared(volume, true); m_volumeChangedHandle = g_signal_connect(volume, "changed", G_CALLBACK(volume_changed_callback), this); m_volumeRemovedHandle = g_signal_connect(volume, "removed", G_CALLBACK(volume_removed_callback), this); m_volumeMonitor = g_volume_monitor_get(); m_mountChangedHandle = g_signal_connect(m_volumeMonitor, "mount_changed", G_CALLBACK(mount_changed_callback), this); m_mountAddedHandle = g_signal_connect(m_volumeMonitor, "mount_added", G_CALLBACK(mount_added_callback), this); updateInfoAsync(); m_model->endInsterItem(); } //ComputerVolumeItem::ComputerVolumeItem(const QString uri,ComputerModel *model,AbstractComputerItem *parentNode,QObject *parent) : AbstractComputerItem(model,parentNode,parent){ // if(uri.isNull() || uri.isEmpty()) // return; // //collectInfoWhenGpartedOpen(uri); //} ComputerVolumeItem::~ComputerVolumeItem() { if(m_volumeMonitor){ g_signal_handler_disconnect(g_volume_monitor_get(), m_mountChangedHandle); g_signal_handler_disconnect(g_volume_monitor_get(), m_mountAddedHandle); } if(m_volume){ g_signal_handler_disconnect(m_volume->getGVolume(), m_volumeChangedHandle); g_signal_handler_disconnect(m_volume->getGVolume(), m_volumeRemovedHandle); } g_cancellable_cancel(m_cancellable); g_object_unref(m_cancellable); if(m_watcher){ m_watcher->stopMonitor(); delete m_watcher; } } QString ComputerVolumeItem::getDevicePath(GVolume* volume) { char *deviceName; QString unixDeviceName; deviceName = g_volume_get_identifier(volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); if(deviceName){ unixDeviceName = QString(deviceName); g_free(deviceName); } return unixDeviceName; } void ComputerVolumeItem::updateInfoAsync() { if (!m_volume) { m_icon = QIcon::fromTheme("drive-harddisk-system"); m_uri = "file:///"; m_displayName = tr("System Disk"); auto file = g_file_new_for_uri("file:///"); g_file_query_filesystem_info_async(file, "*", 0, m_cancellable, GAsyncReadyCallback(query_root_info_async_callback), this); return; } char *deviceName; m_displayName = m_volume->name(); //Handle the Chinese name of fat32 udisk. deviceName = g_volume_get_identifier(m_volume->getGVolume(),G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); if(deviceName){ m_unixDeviceName = QString(deviceName); qDebug()<<"unix Device Name"<< m_unixDeviceName; Peony::FileUtils::handleVolumeLabelForFat32(m_displayName, m_unixDeviceName); g_free(deviceName); } //fix u-disk show as hard-disk icon issue, task#25343 updateBlockIcons(); //qDebug()<getGVolume()); if (mount) { m_mount = std::make_shared(mount, true); auto active_root = g_mount_get_root(mount); if (active_root) { auto uri = g_file_get_uri(active_root); if (uri) { m_uri = uri; g_free(uri); } if (m_uri == "file:///data") { if (Peony::FileUtils::isFileExsit("file:///data/usershare")) { m_isHidden = true; } } g_file_query_filesystem_info_async(active_root, "*", 0, m_cancellable, GAsyncReadyCallback(query_info_async_callback), this); g_object_unref(active_root); } } else { //m_mount = nullptr; //mount first //FIXME: check auto mount // this->mount(); } bool isData = false; Peony::GlobalFstabData *globalFstabData = Peony::GlobalFstabData::getInstance(); if(!globalFstabData->getUuidState()){ if(globalFstabData->isMountPoints(m_unixDeviceName.toUtf8(), "/data")){ isData = true; } }else{ if(globalFstabData->isMountPoints(getDeviceUUID(m_unixDeviceName.toUtf8()), "/data")){ isData = true; } } if(m_uri == "file:///data" || isData){ m_displayName = tr("Data"); } auto index = this->itemIndex(); m_model->dataChanged(index, index); m_model->invalidateRequest(); } const QString ComputerVolumeItem::displayName() { return m_displayName; } const QIcon ComputerVolumeItem::icon() { return m_icon.isNull()? AbstractComputerItem::icon(): m_icon; } bool ComputerVolumeItem::isMount() { return Peony::FileUtils::isMountPoint(m_uri); } void ComputerVolumeItem::findChildren() { //add root auto root = new ComputerVolumeItem(nullptr, m_model, this); //enumerate auto volume_monitor = g_volume_monitor_get(); auto current_volumes = g_volume_monitor_get_volumes(volume_monitor); GList *l = current_volumes; while (l) { auto volume = G_VOLUME(l->data); QString devicePath = getDevicePath(volume); if (!devicePath.isEmpty()) { bool isVfatLoop = IMAGE_MOUNT_MANAGER->isVfatPartition(devicePath); qDebug() << "findChildren()" << __LINE__ << devicePath << "isVfatLoop:" << isVfatLoop; if (isVfatLoop) { l = l->next; continue; } } auto item = new ComputerVolumeItem(volume, m_model, this); l = l->next; } //comment gparted process code to fix duplicated volume issue, bug#41623 // if(!current_volumes) // findChildrenWhenGPartedOpen(); //monitor auto volumeManager = Peony::VolumeManager::getInstance(); connect(volumeManager, &Peony::VolumeManager::volumeAdded, this, &ComputerVolumeItem::onVolumeAdded); //watcher // m_watcher = new Peony::FileWatcher("computer:///",this); // connect(m_watcher, &Peony::FileWatcher::fileCreated, this, &ComputerVolumeItem::onFileAdded); // connect(m_watcher, &Peony::FileWatcher::fileDeleted, this, &ComputerVolumeItem::onFileRemoved); // m_watcher->startMonitor(); if (Peony::FileUtils::isFileExsit("file:///data/usershare")) { new ComputerUserShareItem(nullptr, m_model, this); } } void ComputerVolumeItem::check() { if (!m_volume) return; auto active_root = g_volume_get_activation_root(m_volume->getGVolume()); if (active_root) { auto uri = g_file_get_uri(active_root); auto path = g_file_get_path(active_root); if (QString(uri) == "file:///data") { if (Peony::FileUtils::isFileExsit("file:///data/usershare")) { m_isHidden = true; } } //QMessageBox::information(0, 0, QString("%1 has active root %2").arg(m_displayName).arg(uri)); qDebug() << "ComputerVolumeItem uri:" <getGVolume()); if (nullptr != mount) { file = g_mount_get_root (mount); if (nullptr != file) { m_uri = g_file_get_uri(file); } } if (nullptr != file) { g_object_unref(file); } if (nullptr != mount) { g_object_unref (mount); } } } bool ComputerVolumeItem::canEject() { GVolume *gvolume; GDrive *gdrive; bool ejectAble = false; if("file:///" == m_uri || m_volume == nullptr) /*The root File System cannot eject*/ return false; if(NULL != m_volume->getGVolume()){ gvolume = (GVolume*)g_object_ref(m_volume->getGVolume()); gdrive = g_volume_get_drive(gvolume); if(gdrive){ //qDebug() <<"uri ejectAble:"<getGMount())) { if (g_mount_can_eject(g_mount) && !m_unixDeviceName.startsWith("/dev/sd")) {/* 由于要显示异常U盘,U盘弹出用stop */ Peony::SyncThread::notifyUser(ejectNotify); g_mount_eject_with_operation(g_mount, ejectFlag, mount_op, m_cancellable, GAsyncReadyCallback(eject_async_callback), this); } else { auto g_drive = g_mount_get_drive(g_mount); if (!g_drive) return; if (g_drive_can_stop(g_drive)|| (g_drive_is_removable(g_drive) && !m_unixDeviceName.startsWith("/dev/mmc"))){// for mobile harddisk. /* 加"(g_drive_is_removable(m_drive) && !m_device.startsWith("/dev/mmc"))"这个判断是为了解决bug#184111和bug#149182;有些U盘的can-stop为false,其中为一款sd卡的devicename */ Peony::SyncThread::notifyUser(ejectNotify); g_drive_stop(g_mount_get_drive(g_mount), ejectFlag, mount_op, nullptr, GAsyncReadyCallback(stop_async_callback), this);/*m_cancellable改为nullptr,linkto bug145072【HWE】弹出成功后提示操作被取消 */ }else if(g_drive_is_removable(g_drive)){ //fix bug#141782, SD card eject can not recgonize issue g_mount_eject_with_operation(g_mount, ejectFlag, mount_op, m_cancellable, GAsyncReadyCallback(eject_async_callback), this); } g_object_unref(g_drive); } return; } /*If udisk device is unmounted,eject it from here*/ if (m_volume && (g_volume = m_volume->getGVolume())) { if (g_volume_can_eject(g_volume)) { Peony::SyncThread::notifyUser(ejectNotify); g_volume_eject_with_operation(g_volume, ejectFlag, mount_op, m_cancellable, GAsyncReadyCallback(eject_async_callback), this); } else { auto g_drive = g_mount_get_drive(g_mount); if (!g_drive) return; if (g_drive_can_stop(g_drive)){ Peony::SyncThread::notifyUser(ejectNotify); g_drive_stop(g_mount_get_drive(g_mount), ejectFlag, mount_op, nullptr, GAsyncReadyCallback(stop_async_callback), this);/*m_cancellable改为nullptr,linkto bug145072【HWE】弹出成功后提示操作被取消 */ } g_object_unref(g_drive); } } } bool ComputerVolumeItem::canUnmount() { if(m_uri.endsWith(".mount") || m_uri.endsWith(".volume")) return true; if("file:///data" == m_uri) return false; return m_mount != nullptr; } bool ComputerVolumeItem::unMountIsoImage(GMount* gmount) { GVolume *g_volume = nullptr; bool result = false; g_volume = g_mount_get_volume(gmount); if(g_volume) { g_autofree char *device_identifier = g_volume_get_identifier(g_volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); if (device_identifier) { QString devicePath = QString(device_identifier); if(devicePath.startsWith("/dev/loop") && !m_uri.contains("OfflineSource")) { result = IMAGE_MOUNT_MANAGER->unmountImage(devicePath); if (result) { qDebug() << QString("unmount successed, devicePath: %1").arg(devicePath); } else { qDebug() << QString("unmount failed, devicePath: %1").arg(devicePath); } } } g_object_unref(g_volume); } return result; } void ComputerVolumeItem::unmount(GMountUnmountFlags unmountFlag) { GMount *g_mount = nullptr; GFile *file = nullptr; GVolume *g_volume = nullptr; g_autoptr(GMountOperation) mount_op = g_mount_operation_new(); m_vfs_uri = m_model->m_volumeTargetMap.key(m_uri); QString unmountNotify = QObject::tr("It need to synchronize before operating the device,place wait!"); if (!m_vfs_uri.isEmpty()) { file = g_file_new_for_uri(m_vfs_uri.toUtf8().constData()); if(file){ Peony::SyncThread::notifyUser(unmountNotify); g_file_unmount_mountable_with_operation(file,unmountFlag, mount_op,nullptr, GAsyncReadyCallback(unmount_async_callback), this); } g_object_unref(file); } else if (m_mount && (g_mount = m_mount->getGMount())) { Peony::SyncThread::notifyUser(unmountNotify); if (unMountIsoImage(g_mount)) return; g_mount_unmount_with_operation(g_mount, unmountFlag, mount_op, m_cancellable, GAsyncReadyCallback(unmount_async_callback), this); } else if (!m_uri.isEmpty()) { file = g_file_new_for_uri(m_uri.toUtf8().constData()); if(file){ Peony::SyncThread::notifyUser(unmountNotify); g_file_unmount_mountable_with_operation(file,unmountFlag, mount_op,nullptr, GAsyncReadyCallback(unmount_async_callback), this); } g_object_unref(file); } } void ComputerVolumeItem::mount() { if (m_uri == "file:///") { return; } if (m_mount) { auto root = g_mount_get_root(m_mount->getGMount()); if (root) { auto uri = g_file_get_uri(root); if (uri) { m_uri = uri; g_free(uri); } g_file_query_filesystem_info_async(root, "*", 0, m_cancellable, GAsyncReadyCallback(query_info_async_callback), this); g_object_unref(root); } } else { auto volume = Experimental_Peony::Volume(m_volume->getGVolume()); volume.mount(); // g_autoptr (GMountOperation) op = g_mount_operation_new(); // g_volume_mount(m_volume->getGVolume(), // G_MOUNT_MOUNT_NONE, // op, // m_cancellable, // GAsyncReadyCallback(mount_async_callback), // this); } } QModelIndex ComputerVolumeItem::itemIndex() { if (!m_parentNode) { return m_model->createItemIndex(0, this); } else { return m_model->createItemIndex(m_parentNode->m_children.indexOf(this), this); } } bool ComputerVolumeItem::isHidden() { return m_isHidden; } QString ComputerVolumeItem::getDeviceUUID(const char *device) { struct stat statbuf; if (stat (device, &statbuf) != 0) { return nullptr; } g_autoptr(UDisksClient) client = udisks_client_new_sync (NULL,NULL); g_autoptr(UDisksBlock) block = udisks_client_get_block_for_dev(client, statbuf.st_rdev); if (!block) return nullptr; const gchar *uuid = udisks_block_get_id_uuid(block); return uuid; } void ComputerVolumeItem::volume_changed_callback(GVolume *volume, ComputerVolumeItem *p_this) { //QMessageBox::information(0, 0, tr("Volume Changed")); if(!p_this) return; p_this->m_mount = nullptr; p_this->m_uri = nullptr; p_this->m_icon = QIcon(); p_this->m_displayName = nullptr; p_this->m_usedSpace = 0; p_this->m_totalSpace = 0; p_this->updateInfo(); } void ComputerVolumeItem::volume_removed_callback(GVolume *volume, ComputerVolumeItem *p_this) { auto parentNode = p_this->m_parentNode; if (!parentNode) return; auto row = parentNode->m_children.indexOf(p_this); parentNode->m_model->beginRemoveItem(parentNode->itemIndex(), row); parentNode->m_children.removeAt(row); p_this->deleteLater(); parentNode->m_model->endRemoveItem(); } void ComputerVolumeItem::mount_changed_callback(GVolumeMonitor *volumeMonitor, GMount *gmount, ComputerVolumeItem *p_this) { if (p_this){ p_this->m_usedSpace = 0; p_this->m_totalSpace = 0; p_this->updateInfo(); qDebug()<<"mount changed uri: "<uri()<m_unixDeviceName<m_bMtpQueryInfoFailed; /* hotfix bug#247327 【文件管理器】使用USB连接小米/红米手机,选择传输文件后,计算机界面不显示总容量和已用容量 * hotfix bug#260993 【文件管理器】从任务栏打开文件管理器-计算机,使用USB连接小米手机,选择传输文件后,计算机界面不显示总容量和已用容量 */ if(p_this && p_this->m_unixDeviceName.startsWith("/dev/bus/usb")){ if(p_this->m_totalSpace > 0){ return; } qDebug()<<"mtp mount changed"<m_unixDeviceName<uri(); p_this->m_bMtpQueryInfoFailed = true; p_this->updateInfo(); }//end } } void ComputerVolumeItem::mount_added_callback(GVolumeMonitor *volumeMonitor, GMount *gmount, ComputerVolumeItem *p_this) { if (p_this){ p_this->updateInfo(); qDebug()<<"mount added uri: "<uri(); } } QString iconFileFromMountpoint(const QString& mountpoint){ bool isReadOnly; QDir mountDir; QString iconPath; GUnixMountEntry* entry; if(mountpoint.isEmpty()) return iconPath; entry = g_unix_mount_for(mountpoint.mid(7).toUtf8().constData(),NULL); if(entry){ isReadOnly = g_unix_mount_is_readonly(entry); g_unix_mount_free(entry); if(!isReadOnly)//is not a boot disk return iconPath; } mountDir.setPath(mountpoint.mid(7));//remove 'file://' if(mountDir.exists()){ QStringList filters; filters << "*.ico"; mountDir.setNameFilters(filters); QFileInfoList list = mountDir.entryInfoList(); if(0 != list.length()) iconPath = list.at(0).absoluteFilePath(); } return iconPath; } bool isMountReadOnly(const QString& mountpoint) { bool isReadOnly = false; GUnixMountEntry* entry; if(mountpoint.isEmpty() || mountpoint.length() < 8) return false; entry = g_unix_mount_for(mountpoint.mid(7).toUtf8().constData(),NULL); if(entry){ isReadOnly = g_unix_mount_is_readonly(entry); g_unix_mount_free(entry); } qDebug() << "isMountReadOnly:"<m_unixDeviceName.startsWith("/dev/bus/usb") && p_this->m_bMtpQueryInfoFailed){ p_this->m_bMtpQueryInfoFailed = false; }//end char *fs_type = g_file_info_get_attribute_as_string(info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); QString fsType(fs_type); /* *由于对DVD+RW或者是DVD-RW类型的光盘,无法通过cdromdata类获取到使用的容量,所以使用的容量统一使用gio函数获取 */ quint64 used = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_USED); bool bNotDisk = true; //ignore disk update when the disk deviece is busy,related to bug#143293 if (p_this->m_unixDeviceName.startsWith("/dev/sr") && ! Peony::FileUtils::isBusyDevice(p_this->m_unixDeviceName)) { #ifdef KY_UDF_BURN UdfBurn::DataCDROM *cdrom = new UdfBurn::DataCDROM(p_this->m_unixDeviceName); #else Peony::DataCDROM *cdrom = new Peony::DataCDROM(p_this->m_unixDeviceName); #endif if (cdrom) { cdrom->getCDROMInfo(); //p_this->m_usedSpace = used; //used无法正确获取追加刻录后光盘的使用容量,getCDROMUsedCapacity()无法获取可擦除光盘的使用容量 p_this->m_usedSpace = cdrom->getCDROMUsedCapacity(); if((cdrom->getCDROMType()).contains("DVD+RW") || (cdrom->getCDROMType()).contains("DVD-RW")){ if(fsType.toLower().startsWith("iso")){/* "iso9660"文件系统类型"DVD+RW"和"DVD-RW"光盘的使用容量从libkyudfburn中获取,link bug#271455. */ p_this->m_usedSpace = cdrom->getCDROMUsedCapacity(); }else{ p_this->m_usedSpace = used; } } #ifdef KY_SDK_DISKINFO // p_this->m_totalSpace = kdk_udf_get_cdrom_capacity(p_this->m_unixDeviceName.toUtf8().constData()); // p_this->m_usedSpace = kdk_udf_get_cdrom_used_capacity(p_this->m_unixDeviceName.toUtf8().constData()); #else p_this->m_totalSpace = cdrom->getCDROMCapacity(); #endif delete cdrom; cdrom = nullptr; bNotDisk = false; } } //fix block not update volume issue, link to bug#63326 if (bNotDisk || 0 == p_this->m_totalSpace) { quint64 total = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); quint64 free = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); #ifdef KY_SDK_DISKINFO if (!p_this->m_unixDeviceName.isNull() && !p_this->m_unixDeviceName.isEmpty()) { char *tmpTotalSize = kdk_get_hard_disk_size(p_this->m_unixDeviceName.toUtf8().constData()); if (tmpTotalSize) { quint64 totalSize = atoi(tmpTotalSize); std::free(tmpTotalSize); quint64 freeSize = kdk_get_partition_available_space(p_this->m_unixDeviceName.toUtf8().constData()); if (totalSize > 0 && freeSize > 0) { totalSize = totalSize * 1024 * 1024; freeSize = freeSize * 1024 * 1024; total = totalSize; free = freeSize; } } } #endif if (total > 0 && (used > 0 || free > 0)) { if (used > 0 && used <= total) { p_this->m_usedSpace = used; p_this->m_totalSpace = total; } else if(free > 0 && free <= total) { p_this->m_usedSpace = total - free; p_this->m_totalSpace = total; } p_this->updateBlockIcons(); } if (fsType.contains("ext")) { p_this->m_usedSpace = total - free; } } qWarning()<<"udisk name"<m_volume->name(); qWarning()<<"udisk used space"<m_usedSpace; qWarning()<<"udisk total space"<m_totalSpace; /***************************collect info when gparted open*************************/ // if(p_this->m_icon.name().isEmpty()){ // QString iconName = Peony::FileUtils::getFileIconName(p_this->m_uri); // if(iconName.isNull()) /*Some startup disks cannot get the icon*/ // iconName = "drive-harddisk-usb"; // p_this->m_icon = QIcon::fromTheme(iconName); // } // if(p_this->m_displayName.isEmpty()){ // p_this->m_displayName = Peony::FileUtils::getFileDisplayName(p_this->m_uri); // if(!p_this->m_targetUri.isEmpty()){ // char *realMountPoint = g_filename_from_uri(p_this->m_targetUri.toUtf8().constData(),NULL,NULL); // const char *unixDev = Peony::VolumeManager::getUnixDeviceFileFromMountPoint(realMountPoint); // QString unixDeviceName = unixDev; // Peony::FileUtils::handleVolumeLabelForFat32(p_this->m_displayName,unixDeviceName); // g_free(realMountPoint); // } // } /**********************************************************************************/ auto index = p_this->itemIndex(); p_this->m_model->dataChanged(index, index); //p_this->m_model->dataChanged(p_this->itemIndex(), p_this->itemIndex()); g_object_unref(info); } if (err) { /* related bug(304129、247327、260993) */ qDebug()<<"Fail to query filesystem info,error code:"<code<<", error message:"<message; if ((err->code == G_IO_ERROR_CANCELLED) && (QString(err->message).startsWith("No such interface"))) { if(p_this && p_this->m_bMtpQueryInfoFailed && p_this->m_unixDeviceName.startsWith("/dev/bus/usb")){ qDebug()<<"Failure in query mobile phone information, retrying."; p_this->updateInfoAsync(); } }//end if g_error_free(err); } } void ComputerVolumeItem::query_root_info_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this) { GError *err = nullptr; GFileInfo *info = g_file_query_info_finish(file, res, &err); if (info) { quint64 total = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); quint64 used = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_USED); quint64 available = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); QString unixDevice = getRootUnixDevice(p_this->uri().split("file://").last()); #ifdef KY_SDK_DISKINFO if (!unixDevice.isNull() && !unixDevice.isEmpty()) { char *tmpTotalSize = kdk_get_hard_disk_size(unixDevice.toUtf8().constData()); if (tmpTotalSize) { quint64 totalSize = atoi(tmpTotalSize); free(tmpTotalSize); quint64 freeSize = kdk_get_partition_available_space(unixDevice.toUtf8().constData()); if (totalSize > 0 && freeSize > 0) { totalSize = totalSize * 1024 * 1024; freeSize = freeSize * 1024 * 1024; total = totalSize; available = freeSize; } } } #endif char *fs_type = g_file_info_get_attribute_as_string(info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); QString type(fs_type); if(type.contains("ext")) { used = total - available; } p_this->m_totalSpace = total; p_this->m_usedSpace = used; auto index = p_this->itemIndex(); p_this->m_model->dataChanged(index, index); //p_this->m_model->dataChanged(p_this->itemIndex(), p_this->itemIndex()); g_object_unref(info); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } } void ComputerVolumeItem::mount_async_callback(GVolume *volume, GAsyncResult *res, ComputerVolumeItem *p_this) { GError *err = nullptr; bool successed = g_volume_mount_finish(volume, res, &err); if (err) { //QMessageBox::critical(0, 0, err->message); if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) { Experimental_Peony::Volume v(volume); v.mount(); } g_error_free(err); } if (successed) { QString unmountNotify = QObject::tr("The device has been mount successfully!"); Peony::SyncThread::notifyUser(unmountNotify); p_this->updateInfoAsync(); } } void ComputerVolumeItem::unmount_async_callback(GObject* object,GAsyncResult *res,ComputerVolumeItem *p_this) { GError *err = nullptr; QString errorMsg; bool successed; if(G_IS_MOUNT(object)){ if(successed) p_this->m_mount = nullptr; successed = g_mount_unmount_with_operation_finish(G_MOUNT(object),res,&err); }else if(G_IS_FILE(object)){ successed = g_file_unmount_mountable_with_operation_finish(G_FILE(object),res,&err); } if(successed){ QString unmountNotify = QObject::tr("Data synchronization is complete,the device has been unmount successfully!"); Peony::SyncThread::notifyUser(unmountNotify); // 检查是否需要继续执行完整的弹出操作(针对加密设备) if (p_this->property("pendingFullEject").toBool()) { qDebug() << "unmount_async_callback: Continuing with full device ejection after unmounting encrypted volume"; p_this->setProperty("pendingFullEject", false); // 获取之前保存的ejection flags GMountUnmountFlags ejectFlag = static_cast( p_this->property("ejectionFlags").toInt()); // 延迟一点时间让系统完成卸载操作 QTimer::singleShot(100, [p_this, ejectFlag]() { p_this->eject(ejectFlag); }); } } if(err){ errorMsg = err->message; if(strstr(err->message,"target is busy")){ errorMsg = QObject::tr("One or more programs prevented the unmount operation."); QMessageBox::warning(nullptr, QObject::tr("Unmount failed"), QObject::tr("Error: %1\n").arg(errorMsg), QMessageBox::Yes); }else if(strstr(err->message,"umount: /media/")){ //chinese name need to be converted,this may be a error that from glib2/gio2. errorMsg = QObject::tr("Unable to unmount it, you may need to close some programs, such as: GParted etc."); QMessageBox::warning(nullptr, QObject::tr("Unmount failed"), QObject::tr("%1").arg(errorMsg), QMessageBox::Yes); } else if (err->code == G_IO_ERROR_PERMISSION_DENIED || errorMsg.contains("authorized", Qt::CaseInsensitive)) { // do nothing because we have requested polkit dialog yet. } else { QMessageBox::warning(nullptr, QObject::tr("Unmount failed"), QObject::tr("Error: %1\n").arg(err->message), QMessageBox::Yes); } g_error_free(err); } } void ComputerVolumeItem::eject_async_callback(GObject *object, GAsyncResult *res, ComputerVolumeItem *p_this) { GError *err = nullptr; bool successed; if(G_IS_MOUNT(object)) successed = g_mount_eject_with_operation_finish(G_MOUNT(object), res, &err); else if(G_IS_VOLUME(object)) successed = g_volume_eject_with_operation_finish(G_VOLUME(object), res, &err); if (successed) { //QMessageBox::information(0, 0, "Volume Ejected"); QString unmountNotify = QObject::tr("Data synchronization is complete,the device has been unmount successfully!"); Peony::SyncThread::notifyUser(unmountNotify); } if (err) { qDebug()<<"error code of drive stop:"<code<<", error message:"<message; QString errMsg = err->message; if (!errMsg.contains("authorized", Qt::CaseInsensitive)) { //fix bug#104482, pull device immediately when eject it, error message not translated issue if (err->code == G_IO_ERROR_FAILED){ errMsg = QObject::tr("Eject device failed, the reason may be that the device has been removed, etc."); } QMessageBox warningBox(QMessageBox::Warning, QObject::tr("Eject failed"), errMsg, QMessageBox::Ok); warningBox.exec(); } g_error_free(err); } } void ComputerVolumeItem::stop_async_callback(GDrive *drive, GAsyncResult *res, ComputerVolumeItem *p_this) { GError *err = nullptr; bool successed; successed = g_drive_stop_finish(drive, res, &err); if (!successed) { qDebug()<<"error code of drive stop:"<code<<", error message:"<message; if(!strcmp(err->message, "Not authorized to perform operation")){/* gmountOperation会弹出授权框,防止二次弹框,linkto bug#101075 */ g_error_free(err); return; } //fix bug#104482, pull device immediately when eject it, error message not translated issue QString errMessage = err->message; if (err->code == G_IO_ERROR_FAILED){ errMessage = QObject::tr("Eject device failed, the reason may be that the device has been removed, etc."); } QMessageBox warningBox(QMessageBox::Warning, QObject::tr("Eject failed"), errMessage, QMessageBox::Ok); warningBox.exec(); g_error_free(err); } else { QString ejectNotify = QObject::tr("Data synchronization is complete,the device has been unmount successfully!"); Peony::SyncThread::notifyUser(ejectNotify); } } void ComputerVolumeItem::onVolumeAdded(const std::shared_ptr volume) { auto g_volume = volume->getGVolume(); QString devicePath = getDevicePath(g_volume); if (!devicePath.isEmpty()) { bool isVfatLoop = IMAGE_MOUNT_MANAGER->isVfatPartition(devicePath); qDebug() << "onVolumeAdded" << __LINE__ << devicePath << "isVfatLoop:" << isVfatLoop; if (isVfatLoop) { return; } } if (Peony::FileUtils::isMountMatchFstab(g_volume, "/backup")) { return; } auto item = new ComputerVolumeItem(g_volume, m_model, this); } /* * 根据反馈的bug做逻辑调整: * 1.默认使用 m_volume->iconName() 图标; * 2.只读系统,系统盘优先使用gmount icon,关联bug#120459(删掉第二条,系统盘图标不特殊处理,不使用gmount icon,linkto bug#174770) * 3.需要区分部分型号的U盘显示成移动硬盘图标问题,关联bug#182809, #57660, #70014 #96652 * 4.在Windows系统使用工具,比如软碟通制作的系统盘,不是只读格式,获取iconName后缀带'.ico'做判断 * */ void ComputerVolumeItem::updateBlockIcons() { //qDebug() << "updateBlockIcons volume icon:"<iconName()<iconName()); // 显示加密分区角标 GVolume *volume = m_volume->getGVolume(); if (volume) { g_autoptr (GIcon) volumeIcon = g_volume_get_icon(volume); if (G_IS_EMBLEMED_ICON(volumeIcon)) { GEmblemedIcon *emblemedIcon = G_EMBLEMED_ICON (volumeIcon); GList *l = g_emblemed_icon_get_emblems(emblemedIcon); if (g_list_length (l) > 0) { GEmblem *emblem = G_EMBLEM (l->data); GIcon *emblemIcon = g_emblem_get_icon(emblem); QString emblemIconName = Peony::FileUtils::getIconStringFromGIcon(emblemIcon); setProperty("emblemIconName", emblemIconName); } else { setProperty("emblemIconName", QVariant()); } } else { setProperty("emblemIconName", QVariant()); } } GDrive* gdrive = g_volume_get_drive(m_volume->getGVolume()); QString tmpDevice; if(gdrive){ g_autofree char* gdevice = g_drive_get_identifier(gdrive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); tmpDevice = gdevice; g_object_unref(gdrive); } //iso image files are mounted on loop devices, always use the "media-optical" icon, link to #358341 if (m_unixDeviceName.startsWith("/dev/loop")) { m_icon = QIcon::fromTheme("media-optical"); return; } if(tmpDevice.startsWith("/dev/sr") && m_volume->iconName().startsWith("media-optical")){/* 规范光盘图标,与竞品对比,光盘图标使用的是"media-optical",linkto bug#174770、#120459 */ m_icon = QIcon::fromTheme("media-optical"); return; } //fix bug#182809, U disk show mobile-SSD icon issue, only readonly device use gmount icon if (m_volume->iconName() == "drive-harddisk-usb"){ //用设备容量区分U盘和移动硬盘,大于128GiB为移动硬盘 //FIXME with a better solution, fix bug#57660, #70014 #96652 double size = 0.0; if(!tmpDevice.isEmpty()){ size = Peony::FileUtils::getDeviceSize(tmpDevice.toUtf8().constData()); } if (m_totalSpace/(1024 * 1024 *1024) > 128 || size > 128) m_icon = QIcon::fromTheme("drive-harddisk-usb"); else m_icon = QIcon::fromTheme("drive-removable-media-usb"); } } /* Usage scenarios: * -> open Peony first,then open Gparted. * -> when computer:///xxx.mount or computer:///xxx.volume is generated, * it will query their information from here. */ //comment gparted process code to fix duplicated volume issue, bug#41623 //void ComputerVolumeItem::collectInfoWhenGpartedOpen(QString uri) //{ // GFile *file = NULL; // m_uri = uri; // m_displayName = ""; // m_icon = QIcon(); // m_cancellable = g_cancellable_new(); // m_targetUri = Peony::FileUtils::getTargetUri(m_uri); // file = g_file_new_for_uri(m_targetUri.toUtf8().constData()); // g_file_query_filesystem_info_async(file,"*",0, // m_cancellable, // GAsyncReadyCallback(qeury_info_async_callback),this); // g_object_unref(file); //} /* monitor file generation for computer:///xxx.mount or computer:///xxx.volume */ //void ComputerVolumeItem::onFileAdded(const QString &uri) //{ // QString targetUri = Peony::FileUtils::getTargetUri(uri); // if(!targetUri.contains("file:///") || targetUri.isEmpty()) // return; // for (auto item : m_children) { // qDebug() << "ComputerVolumeItem onFileAdded uri:"<uri(); // if (item->uri() == uri) // return; // } // m_model->beginInsertItem(itemIndex(), m_children.count()); // auto item = new ComputerVolumeItem(uri, m_model, this); // m_children<endInsterItem(); //} //void ComputerVolumeItem::onFileRemoved(const QString &uri) //{ // int row = -1; // for (auto item : m_children) { // qDebug() << "ComputerVolumeItem onFileRemoved uri:"<uri(); // if (item->uri() == uri) { // row = m_children.indexOf(item); // break; // } // } // if (row < 0) // return; // m_model->beginRemoveItem(itemIndex(), row); // auto item = m_children.takeAt(row); // item->deleteLater(); // m_model->endRemoveItem(); //} /* Usage scenarios: * -> open Gparted first,then open Peony. * -> when Gparted is open,it is used to find all partition devices * except the root partition. */ //void ComputerVolumeItem::findChildrenWhenGPartedOpen() //{ // GFile *file; // file = g_file_new_for_uri("computer:///"); // m_tmpCancellable = g_cancellable_new(); // g_file_enumerate_children_async(file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, // m_tmpCancellable, GAsyncReadyCallback(enumerate_async_callback), this); // //g_object_unref(file); //} //void ComputerVolumeItem::enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this) //{ // GError *err = nullptr; // auto enumerator = g_file_enumerate_children_finish(file, res, &err); // if (enumerator) { // g_file_enumerator_next_files_async(enumerator, 9999, 0, p_this->m_tmpCancellable, // GAsyncReadyCallback(find_children_async_callback), p_this); // } // if (err) // g_error_free(err); //} //void ComputerVolumeItem::find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerVolumeItem *p_this) //{ // GError *err = nullptr; // auto infos = g_file_enumerator_next_files_finish(enumerator, res, &err); // GList *l = infos; // while (l) { // auto info = G_FILE_INFO(l->data); // l = l->next; // if (!info) // continue; // auto file = g_file_enumerator_get_child(enumerator, info); // if (!file) // continue; // auto uri = g_file_get_uri(file); // if (!uri) // continue; // //not include ftp://xxx etc. // QString targetUri; // targetUri = Peony::FileUtils::getTargetUri(uri); // if(targetUri.isEmpty()) // continue; // if("file:///" == targetUri) // continue; // if(!targetUri.contains("file:///")) // continue; // p_this->m_model->beginInsertItem(p_this->itemIndex(), p_this->m_children.count()); // auto item = new ComputerVolumeItem(uri, p_this->m_model, p_this); // qDebug() << "find_children_async_callback uri:" <m_children<m_model->endInsterItem(); // g_free(uri); // g_object_unref(file); // } // if (infos) // g_list_free_full(infos, g_object_unref); // if (enumerator) { // g_file_enumerator_close(enumerator, nullptr, nullptr); // g_object_unref(enumerator); // } // if(p_this->m_tmpCancellable){ // g_cancellable_cancel(p_this->m_tmpCancellable); // g_object_unref(p_this->m_tmpCancellable); // } // if (err) { // g_error_free(err); // } //} quint64 calcVolumeCapacity(ComputerVolumeItem* pThis) { char* tmpDevice; GVolume* gVolume; quint64 capacityBytes; QString unixDevice,dbusPath; if(!pThis->m_mount && pThis->m_targetUri.isEmpty()) return 0; if(pThis->m_mount){ if(gVolume = pThis->m_volume->getGVolume()){ tmpDevice = g_volume_get_identifier(gVolume,G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); if (tmpDevice) { unixDevice = QString(tmpDevice+5); //fix crash issue if (tmpDevice) g_free(tmpDevice); } else { unixDevice = Peony::FileUtils::getUnixDevice(pThis->m_uri); unixDevice = unixDevice.section('/',-1); } } }else{ unixDevice = Peony::FileUtils::getUnixDevice(pThis->m_uri); unixDevice = unixDevice.section('/',-1); } if (unixDevice.isEmpty()) { return 0; } dbusPath = "/org/freedesktop/UDisks2/block_devices/" + unixDevice; QDBusInterface blockInterface("org.freedesktop.UDisks2", dbusPath, "org.freedesktop.UDisks2.Block", QDBusConnection::systemBus()); capacityBytes = 0; if(blockInterface.isValid()) capacityBytes = blockInterface.property("Size").toULongLong(); return capacityBytes; } bool ComputerVolumeItem::tryStopDrive(GMountUnmountFlags ejectFlag, GMountOperation *mount_op) { bool stopped = false; qDebug() << "tryStopDrive: Starting for device" << m_unixDeviceName << "uri:" << m_uri; if (m_unixDeviceName.startsWith("/dev/sr")) { return false; } // 检查是否为加密设备 bool isEncryptedAndMounted = false; if ((m_unixDeviceName.startsWith("/dev/dm-") || m_unixDeviceName.contains("dm-"))) { isEncryptedAndMounted = true; qDebug() << "tryStopDrive: Detected mounted encrypted device (dm device)"; } // 对于已挂载的加密设备,先卸载 if (isEncryptedAndMounted) { qDebug() << "tryStopDrive: Attempting to unmount encrypted volume first"; // 设置属性,以便在卸载回调中继续完整弹出流程 setProperty("pendingFullEject", true); setProperty("ejectionFlags", static_cast(ejectFlag)); unmount(ejectFlag); return true; } // 尝试从mount获取drive GDrive *g_drive = nullptr; if (m_mount && m_mount->getGMount()) { g_drive = g_mount_get_drive(m_mount->getGMount()); qDebug() << "tryStopDrive: Got drive from mount:" << (g_drive ? "yes" : "no"); } else { qDebug() << "tryStopDrive: No valid mount available"; } if (!g_drive && m_volume && m_volume->getGVolume()) { g_drive = g_volume_get_drive(m_volume->getGVolume()); qDebug() << "tryStopDrive: Got drive from volume:" << (g_drive ? "yes" : "no"); } else if (!m_volume) { qDebug() << "tryStopDrive: No valid volume available"; } if (g_drive) { bool can_stop = g_drive_can_stop(g_drive); bool is_removable = g_drive_is_removable(g_drive); bool is_mmc = m_unixDeviceName.startsWith("/dev/mmc"); qDebug() << "tryStopDrive: Drive capabilities - can_stop:" << can_stop << "is_removable:" << is_removable << "is_mmc:" << is_mmc; char *drive_name = g_drive_get_name(g_drive); qDebug() << "tryStopDrive: Drive name:" << (drive_name ? drive_name : "unknown"); if (drive_name) g_free(drive_name); qDebug() << "tryStopDrive: Has media:" << g_drive_has_media(g_drive) << "Is media removable:" << g_drive_is_media_removable(g_drive); if (can_stop || (is_removable && !is_mmc)) { qDebug() << "tryStopDrive: Attempting to stop drive"; QString ejectNotify = QObject::tr("It need to synchronize before operating the device,place wait!"); Peony::SyncThread::notifyUser(ejectNotify); g_drive_stop(g_drive, ejectFlag, mount_op, nullptr, GAsyncReadyCallback(stop_async_callback), this); stopped = true; qDebug() << "tryStopDrive: Stop operation initiated"; } else { qDebug() << "tryStopDrive: Drive cannot be stopped with current conditions"; } } else { qDebug() << "tryStopDrive: No drive found for device" << m_unixDeviceName; } if (g_drive) { g_object_unref(g_drive); } qDebug() << "tryStopDrive: Result for" << m_unixDeviceName << ":" << (stopped ? "stop attempted" : "no action taken"); return stopped; } peony-extensions/peony-extension-computer-view/computer-view/computer-model.cpp0000664000175000017500000001751115156143275027263 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include #include "computer-model.h" #include "abstract-computer-item.h" #include "computer-volume-item.h" #include "computer-remote-volume-item.h" #include "computer-network-item.h" #include "file-info.h" #include "file-utils.h" #include "file-operation-utils.h" #include "file-operation-manager.h" #include #include ComputerModel::ComputerModel(QObject *parent) : QAbstractItemModel(parent) { beginResetModel(); m_parentNode = new AbstractComputerItem(this, nullptr, this); auto computerItem = new ComputerVolumeItem(nullptr, this, m_parentNode); computerItem->findChildren(); auto remoteItem = new ComputerRemoteVolumeItem("computer:///", this, m_parentNode); m_parentNode->m_children<findChildren(); // auto networkItem = new ComputerNetworkItem("network:///", this, m_parentNode); // m_parentNode->m_children<findChildren(); connect(Peony::FileOperationManager::getInstance(), &Peony::FileOperationManager::operationFinished, this, &ComputerModel::refresh); endResetModel(); } void ComputerModel::beginInsertItem(const QModelIndex &parent, int row) { beginInsertRows(parent, row, row); } void ComputerModel::endInsterItem() { endInsertRows(); } void ComputerModel::beginRemoveItem(const QModelIndex &parent, int row) { beginRemoveRows(parent, row, row); } void ComputerModel::endRemoveItem() { endRemoveRows(); } QModelIndex ComputerModel::createItemIndex(int row, QObject *item) { return createIndex(row, 0, item); } QModelIndex ComputerModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { auto item = m_parentNode->m_children.at(row); return createIndex(row, column, item); } auto parentNode = static_cast(parent.internalPointer()); if (parentNode->m_children.count() < row) return QModelIndex(); return createIndex(row, column, parentNode->m_children.at(row)); } QModelIndex ComputerModel::parent(const QModelIndex &index) const { auto item = static_cast(index.internalPointer()); if (item->m_parentNode) { return item->m_parentNode->itemIndex(); } return QModelIndex(); } int ComputerModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) return m_parentNode->m_children.count(); auto item = static_cast(parent.internalPointer()); return item->m_children.count(); } int ComputerModel::columnCount(const QModelIndex &parent) const { return 1; } QVariant ComputerModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: return item->displayName(); case Qt::DecorationRole: { if (item->m_parentNode == m_parentNode) return QVariant(); return item->icon(); } case Qt::ToolTipRole: return item->displayName(); default: return QVariant(); } } bool ComputerModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (data(index, role) != value) { // FIXME: Implement me! Q_EMIT dataChanged(index, index, QVector() << role); return true; } return false; } Qt::ItemFlags ComputerModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return QAbstractItemModel::flags(index); // FIXME: Implement me! } bool ComputerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (data->urls().isEmpty()) return false; auto i = index(row, column, parent); if (i.isValid()) { auto item = static_cast(i.internalPointer()); if (!item->uri().isEmpty()) { if (item->canUnmount()) { QStringList sourcesUris; for (auto url : data->urls()) { sourcesUris<uri(), true, action == Qt::CopyAction); connect(operation, &Peony::FileOperation::operationFinished, item, [=](){ if (!operation->hasError()) { Q_EMIT updateLocationRequest(item->uri()); } }); } } } return false; } QString ComputerModel::tryGetVolumeUriFromMountRoot(const QString &mountRootUri) { auto value = m_volumeTargetMap.key(mountRootUri); return value; } QString ComputerModel::tryGetVolumeRealUriFromUri(const QString &uri) { QString realUri = ""; if (uri.isEmpty()) { return realUri; } if (uri == "file:///data" && Peony::FileUtils::isFileExsit("file:///data/usershare")) { return "computer:///ukui-data-volume"; } QList::iterator iter; for(iter = m_volumeRealUri.begin(); iter != m_volumeRealUri.end(); iter++) { realUri = *iter; auto info = Peony::FileInfo::fromUri(realUri); QString targetUri = info->targetUri(); qDebug()<<"real uri"<::iterator iter; for(iter = m_volumeRealUri.begin(); iter != m_volumeRealUri.end(); iter++){ if (*iter == realUri) { return; } } m_volumeRealUri.append(realUri); return; } void ComputerModel::removeRealUri(const QString &realUri) { qDebug()<<"remove volume real uri"<::iterator iter; int index = 0; for(iter = m_volumeRealUri.begin(); iter != m_volumeRealUri.end(); iter++){ if (*iter == realUri) { m_volumeRealUri.removeAt(index); return; } index++; } return; } void ComputerModel::refresh() { for (auto child : m_parentNode->m_children) { for (auto child2 : child->m_children) { if (auto item = qobject_cast(child2)) { item->updateInfo(); } } } } QString getRootUnixDevice(const QString &mountPath) { QString unixDevice; if (mountPath.isEmpty()) { return unixDevice; } GUnixMountEntry* entry = g_unix_mount_at(mountPath.toUtf8().constData(), nullptr); if(!entry) entry = g_unix_mount_for(mountPath.toUtf8().constData(), nullptr); if(!entry) return unixDevice; const char* device = g_unix_mount_get_device_path(entry); unixDevice = device; g_unix_mount_free(entry); return unixDevice; } peony-extensions/peony-extension-computer-view/computer-view/computer-view-style.h0000664000175000017500000000365215156143137027736 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #ifndef COMPUTERVIEWSTYLE_H #define COMPUTERVIEWSTYLE_H #include #include class ComputerViewStyle : public QProxyStyle { public: static ComputerViewStyle *getStyle(); void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override; void drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override; void drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const override; void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const override; private: explicit ComputerViewStyle(QStyle *style = nullptr); ~ComputerViewStyle() override {} }; #endif // COMPUTERVIEWSTYLE_H peony-extensions/peony-extension-computer-view/computer-view/computer-item-delegate.h0000664000175000017500000000403515156143275030333 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERITEMDELEGATE_H #define COMPUTERITEMDELEGATE_H #include class QListView; class QTreeView; class QProgressBar; class AbstractComputerItem; class ComputerItemDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit ComputerItemDelegate(QObject *parent = nullptr); ~ComputerItemDelegate(); protected: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paintVolumeItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const; void paintRemoteItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const; void paintNetworkItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const; void drawTab(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawStyledItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; private: QListView *m_styleIconView; QTreeView *m_styleTreeView; QProgressBar *m_styleProgressBar; }; #endif // COMPUTERITEMDELEGATE_H peony-extensions/peony-extension-computer-view/computer-view/computer-view.pri0000664000175000017500000000161515156143275027143 0ustar fengfengINCLUDEPATH += $$PWD CONFIG += link_pkgconfig PKGCONFIG += gio-unix-2.0 gudev-1.0 HEADERS += \ $$PWD/abstract-computer-item.h \ $$PWD/computer-item-delegate.h \ $$PWD/computer-model.h \ $$PWD/computer-network-item.h \ $$PWD/computer-personal-item.h \ $$PWD/computer-proxy-model.h \ $$PWD/computer-remote-volume-item.h \ $$PWD/computer-view-style.h \ $$PWD/computer-view.h \ $$PWD/computer-user-share-item.h \ $$PWD/computer-volume-item.h SOURCES += \ $$PWD/abstract-computer-item.cpp \ $$PWD/computer-item-delegate.cpp \ $$PWD/computer-model.cpp \ $$PWD/computer-network-item.cpp \ $$PWD/computer-personal-item.cpp \ $$PWD/computer-proxy-model.cpp \ $$PWD/computer-remote-volume-item.cpp \ $$PWD/computer-view-style.cpp \ $$PWD/computer-view.cpp \ $$PWD/computer-user-share-item.cpp \ $$PWD/computer-volume-item.cpp peony-extensions/peony-extension-computer-view/computer-view/computer-view.h0000664000175000017500000000767515156143275026614 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERVIEW_H #define COMPUTERVIEW_H #include #include class ComputerProxyModel; class QRubberBand; class ComputerView : public QAbstractItemView { Q_OBJECT public: explicit ComputerView(QWidget *parent = nullptr); bool eventFilter(QObject *object, QEvent *event); virtual QRect visualRect(const QModelIndex &index) const; virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); virtual QModelIndex indexAt(const QPoint &point) const; virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); virtual int horizontalOffset() const; virtual int verticalOffset() const; virtual bool isIndexHidden(const QModelIndex &index) const; virtual void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); virtual QRegion visualRegionForSelection(const QItemSelection &selection) const; void setModel(QAbstractItemModel *model); QString tryGetVolumeUriFromMountTarget(const QString &mountTargetUri); QString tryGetVolumeRealUriFromUri(const QString &uri); void refresh(); bool getRightDoubleClickState(); void setRightDoubleClickState(bool flag); Q_SIGNALS: void updateLocationRequest(const QString &uri); protected: void updateEditorGeometries(); void updateGeometries(); void resizeEvent(QResizeEvent *event); bool viewportEvent(QEvent *event) override; void paintEvent(QPaintEvent *e); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void mouseDoubleClickEvent(QMouseEvent *event); void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); void rowsInserted(const QModelIndex &parent, int start, int end); void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); void layoutVolumeIndexes(const QModelIndex &volumeParentIndex); void layoutRemoteIndexes(const QModelIndex &remoteParentIndex); void layoutNetworkIndexes(const QModelIndex &networkParentIndex); void adjustLayout(); protected: void doLayout(); public: QTimer *m_touch_active_timer = nullptr; private: ComputerProxyModel *m_model; QRubberBand *m_rubberBand; QPoint m_lastPressedPoint; QPoint m_lastPressedLogicPoint; QRect m_logicRect; bool m_isLeftButtonPressed = false; bool m_isRightButonDClick = false; bool m_isShowNetwork = true; QModelIndex m_hoverIndex; int m_scrollStep = 100; int m_totalHeight = 0; int m_totalWidth = 0; int m_hSpacing = 20; int m_vSpacing = 20; int m_tabPadding = 26; QSize m_volumeItemFixedSize = QSize(256, 108); QSize m_remoteItemFixedSize = QSize(108, 144); QSize m_networkItemFixedSize = QSize(108, 144); QHash m_rect_cache; QTimer *m_timer = nullptr; }; #endif // COMPUTERVIEW_H peony-extensions/peony-extension-computer-view/computer-view/computer-remote-volume-item.h0000664000175000017500000000464115156143137031361 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERREMOTEVOLUMEITEM_H #define COMPUTERREMOTEVOLUMEITEM_H #include #include #include "abstract-computer-item.h" class ComputerRemoteVolumeItem : public AbstractComputerItem { Q_OBJECT public: explicit ComputerRemoteVolumeItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); ~ComputerRemoteVolumeItem(); Type itemType() override {return RemoteVolume;} const QString uri() override {return m_uri;} const QString displayName() override; const QIcon icon() override; void findChildren() override; void updateInfo() override; bool canUnmount() override {return true;} void unmount(GMountUnmountFlags unmountFlag) override; QModelIndex itemIndex() override; bool isHidden() override; public: void onFileAdded(const QString &uri); void onFileRemoved(const QString &uri); void onFileChanged(const QString &uri); protected: //enumeration static void enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerRemoteVolumeItem *p_this); static void find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerRemoteVolumeItem *p_this); //info static void query_info_async_callback(GFile *file, GAsyncResult *res, ComputerRemoteVolumeItem *p_this); //monitor private: QString m_uri; GCancellable *m_cancellable = nullptr; bool m_isUnixDevice = false; bool m_isHidden = true; QString m_displayName; QIcon m_icon; Peony::FileWatcher *m_watcher = nullptr; }; #endif // COMPUTERREMOTEVOLUMEITEM_H peony-extensions/peony-extension-computer-view/computer-view/computer-volume-item.h0000664000175000017500000001144115156143275030067 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERVOLUMEITEM_H #define COMPUTERVOLUMEITEM_H #include "abstract-computer-item.h" #include #include class ComputerVolumeItem : public AbstractComputerItem { Q_OBJECT public: explicit ComputerVolumeItem(GVolume *volume, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); //explicit ComputerVolumeItem(const QString uri,ComputerModel *model,AbstractComputerItem *parentNode,QObject *parent = nullptr); ~ComputerVolumeItem(); void updateInfoAsync(); Type itemType() override {return Volume;} const QString uri() override {return m_uri;} const QString displayName() override; const QIcon icon() override; bool isMount() override; void findChildren() override; void updateInfo() override {updateInfoAsync();} void check() override; bool canEject() override; void eject(GMountUnmountFlags ejectFlag) override; bool canUnmount() override; void unmount(GMountUnmountFlags unmountFlag) override; void mount() override; QModelIndex itemIndex() override; qint64 usedSpace() override {return m_usedSpace;} qint64 totalSpace() override {return m_totalSpace;} bool isHidden() override; QString getDeviceUUID(const char *device); bool tryStopDrive(GMountUnmountFlags ejectFlag, GMountOperation *mount_op); bool unMountIsoImage(GMount* gmount); static QString getDevicePath(GVolume* volume); protected: //monitor static void volume_changed_callback(GVolume *volume, ComputerVolumeItem *p_this); static void volume_removed_callback(GVolume *volume, ComputerVolumeItem *p_this); static void mount_changed_callback(GVolumeMonitor* volumeMonitor, GMount *gmount, ComputerVolumeItem *p_this); static void mount_added_callback(GVolumeMonitor* volumeMonitor, GMount *gmount, ComputerVolumeItem *p_this); //info static void query_info_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this); static void query_root_info_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this); //mount op static void mount_async_callback(GVolume *volume, GAsyncResult *res, ComputerVolumeItem *p_this); static void unmount_async_callback(GObject *object, GAsyncResult *res, ComputerVolumeItem *p_this); static void eject_async_callback(GObject *object, GAsyncResult *res, ComputerVolumeItem *p_this); static void stop_async_callback(GDrive *drive, GAsyncResult *res, ComputerVolumeItem *p_this); //watcher //comment gparted process code to fix duplicated volume issue, bug#41623 // void collectInfoWhenGpartedOpen(QString uri); // void onFileAdded(const QString &uri); // void onFileRemoved(const QString &uri); //gparted //void findChildrenWhenGPartedOpen(); //static void enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this); //static void find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerVolumeItem *p_this); friend quint64 calcVolumeCapacity(ComputerVolumeItem* pThis); private Q_SLOTS: void onVolumeAdded(const std::shared_ptr volume); void updateBlockIcons(); private: QString m_uri; QString m_vfs_uri; std::shared_ptr m_volume = nullptr; std::shared_ptr m_mount = nullptr; GCancellable *m_cancellable = nullptr; GCancellable *m_tmpCancellable = nullptr; GVolumeMonitor *m_volumeMonitor = nullptr; gulong m_volumeChangedHandle = 0; gulong m_volumeRemovedHandle = 0; gulong m_mountChangedHandle = 0; gulong m_mountAddedHandle = 0; //info QString m_displayName; QIcon m_icon; qint64 m_totalSpace = 0; qint64 m_usedSpace = 0; Peony::FileWatcher *m_watcher = nullptr; QString m_targetUri; bool m_isHidden = false; bool m_bMtpQueryInfoFailed = false; /* hotfix bug#247327、260993 手机信息查询,容量显示问题 */ }; #endif // COMPUTERVOLUMEITEM_H peony-extensions/peony-extension-computer-view/computer-view/computer-model.h0000664000175000017500000000534315156143275026730 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERMODEL_H #define COMPUTERMODEL_H #include #include class AbstractComputerItem; QString getRootUnixDevice(const QString &mountPath); class ComputerModel : public QAbstractItemModel { friend class ComputerNetworkItem; friend class ComputerVolumeItem; friend class ComputerRemoteVolumeItem; Q_OBJECT public: explicit ComputerModel(QObject *parent = nullptr); void beginInsertItem(const QModelIndex &parent, int row); void endInsterItem(); void beginRemoveItem(const QModelIndex &parent, int row); void endRemoveItem(); QModelIndex createItemIndex(int row, QObject *item); // Basic functionality: QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // Editable: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); QString tryGetVolumeUriFromMountRoot(const QString &mountRootUri); QString tryGetVolumeRealUriFromUri(const QString &uri); void addRealUri(const QString &realUri); void removeRealUri(const QString &realUri); void refresh(); Q_SIGNALS: void updateLocationRequest(const QString &uri); void invalidateRequest(); void updateRequest(); private: AbstractComputerItem *m_parentNode; QMap m_volumeTargetMap; QList m_volumeRealUri; }; #endif // COMPUTERMODEL_H peony-extensions/peony-extension-computer-view/computer-view/computer-view-style.cpp0000664000175000017500000000712715156143137030272 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: dingjing * */ #include "computer-view-style.h" #include #include #include static ComputerViewStyle *global_instance = nullptr; ComputerViewStyle::ComputerViewStyle(QStyle *style) : QProxyStyle(style) { } ComputerViewStyle *ComputerViewStyle::getStyle() { if (!global_instance) global_instance = new ComputerViewStyle; return global_instance; } void ComputerViewStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == PE_Frame) { return; } return QProxyStyle::drawPrimitive(element, option, painter, widget); } void ComputerViewStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == QStyle::CE_RubberBand) { //qDebug()<<"draw control rubber band"; if (qstyleoption_cast(option)) { auto rect = option->rect; QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight); painter->save(); QColor penColor = highlight; penColor.setAlpha(180); painter->setPen(penColor); QColor dimHighlight(qMin(highlight.red()/2 + 110, 255), qMin(highlight.green()/2 + 110, 255), qMin(highlight.blue()/2 + 110, 255)); dimHighlight.setAlpha(widget && widget->isTopLevel() ? 255 : 80); QLinearGradient gradient(rect.topLeft(), QPoint(rect.bottomLeft().x(), rect.bottomLeft().y())); gradient.setColorAt(0, dimHighlight.lighter(120)); gradient.setColorAt(1, dimHighlight); painter->setRenderHint(QPainter::Antialiasing, true); painter->translate(0.5, 0.5); painter->setBrush(dimHighlight); painter->drawRoundedRect(option->rect.adjusted(0, 0, -1, -1), 1, 1); QColor innerLine = Qt::white; innerLine.setAlpha(40); painter->setPen(innerLine); painter->drawRoundedRect(option->rect.adjusted(1, 1, -2, -2), 1, 1); painter->restore(); } return; } QProxyStyle::drawControl(element, option, painter, widget); } void ComputerViewStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const { //qDebug()<<"drawItemPixmap"; QProxyStyle::drawItemPixmap(painter, rect, alignment, pixmap); } void ComputerViewStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const { //qDebug()<<"drawItemText"; QProxyStyle::drawItemText(painter, rect, flags, pal, enabled, text, textRole); } peony-extensions/peony-extension-computer-view/computer-view/computer-view.cpp0000664000175000017500000004506015156143275027135 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "computer-view.h" #include "computer-proxy-model.h" #include "abstract-computer-item.h" #include "computer-item-delegate.h" #include "computer-view-style.h" #include #include #include #include #include #include #include #include #include #include #include #include #include ComputerView::ComputerView(QWidget *parent) : QAbstractItemView(parent) { m_touch_active_timer = new QTimer(this); m_touch_active_timer->setInterval(2000); m_touch_active_timer->setSingleShot(true); setDragDropMode(QAbstractItemView::DropOnly); setItemDelegate(new ComputerItemDelegate(this)); m_model = ComputerProxyModel::globalInstance(); setModel(m_model); setStyle(ComputerViewStyle::getStyle()); model()->sort(0, Qt::AscendingOrder); m_rubberBand = new QRubberBand(QRubberBand::Shape::Rectangle, this); connect(m_model, &ComputerProxyModel::updateLocationRequest, this, &ComputerView::updateLocationRequest); connect(this, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &index){ qDebug()<selectionModel(), &QItemSelectionModel::selectionChanged, this, [=](){ this->viewport()->update(); }); //fix #18184 auto volumeManager = Peony::VolumeManager::getInstance(); connect(volumeManager,&Peony::VolumeManager::volumeRemoved,this,[=](const std::shared_ptr volume){ this->viewport()->update(); }); if (QGSettings::isSchemaInstalled("org.ukui.style")) { adjustLayout(); //font monitor, adjust to font size to fix lauout issue, link to bug#65238 QGSettings *fontSetting = new QGSettings(FONT_SETTINGS, QByteArray(), this); connect(fontSetting, &QGSettings::changed, this, [=](const QString &key){ qDebug() << "fontSetting changed:" << key; if (key == "systemFontSize") { adjustLayout(); } }); } setSelectionMode(QAbstractItemView::ExtendedSelection); this->viewport()->setMouseTracking(true); this->viewport()->installEventFilter(this); m_timer = new QTimer(this); m_timer->setInterval(0); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, [=]{ doLayout(); }); connect(m_model, &ComputerProxyModel::updateRequest, this, [=]{ doLayout(); }); connect(Peony::GlobalSettings::getInstance(), &Peony::GlobalSettings::valueChanged, this, [=](const QString& key){ if (SHOW_NETWORK == key) { doLayout(); } }); } void ComputerView::adjustLayout() { int fontSize = this->font().pointSize(); if (fontSize <= 0) { fontSize = this->font().pixelSize(); } int width = 256 + (fontSize -11) * 64/5; int height = 108 + (fontSize -11) * 36/5; m_volumeItemFixedSize = QSize(width, height); int other_width = 108 + (fontSize -11) * 36/5; int other_height = 144 + (fontSize -11) * 48/5; m_remoteItemFixedSize = QSize(other_width, other_height); m_networkItemFixedSize = QSize(other_width, other_height); } void ComputerView::doLayout() { m_totalHeight = 0; m_totalWidth = this->viewport()->width(); m_rect_cache.clear(); for (int row = 0; row < m_model->rowCount(); row++) { auto index = m_model->index(row, 0); auto item = m_model->itemFromIndex(index); switch (item->itemType()) { case AbstractComputerItem::Volume: { layoutVolumeIndexes(index); break; } case AbstractComputerItem::RemoteVolume: { layoutRemoteIndexes(index); break; } case AbstractComputerItem::Network: { m_isShowNetwork = Peony::GlobalSettings::getInstance()->isExist(SHOW_NETWORK) ? Peony::GlobalSettings::getInstance()->getValue(SHOW_NETWORK).toBool() : true; if (m_isShowNetwork) { layoutNetworkIndexes(index); } break; } default: break; } } //confirm total width for (auto rect : m_rect_cache.values()) { if (rect.right() > m_totalWidth) m_totalWidth = rect.right(); } horizontalScrollBar()->setRange(0, qMax(0, m_totalWidth - viewport()->width())); verticalScrollBar()->setRange(0, qMax(0, (m_totalHeight - viewport()->height() + 200)/m_scrollStep)); //update tab index rect width for (auto index : m_rect_cache.keys()) { if (!index.parent().isValid()) { auto rect = m_rect_cache.value(index); rect.setWidth(m_totalWidth); m_rect_cache.remove(index); m_rect_cache.insert(index, rect); } } viewport()->update(); } bool ComputerView::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::MouseMove) { if (!m_isLeftButtonPressed) { auto pos = mapFromGlobal(QCursor::pos()); auto newIndex = indexAt(pos); if (newIndex != m_hoverIndex) { m_hoverIndex = newIndex; this->viewport()->update(); } } else { m_hoverIndex = QModelIndex(); } } return false; } QRect ComputerView::visualRect(const QModelIndex &index) const { return m_rect_cache.value(index); } void ComputerView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint) { //return; //FIXME: scroll to the index more accurecely. //auto y = m_rect_cache.value(index).y(); //verticalScrollBar()->setValue(y); } QModelIndex ComputerView::indexAt(const QPoint &point) const { auto pos = point + QPoint(horizontalOffset(), verticalOffset()); for (auto index : m_rect_cache.keys()) { auto rect = m_rect_cache.value(index); if (rect.contains(pos) && index.parent().isValid()){ return index; } } return QModelIndex(); } QModelIndex ComputerView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { return QModelIndex(); } int ComputerView::horizontalOffset() const { return horizontalScrollBar()->value()*m_scrollStep; } int ComputerView::verticalOffset() const { return verticalScrollBar()->value()*m_scrollStep; } bool ComputerView::isIndexHidden(const QModelIndex &index) const { return false; } void ComputerView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) { //caculate logic rubber band rect in mouse move event. //qDebug()<<"setSelection"<isVisible()) { auto realRect = m_rubberBand->geometry(); realRect.adjust(horizontalOffset(), verticalOffset(), horizontalOffset(), verticalOffset()); for (auto index : m_rect_cache.keys()) { auto indexRect = m_rect_cache.value(index); if (realRect.contains(indexRect.center()) && index.parent().isValid()) { selectionModel()->select(index, QItemSelectionModel::Select); } else { selectionModel()->select(index, QItemSelectionModel::Deselect); } } } else { auto pos = rect.center(); auto index = indexAt(pos); if (!index.isValid()) { clearSelection(); return; } // if (!selectedIndexes().contains(index)) // selectionModel()->select(index, QItemSelectionModel::SelectCurrent); setCurrentIndex(index); } } QRegion ComputerView::visualRegionForSelection(const QItemSelection &selection) const { return QRegion(); } void ComputerView::setModel(QAbstractItemModel *model) { // if (this->model()) { // disconnect(this->model(), &QAbstractItemModel::rowsAboutToBeRemoved, 0, 0); // disconnect(this->model(), &QAbstractItemModel::rowsInserted, 0, 0); // } // connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ComputerView::rowsAboutToBeRemoved); // connect(model, &QAbstractItemModel::rowsInserted, this, &ComputerView::rowsInserted); QAbstractItemView::setModel(model); } QString ComputerView::tryGetVolumeUriFromMountTarget(const QString &mountTargetUri) { return m_model->tryGetVolumeUriFromMountTarget(mountTargetUri); } QString ComputerView::tryGetVolumeRealUriFromUri(const QString &uri) { return m_model->tryGetVolumeRealUriFromUri(uri); } void ComputerView::refresh() { m_model->refresh(); } bool ComputerView::getRightDoubleClickState() { return m_isRightButonDClick; } void ComputerView::setRightDoubleClickState(bool flag) { m_isRightButonDClick = flag; } void ComputerView::updateEditorGeometries() { QAbstractItemView::updateEditorGeometries(); } void ComputerView::updateGeometries() { doLayout(); } void ComputerView::resizeEvent(QResizeEvent *event) { QAbstractItemView::resizeEvent(event); updateEditorGeometries(); } bool ComputerView::viewportEvent(QEvent *event) { if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); QModelIndex index = indexAt(helpEvent->pos()); if (index.isValid()) { QString itemText = index.data(Qt::ToolTipRole).toString(); QToolTip::showText(helpEvent->globalPos(), itemText); return true; } } return QAbstractItemView::viewportEvent(event); } void ComputerView::paintEvent(QPaintEvent *e) { QPainter p(this->viewport()); p.fillRect(this->rect(), palette().base()); //p.fillRect(QRect(0, 0, m_totalWidth, m_totalHeight), Qt::blue); p.save(); p.translate(-horizontalOffset(), -verticalOffset()); for (auto rect : m_rect_cache) { //qDebug()<paint(&p, opt, index); } p.restore(); } void ComputerView::mousePressEvent(QMouseEvent *event) { bool singleClicked = qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick); if (singleClicked) { if (!m_touch_active_timer->isActive()) { m_touch_active_timer->start(1100); } } if (event->button() == Qt::LeftButton) { m_isLeftButtonPressed = true; m_rubberBand->hide(); m_lastPressedPoint = event->pos(); m_lastPressedLogicPoint = event->pos() + QPoint(horizontalOffset(), verticalOffset()); } else m_rubberBand->hide(); QAbstractItemView::mousePressEvent(event); this->viewport()->update(); } void ComputerView::mouseMoveEvent(QMouseEvent *event) { QAbstractItemView::mouseMoveEvent(event); if (m_isLeftButtonPressed) { auto pos = event->pos(); auto offset = QPoint(horizontalOffset(), verticalOffset()); auto logicPos = pos + offset; QRect logicRect = QRect(logicPos, m_lastPressedLogicPoint); m_logicRect = logicRect.normalized(); int dx = -horizontalOffset(); int dy = -verticalOffset(); auto realRect = m_logicRect.adjusted(dx, dy, dx ,dy); if (!m_rubberBand->isVisible()) m_rubberBand->show(); m_rubberBand->setGeometry(realRect); } else { m_rubberBand->hide(); } } void ComputerView::mouseReleaseEvent(QMouseEvent *event) { m_rubberBand->hide(); m_isLeftButtonPressed = false; QAbstractItemView::mouseReleaseEvent(event); } void ComputerView::mouseDoubleClickEvent(QMouseEvent *event) { if(event->button() == Qt::RightButton){ m_isRightButonDClick = true; }else{ m_isRightButonDClick = false; } QAbstractItemView::mouseDoubleClickEvent(event); } void ComputerView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QAbstractItemView::dataChanged(topLeft, bottomRight, roles); this->viewport()->update(); } void ComputerView::dragEnterEvent(QDragEnterEvent *event) { event->accept(); } void ComputerView::dragMoveEvent(QDragMoveEvent *event) { event->accept(); auto newIndex = indexAt(event->pos()); if (newIndex != m_hoverIndex) { m_hoverIndex = newIndex; this->viewport()->update(); } } void ComputerView::dropEvent(QDropEvent *event) { bool isMoveOp = event->keyboardModifiers() & Qt::ShiftModifier; auto index = indexAt(event->pos()); if (index.isValid()) { m_model->dropMimeData(event->mimeData(), isMoveOp? Qt::MoveAction: Qt::CopyAction, index.row(), index.column(), index.parent()); } } void ComputerView::rowsInserted(const QModelIndex &parent, int start, int end) { m_timer->start(); QAbstractItemView::rowsInserted(parent, start, end); } void ComputerView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { m_timer->start(); QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); } void ComputerView::layoutVolumeIndexes(const QModelIndex &volumeParentIndex) { int rowCount = m_model->rowCount(volumeParentIndex); if (rowCount <= 0) return; //add tab // m_rect_cache.insert(volumeParentIndex, QRect(QPoint(0, m_totalHeight), QSize(this->viewport()->width(), m_tabPadding))); m_totalHeight += m_tabPadding; //layout indexes int tmpX = 0; int maxColumnCount = 0; //if view's width is not enough, expand the m_totalWidth to ensure scroallble if (m_totalWidth < 2 * (m_hSpacing + m_volumeItemFixedSize.width())) { maxColumnCount = 1; } else { maxColumnCount = this->viewport()->width()/(m_hSpacing + m_volumeItemFixedSize.width());// - 1; } int cloumn = 0; for (int row = 0; row < rowCount; row++) { //layout next row if (cloumn > maxColumnCount - 1) { //m_totalWidth = tmpX + m_hSpacing + m_volumeItemFixedSize.width(); cloumn = 1; tmpX = m_hSpacing; m_totalHeight = m_totalHeight + m_volumeItemFixedSize.height() + m_vSpacing; } else { tmpX = m_hSpacing + cloumn*(m_hSpacing+m_volumeItemFixedSize.width()); cloumn++; } auto index = m_model->index(row, 0, volumeParentIndex); m_rect_cache.insert(index, QRect(QPoint(tmpX, m_totalHeight), m_volumeItemFixedSize)); } m_totalHeight = m_totalHeight + m_volumeItemFixedSize.height() + m_vSpacing; } void ComputerView::layoutRemoteIndexes(const QModelIndex &remoteParentIndex) { int rowCount = m_model->rowCount(remoteParentIndex); if (rowCount <= 0) return; //add tab m_rect_cache.insert(remoteParentIndex, QRect(QPoint(0, m_totalHeight), QSize(this->viewport()->width(), m_tabPadding))); m_totalHeight += m_tabPadding + 10; //layout indexes int tmpX = 0; int maxColumnCount = 0; //if view's width is not enough, expand the m_totalWidth to ensure scroallble if (m_totalWidth < 2 * (m_hSpacing + m_remoteItemFixedSize.width())) { //m_totalWidth = m_hSpacing + m_remoteItemFixedSize.width(); maxColumnCount = 1; } else { maxColumnCount = this->viewport()->width()/(m_hSpacing + m_remoteItemFixedSize.width());// - 1; } int cloumn = 0; for (int row = 0; row < rowCount; row++) { //layout next row if (cloumn > maxColumnCount - 1) { //m_totalWidth = tmpX + m_hSpacing + m_volumeItemFixedSize.width(); cloumn = 1; tmpX = m_hSpacing; m_totalHeight = m_totalHeight + m_remoteItemFixedSize.height() + m_vSpacing; } else { tmpX = m_hSpacing + cloumn*(m_hSpacing+m_remoteItemFixedSize.width()); cloumn++; } auto index = m_model->index(row, 0, remoteParentIndex); m_rect_cache.insert(index, QRect(QPoint(tmpX, m_totalHeight), m_remoteItemFixedSize)); } m_totalHeight = m_totalHeight + m_remoteItemFixedSize.height() + m_vSpacing; } void ComputerView::layoutNetworkIndexes(const QModelIndex &networkParentIndex) { int rowCount = m_model->rowCount(networkParentIndex); if (rowCount <= 0) return; //add tab m_rect_cache.insert(networkParentIndex, QRect(QPoint(0, m_totalHeight), QSize(this->viewport()->width(), m_tabPadding))); m_totalHeight += m_tabPadding + 10; //layout indexes int tmpX = 0; int maxColumnCount = 0; //if view's width is not enough, expand the m_totalWidth to ensure scroallble if (m_totalWidth < 2 * (m_hSpacing + m_networkItemFixedSize.width())) { maxColumnCount = 1; } else { maxColumnCount = this->viewport()->width()/(m_hSpacing + m_networkItemFixedSize.width());// - 1; } int cloumn = 0; for (int row = 0; row < rowCount; row++) { //layout next row if (cloumn > maxColumnCount - 1) { //m_totalWidth = tmpX + m_hSpacing + m_volumeItemFixedSize.width(); cloumn = 1; tmpX = m_hSpacing; m_totalHeight = m_totalHeight + m_networkItemFixedSize.height() + m_vSpacing; } else { tmpX = m_hSpacing + cloumn*(m_hSpacing+m_networkItemFixedSize.width()); cloumn++; } auto index = m_model->index(row, 0, networkParentIndex); m_rect_cache.insert(index, QRect(QPoint(tmpX, m_totalHeight), m_networkItemFixedSize)); } m_totalHeight = m_totalHeight + m_networkItemFixedSize.height() + m_vSpacing; } peony-extensions/peony-extension-computer-view/computer-view/computer-item-delegate.cpp0000664000175000017500000003404015156143275030665 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include #include "computer-view.h" #include "computer-proxy-model.h" #include "computer-item-delegate.h" #include "abstract-computer-item.h" #include #include #include #include #include #include #include #include ComputerItemDelegate::ComputerItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { m_styleIconView = new QListView; m_styleIconView->setViewMode(QListView::IconMode); m_styleTreeView = new QTreeView; m_styleProgressBar = new QProgressBar; } ComputerItemDelegate::~ComputerItemDelegate() { m_styleIconView->deleteLater(); m_styleProgressBar->deleteLater(); } void ComputerItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); //use rounded rect primitive in ukui style. if (index.parent().isValid()) { opt.decorationPosition = QStyleOptionViewItem::Top; opt.decorationSize = QSize(64, 64); opt.features = QStyleOptionViewItem::WrapText; opt.viewItemPosition = QStyleOptionViewItem::OnlyOne; opt.displayAlignment = Qt::AlignHCenter|Qt::AlignTop; opt.rect.adjust(2, 2, -2, -2); //opt.features.setFlag(QStyleOptionViewItem::WrapText); } auto view = qobject_cast(parent()); auto model = qobject_cast(view->model()); auto item = model->itemFromIndex(index); if (!item) return; switch (item->itemType()) { case AbstractComputerItem::Volume: paintVolumeItem(painter, opt, index, item); break; case AbstractComputerItem::RemoteVolume: paintRemoteItem(painter, opt, index, item); break; case AbstractComputerItem::Network: paintNetworkItem(painter, opt, index, item); break; default: break; } } void ComputerItemDelegate::paintVolumeItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const { auto opt = option; bool isHover = (option.state & QStyle::State_MouseOver) && (option.state & ~QStyle::State_Selected); bool isSelected = option.state & QStyle::State_Selected; bool enable = option.state & QStyle::State_Enabled; QColor color = option.palette.color(enable? QPalette::Active: QPalette::Disabled, QPalette::NoRole); if (isHover && !isSelected) { color = option.palette.color(QPalette::BrightText); color.setAlpha(8); } else if (!isSelected){ color.setAlpha(0); } // painter->save(); // painter->setRenderHint(QPainter::Antialiasing); // painter->setPen(Qt::transparent); // painter->setBrush(color); // painter->drawRoundedRect(option.rect, 6, 6); // painter->restore(); qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, m_styleTreeView); if (index.parent().isValid()) { painter->save(); painter->setRenderHint(QPainter::Antialiasing); //get pixmap QIcon icon = option.icon; bool enable = option.state & QStyle::State_Enabled; bool selected = option.state &QStyle::State_Selected; QPixmap pix = icon.pixmap(QSize(64, 64), enable? QIcon::Normal: QIcon::Disabled); //draw emblem icon on pixmap if (item->property("emblemIconName").isValid()) { QString emblemIconName = item->property("emblemIconName").toString(); QIcon emblemIcon = QIcon::fromTheme(emblemIconName); QPixmap overpix = emblemIcon.pixmap(QSize(30, 30)); QPainter emblem_painter(&pix); emblem_painter.drawPixmap(34, 34, 30, 30, overpix); emblem_painter.end(); } //draw pixmap qApp->style()->drawItemPixmap(painter, option.rect.adjusted(5, 0, 0, 0), Qt::AlignVCenter|Qt::AlignLeft, pix); int textOffetX1 = 84; int textOffetX2 = -5; int progressX1 = 0; int progressX2 = -10; if (opt.direction == Qt::RightToLeft) { textOffetX1= 5; textOffetX2 = -84; progressX1 = 10; progressX2 = 0; } //draw text auto textRect = option.rect; textRect.adjust(textOffetX1, 10, textOffetX2, -25); textRect.translate(0, -option.fontMetrics.ascent()); auto elideText = opt.fontMetrics.elidedText(opt.text, Qt::ElideMiddle, textRect.width()); qApp->style()->drawItemText(painter, textRect, Qt::AlignLeft|Qt::AlignVCenter, option.palette, enable, elideText, QPalette::Text); //space bool shouldDrawProgress = false; QString spaceInfo; auto total = item->totalSpace(); auto used = item->usedSpace(); if (total > 0) { char *totalFormat = g_format_size_full(total,G_FORMAT_SIZE_IEC_UNITS); char *usedFormat = g_format_size_full(used,G_FORMAT_SIZE_IEC_UNITS); QString totalFormatString(totalFormat); QString usedFormatString(usedFormat); totalFormatString.replace("iB", "B"); usedFormatString.replace("iB", "B"); spaceInfo = QString("%1/%2").arg(usedFormatString).arg(totalFormatString); g_free(totalFormat); g_free(usedFormat); shouldDrawProgress = true; } else { if (!item->isMount()) { spaceInfo = tr("You should mount volume first"); } } QRect spaceInfoArea = textRect; if (option.fontMetrics.ascent() <= option.font.pointSize() + 4) { spaceInfoArea = textRect.translated(0, 2.6 * option.fontMetrics.ascent()); spaceInfoArea.setHeight (option.fontMetrics.height () + 3.0 * option.fontMetrics.ascent()); } else { spaceInfoArea = textRect.translated(0, 1.5 * option.fontMetrics.ascent()); spaceInfoArea.setHeight (option.fontMetrics.height () + 2.5 * option.fontMetrics.ascent()); } QFontMetrics fontMetris(option.font); spaceInfo = fontMetris.elidedText(spaceInfo, Qt::ElideMiddle, spaceInfoArea.width() * 1.5); qApp->style()->drawItemText(painter, spaceInfoArea, Qt::AlignLeft|Qt::AlignVCenter|Qt::TextWordWrap, option.palette, enable, spaceInfo, QPalette::PlaceholderText); if (shouldDrawProgress) { painter->save(); QRect progressRect = spaceInfoArea.translated (0, spaceInfoArea.height ()); progressRect.adjust (progressX1, -10, progressX2, 0); progressRect.setHeight(10); // QPainterPath clipPath; // clipPath.addRoundedRect(option.rect, 6, 6); // painter->setClipPath(clipPath); qreal percent = used*1.0/total*1.0; int progressBarWidth = progressRect.width() * percent; QStyleOptionProgressBar optBar; optBar.rect = progressRect; optBar.state = opt.state; optBar.palette = qApp->palette(); optBar.minimum = 0; optBar.maximum = 100; optBar.progress = percent * 100; //qApp->style()->drawControl(QStyle::CE_ProgressBar, &optBar, painter); painter->save(); QPen pen; //pen.setColor(percent < 0.8?Qt::blue:Qt::red); pen.setCapStyle(Qt::PenCapStyle::RoundCap); pen.setJoinStyle(Qt::PenJoinStyle::RoundJoin); pen.setWidth(6); QColor color = option.palette.color(QPalette::BrightText); color.setAlpha(8); pen.setColor(color); painter->setPen(pen); //painter->drawLine(progressRect.topLeft(), progressRect.topRight()); if (percent < 0.8) { pen.setColor(option.palette.highlight().color()); m_styleProgressBar->setProperty("setContentBrush", QBrush(option.palette.highlight().color())); } else { pen.setColor(Qt::red); m_styleProgressBar->setProperty("setContentBrush", QBrush(Qt::red)); } painter->setPen(pen); auto pos = progressRect.topLeft(); pos.setX(pos.x() + progressBarWidth); //The percent is too small, resulting in incorrect coordinates. if((option.rect.bottomLeft() + QPoint(8, -5)).x() >= pos.x()){ pos = progressRect.topLeft () + QPoint(9, -5); } //painter->drawLine(progressRect.topLeft (), pos); qApp->style()->drawControl(QStyle::CE_ProgressBar, &optBar, painter, m_styleProgressBar); painter->restore(); painter->restore(); } painter->restore(); } else { //auto textRect = qApp->style()->subElementRect(QStyle::SE_ItemViewItemText, &option, m_styleIconView); drawTab(painter, option, index); } } void ComputerItemDelegate::paintRemoteItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const { if (index.parent().isValid()) { bool isHover = (option.state & QStyle::State_MouseOver) && (option.state & ~QStyle::State_Selected); bool isSelected = option.state & QStyle::State_Selected; bool enable = option.state & QStyle::State_Enabled; QColor color = option.palette.color(enable? QPalette::Active: QPalette::Disabled, QPalette::NoRole); if (isHover && !isSelected) { color = option.palette.color(QPalette::BrightText); color.setAlpha(8); } else if (!isSelected){ color.setAlpha(0); } // painter->save(); // painter->setRenderHint(QPainter::Antialiasing); // painter->setPen(Qt::transparent); // painter->setBrush(color); // painter->drawRoundedRect(option.rect, 6, 6); // painter->restore(); qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, m_styleTreeView); drawStyledItem(painter, option, index); } else { drawTab(painter, option, index); } } void ComputerItemDelegate::paintNetworkItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const { if (index.parent().isValid()) { bool isHover = (option.state & QStyle::State_MouseOver) && (option.state & ~QStyle::State_Selected); bool isSelected = option.state & QStyle::State_Selected; bool enable = option.state & QStyle::State_Enabled; QColor color = option.palette.color(enable? QPalette::Active: QPalette::Disabled, QPalette::NoRole); if (isHover && !isSelected) { color = option.palette.color(QPalette::BrightText); color.setAlpha(8); } else if (!isSelected){ color.setAlpha(0); } // painter->save(); // painter->setRenderHint(QPainter::Antialiasing); // painter->setPen(Qt::transparent); // painter->setBrush(color); // painter->drawRoundedRect(option.rect, 6, 6); // painter->restore(); qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, m_styleTreeView); drawStyledItem(painter, option, index); } else { drawTab(painter, option, index); } } void ComputerItemDelegate::drawTab(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); auto opt = option; auto titleFont = opt.font; if (titleFont.pixelSize() > 0) { titleFont.setPixelSize(titleFont.pixelSize()*1.25); } else { titleFont.setPointSizeF(titleFont.pointSizeF()*1.25); } opt.icon = QIcon(); opt.decorationPosition = QStyleOptionViewItem::Right; opt.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; opt.font = titleFont; opt.fontMetrics = QFontMetrics(opt.font); painter->translate(QPoint(8, 0)); qApp->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter); painter->restore(); //auto textRect = qApp->style()->subElementRect(QStyle::SE_ItemViewItemText, &opt, m_styleIconView); //qApp->style()->drawItemText(painter, opt.rect.adjusted(5, 0, 0, 0), Qt::AlignTop|Qt::AlignVCenter, option.palette, enable, option.text, selected? QPalette::HighlightedText: QPalette::Text); } void ComputerItemDelegate::drawStyledItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); painter->setRenderHint(QPainter::Antialiasing); //draw pix map QIcon icon = option.icon; bool enable = option.state & QStyle::State_Enabled; bool selected = option.state &QStyle::State_Selected; QPixmap pix = icon.pixmap(QSize(64, 64), enable? QIcon::Normal: QIcon::Disabled); qApp->style()->drawItemPixmap(painter, option.rect.adjusted(0, 5, 0, 0), Qt::AlignTop|Qt::AlignHCenter, pix); //draw text auto textRect = option.rect.adjusted(2, 74, -2, -2); auto elideText = option.fontMetrics.elidedText(option.text, Qt::ElideRight, 2 * textRect.width() - 10); qApp->style()->drawItemText(painter, textRect, Qt::ElideRight|Qt::TextWrapAnywhere|Qt::AlignTop|Qt::AlignHCenter, option.palette, enable, elideText, QPalette::Text); painter->restore(); } peony-extensions/peony-extension-computer-view/computer-view/computer-user-share-item.cpp0000664000175000017500000001071115156143275031170 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #include "computer-user-share-item.h" #include "computer-model.h" #include #include #include #ifdef KY_SDK_DISKINFO #include #endif void query_file_info_async_callback(GFile *file, GAsyncResult *res, ComputerUserShareItem* p_this); ComputerUserShareItem::ComputerUserShareItem(GVolume *volume, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { m_cancellable = g_cancellable_new(); m_model->beginInsertItem(parentNode->itemIndex(), parentNode->m_children.count()); parentNode->m_children<property("isDataEncrypted").toBool()) { setProperty("emblemIconName", "emblem-unlocked"); } m_model->endInsterItem(); m_file = g_file_new_for_uri("file:///data"); m_unixDeviceName = getRootUnixDevice(m_uri.split("file://").last()); GFile* file = g_file_new_for_uri("file:///data"); GFileInfo* fileInfo = g_file_query_info(file, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, nullptr); if (g_file_info_get_attribute_boolean(fileInfo, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT)) { updateInfoAsync(); } if (file) g_object_unref(file); if (fileInfo) g_object_unref(fileInfo); Q_UNUSED(volume); } ComputerUserShareItem::~ComputerUserShareItem() { g_cancellable_cancel(m_cancellable); g_object_unref(m_cancellable); if (m_file) g_object_unref(m_file); } void ComputerUserShareItem::updateInfoAsync() { g_file_query_filesystem_info_async(m_file, "filesystem::*", 0, m_cancellable, GAsyncReadyCallback(query_file_info_async_callback), this); } void ComputerUserShareItem::check() { } QModelIndex ComputerUserShareItem::itemIndex() { return m_model->createItemIndex(m_parentNode->m_children.indexOf(this), this); } void query_file_info_async_callback(GFile *file, GAsyncResult *res, ComputerUserShareItem* p_this) { GError *err = nullptr; GFileInfo *info = g_file_query_info_finish(file, res, &err); if (err) { g_error_free(err); return; } if (info) { quint64 total = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); quint64 used = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_USED); quint64 free = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); #ifdef KY_SDK_DISKINFO if (!p_this->m_unixDeviceName.isNull() && !p_this->m_unixDeviceName.isEmpty()) { char *tmpTotalSize = kdk_get_hard_disk_size(p_this->m_unixDeviceName.toUtf8().constData()); if (tmpTotalSize) { quint64 totalSize = atoi(tmpTotalSize); std::free(tmpTotalSize); quint64 freeSize = kdk_get_partition_available_space(p_this->m_unixDeviceName.toUtf8().constData()); if (totalSize > 0 && freeSize > 0) { totalSize = totalSize * 1024 * 1024; freeSize = freeSize * 1024 * 1024; total = totalSize; free = freeSize; } } } #endif char *fs_type = g_file_info_get_attribute_as_string(info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); QString type(fs_type); p_this->m_totalSpace = total; p_this->m_usedSpace = used; if (type.contains("ext")) { p_this->m_usedSpace = total - free; } auto index = p_this->itemIndex(); p_this->m_model->dataChanged(index, index); g_object_unref(info); } } peony-extensions/peony-extension-computer-view/computer-view/computer-network-item.h0000664000175000017500000000432715156143137030253 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERNETWORKITEM_H #define COMPUTERNETWORKITEM_H #include "abstract-computer-item.h" #include #include class ComputerNetworkItem : public AbstractComputerItem { Q_OBJECT public: explicit ComputerNetworkItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); ~ComputerNetworkItem(); Type itemType() override {return Network;} const QString uri() override {return m_uri;} const QString displayName() override; const QIcon icon() override {return m_icon;} void findChildren() override; void updateInfo() override; QModelIndex itemIndex() override; public Q_SLOTS: void reloadDirectory(const QString &uri); void onFileAdded(const QString &uri); void onFileRemoved(const QString &uri); void onFileChanged(const QString &uri); protected: //enumeration static void enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this); static void find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerNetworkItem *p_this); //info static void query_info_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this); private: QString m_uri; QString m_displayName; QIcon m_icon; GCancellable *m_cancellable; Peony::FileWatcher *m_watcher = nullptr; }; #endif // COMPUTERNETWORKITEM_H peony-extensions/peony-extension-computer-view/computer-view/computer-proxy-model.cpp0000664000175000017500000001733015156143275030441 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "computer-proxy-model.h" #include "computer-model.h" #include "abstract-computer-item.h" #include #include #include #include #include static ComputerProxyModel *global_instance = nullptr; static bool getRawDeviceIsEncrypted(const QString &mapperDevice) { if (mapperDevice.startsWith("/dev/dm") || mapperDevice.startsWith("/dev/mapper")) { g_autoptr (GUdevClient) client = g_udev_client_new(nullptr); g_return_val_if_fail(client, false); g_autoptr (GUdevDevice) device = g_udev_client_query_by_device_file(client, mapperDevice.toLocal8Bit().constData()); g_return_val_if_fail(device, false); QString sysfsPath = g_udev_device_get_sysfs_path(device); QString rawDeviceLinkDirPath = sysfsPath + "/slaves"; QDir dir(rawDeviceLinkDirPath); auto results = dir.entryList(QDir::NoDotAndDotDot|QDir::AllEntries); if (!results.isEmpty()) { QString result = results.first(); if (!result.startsWith("dm")) { result.prepend("/dev/"); g_autoptr (GUdevDevice) rootDevice = g_udev_client_query_by_device_file(client, result.toLocal8Bit().constData()); QString fsUsage = g_udev_device_get_property(rootDevice, "ID_FS_USAGE"); if (fsUsage.contains("crypto")) { return true; } else { return false; } } else { qWarning() << "failed to get raw device file for" << result; result.prepend("/dev/"); if (result != mapperDevice) { // 处理嵌套映射场景,如加密分区套lvm逻辑卷的场景,关联bug# return getRawDeviceIsEncrypted(result); } return false; } } return false; } else { return false; } } ComputerProxyModel::ComputerProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { auto systemMount = g_unix_mount_at("/", nullptr); if (systemMount) { QString devPath = g_unix_mount_get_device_path(systemMount); if (devPath.startsWith("/dev/mapper")) { bool isSystemEncrypted = getRawDeviceIsEncrypted(devPath); qApp->setProperty("isSystemEncrypted", isSystemEncrypted); } } auto dataMount = g_unix_mount_at("/data", nullptr); if (dataMount) { QString devPath = g_unix_mount_get_device_path(dataMount); if (devPath.startsWith("/dev/mapper")) { bool isDataEncrypted = getRawDeviceIsEncrypted(devPath); qApp->setProperty("isDataEncrypted", isDataEncrypted); } } setDynamicSortFilter(true); auto computerModel = new ComputerModel(this); m_model = computerModel; setSourceModel(computerModel); m_locale = QLocale(QLocale::system().name()); m_collator = QCollator(m_locale); m_collator.setNumericMode(true); connect(m_model, &ComputerModel::updateLocationRequest, this, &ComputerProxyModel::updateLocationRequest); connect(m_model, &ComputerModel::invalidateRequest, this, &ComputerProxyModel::invalidateFilter); connect(m_model, &ComputerModel::updateRequest, this, &ComputerProxyModel::updateRequest); } ComputerProxyModel *ComputerProxyModel::globalInstance() { if (!global_instance) { global_instance = new ComputerProxyModel; } return global_instance; } AbstractComputerItem *ComputerProxyModel::itemFromIndex(const QModelIndex &proxyIndex) { auto index = mapToSource(proxyIndex); return static_cast(index.internalPointer()); } QString ComputerProxyModel::tryGetVolumeUriFromMountTarget(const QString &mountTargetUri) { return m_model->tryGetVolumeUriFromMountRoot(mountTargetUri); } QString ComputerProxyModel::tryGetVolumeRealUriFromUri(const QString &uri) { return m_model->tryGetVolumeRealUriFromUri(uri); } void ComputerProxyModel::refresh() { m_model->refresh(); } bool ComputerProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { auto item = static_cast(m_model->index(source_row, 0, source_parent).internalPointer()); return !item->isHidden(); } bool ComputerProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { if (!source_left.parent().isValid() && source_right.parent().isValid()) { return sortOrder() == Qt::AscendingOrder ? false : true; } if (!(source_left.isValid() && source_right.isValid())) { return QSortFilterProxyModel::lessThan(source_left, source_right); } auto leftItem = static_cast(source_left.internalPointer()); auto rightItem = static_cast(source_right.internalPointer()); if (leftItem->itemType() != AbstractComputerItem::Volume || rightItem->itemType() != AbstractComputerItem::Volume) { return false; } if (leftItem->uri().compare("file:///") == 0) { return true; } if (rightItem->uri().compare("file:///") == 0) { return false; } if (leftItem->uri().compare("computer:///ukui-data-volume") == 0 || leftItem->uri().compare("file:///data") == 0) { return true; } if (rightItem->uri().compare("computer:///ukui-data-volume") == 0 || rightItem->uri().compare("file:///data") == 0) { return false; } if (!leftItem->canEject() && !leftItem->m_unixDeviceName.startsWith("/dev/bus/usb") && !rightItem->canEject() && !rightItem->m_unixDeviceName.startsWith("/dev/bus/usb")) { return m_collator.compare(leftItem->m_unixDeviceName, rightItem->m_unixDeviceName) < 0; } else if (!leftItem->canEject() && !leftItem->m_unixDeviceName.startsWith("/dev/bus/usb")) { return true; } else if (!rightItem->canEject() && !rightItem->m_unixDeviceName.startsWith("/dev/bus/usb")) { return false; } if ((leftItem->canEject() && !leftItem->m_unixDeviceName.startsWith("/dev/sr")) && (rightItem->canEject() && !rightItem->m_unixDeviceName.startsWith("/dev/sr"))) { return m_collator.compare(leftItem->m_unixDeviceName, rightItem->m_unixDeviceName) < 0; } else if (leftItem->canEject() && !leftItem->m_unixDeviceName.startsWith("/dev/sr")) { return true; } else if (rightItem->canEject() && !rightItem->m_unixDeviceName.startsWith("/dev/sr")) { return false; } if (leftItem->m_unixDeviceName.startsWith("/dev/bus/usb") && rightItem->m_unixDeviceName.startsWith("/dev/bus/usb")) { return m_collator.compare(leftItem->m_unixDeviceName, rightItem->m_unixDeviceName) < 0; } else if (leftItem->m_unixDeviceName.startsWith("/dev/bus/usb")) { return true; } else if (rightItem->m_unixDeviceName.startsWith("/dev/bus/usb")) { return false; } return m_collator.compare(leftItem->m_unixDeviceName, rightItem->m_unixDeviceName) < 0; } peony-extensions/peony-extension-computer-view/computer-view-container.cpp0000664000175000017500000011074215156143275026307 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "computer-view-container.h" #include "computer-view.h" #include "computer-proxy-model.h" #include "abstract-computer-item.h" #include "login-remote-filesystem.h" #include "peony-drive-rename/drive-rename.h" #include "FMWindowIface.h" #include "mount-operation.h" #include #include #include #include #include #include #include #ifndef KY_UDF_BURN #include #include #else #include #include "volumeManager.h" #include "udfAppendBurnDataDialog.h" using namespace UdfBurn; #endif #include #include #include #include #include #include #include #include #include #include static void ask_question_cb(GMountOperation *op, char *message, char **choices, Peony::ComputerViewContainer *p_this); static void ask_password_cb(GMountOperation *op, const char *message, const char *default_user, const char *default_domain, GAskPasswordFlags flags, Peony::ComputerViewContainer *p_this); static GAsyncReadyCallback mount_enclosing_volume_callback(GFile *volume, GAsyncResult *res, Peony::ComputerViewContainer *p_this) { GError *err = nullptr; g_file_mount_enclosing_volume_finish (volume, res, &err); if ((nullptr == err) || (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED))) { qDebug() << "login successful!"; Q_EMIT p_this->updateWindowLocationRequest(p_this->m_remote_uri); } else if (err->message) { qDebug() << "login remote error: " <code<message<domain; QString showMessage(err->message); if (showMessage.startsWith("Message recipient")) { showMessage = QObject::tr("Message recipient disconnected from message bus without replying!"); } QMessageBox::warning(nullptr, QObject::tr("log remote error"), showMessage, QMessageBox::Ok); } if (nullptr != err) { g_error_free(err); } return nullptr; } void aborted_cb(GMountOperation *op, Peony::ComputerViewContainer *p_this) { g_mount_operation_reply(op, G_MOUNT_OPERATION_ABORTED); p_this->disconnect(); } #include "peony-qt/file-enumerator.h" static QString getComputerUriFromUri(const QString &uri){ /* volume item,遍历方式获取uri */ FileEnumerator e; e.setEnumerateDirectory("computer:///"); e.enumerateSync(); QString computerUri = uri; for (auto fileInfo : e.getChildren()) { FileInfoJob infoJob(fileInfo); infoJob.querySync(); /* 由item的uri获取computer uir */ auto info = infoJob.getInfo(); if(fileInfo.get()->targetUri() == uri && !uri.isEmpty()){ computerUri = fileInfo.get()->uri(); break; } } return computerUri; } static QString getComputerUriFromUnixDevice(const QString &unixDevice) { /* volume item,遍历方式获取uri */ Peony::FileEnumerator e; e.setEnumerateDirectory("computer:///"); e.enumerateSync(); QString uri; for (auto fileInfo : e.getChildren()) { Peony::FileInfoJob infoJob(fileInfo); infoJob.querySync(); /* 由volume的unixDevice获取computer uir */ auto info = infoJob.getInfo(); QString device = fileInfo.get()->unixDeviceFile(); if(device==unixDevice){ uri = fileInfo.get()->uri(); break; } } return uri; } Peony::ComputerViewContainer::ComputerViewContainer(QWidget *parent) : DirectoryViewWidget(parent) { setContextMenuPolicy(Qt::CustomContextMenu); m_op = g_mount_operation_new(); g_signal_connect (m_op, "aborted", G_CALLBACK (aborted_cb), this); g_signal_connect (m_op, "ask-question", G_CALLBACK(ask_question_cb), this); g_signal_connect (m_op, "ask-password", G_CALLBACK (ask_password_cb), this); // connect(this, &QWidget::customContextMenuRequested, this, [=](const QPoint &pos){ // auto selectedIndexes = m_view->selectionModel()->selectedIndexes(); // auto index = m_view->indexAt(pos); // if (!selectedIndexes.contains(index)) // m_view->clearSelection(); // if (index.isValid()) { // m_view->selectionModel()->select(index, QItemSelectionModel::SelectCurrent); // } // QMenu menu(this); // auto model = static_cast(m_view->model()); // QStringList uris; // QList items; // for (auto index : m_view->selectionModel()->selectedIndexes()) { // auto item = model->itemFromIndex(index); // uris<uri(); // if (item->uri() == "network:///") // return; // items<exec(); // if (code == QDialog::Rejected) { // return; // } // GFile* m_volume = g_file_new_for_uri(m_dlg->uri().toUtf8().constData()); // m_remote_uri = m_dlg->uri(); // if (nullptr != m_serverLoginDlg) { // delete m_serverLoginDlg; // m_serverLoginDlg = nullptr; // } // m_serverLoginDlg = new ConnectServerLogin(m_remote_uri); // g_file_mount_enclosing_volume(m_volume, G_MOUNT_MOUNT_NONE, m_op, nullptr, GAsyncReadyCallback(mount_enclosing_volume_callback), this); // }); // } else if (items.count() == 1 // // && (items.first()->uri() != "" ||items.first()->m_unixDeviceName.contains("/dev/sr")) // && items.first()->uri() != "network:///" || items.first()->m_unixDeviceName.contains("/dev/sr")) { // //bool isWayland = qApp->property("isWayland").toBool(); // related to #105070 // auto item = items.first(); // bool unmountable = item->canUnmount() && !item->property("isKydrive").toBool(); // bool isReddisk = false; // //fix bug#212689, 212690, 213120, 213121, hide reddisk format and unmount option // if (item->m_unixDeviceName.startsWith("/dev/dm") && QFile::exists("/opt/AQTJ/Client/JC/MAIN/bin/jc_main_ui")) // isReddisk = true; // if(!item->canEject() && ! isReddisk){ // menu.addAction(tr("Unmount"), [=](){ // item->unmount(G_MOUNT_UNMOUNT_NONE); // }); // menu.actions().first()->setEnabled(unmountable); // } // //fix bug#175330, wayland should be the same with mainline version // /* else if (isWayland && ! isReddisk) { // menu.addAction(tr("Unmount"), [=](){ // item->unmount(G_MOUNT_UNMOUNT_NONE); // }); // menu.actions().first()->setEnabled(unmountable); // }*/ // /*eject function for volume. fix #18216*/ // if(item->canEject()){ // auto ejectAction = menu.addAction(tr("Eject"), [=](){ // item->eject(G_MOUNT_UNMOUNT_NONE); // }); // QString unixDevice = item->m_unixDeviceName; // if(unixDevice.contains("/dev/sr")){/* 光盘在刻录数据、镜像等操作时,即若处于busy状态时,将弹出菜单置灰不可用。link to bug#143293. */ // menu.actions().first()->setDisabled(FileUtils::isBusyDevice(unixDevice)); // } // } // auto uri = item->uri(); // QString unixDevice = item->m_unixDeviceName; // if (uri.isEmpty() && !unixDevice.isEmpty()) { // uri = getComputerUriFromUnixDevice(unixDevice); // } // auto realUri = m_view->tryGetVolumeRealUriFromUri(uri); // if (!realUri.isEmpty()) { // uri = realUri; // } // if(uri.startsWith("file://") || !unixDevice.isEmpty()) { // uri = getComputerUriFromUri(uri); // } // auto info = FileInfo::fromUri(uri); // if (info->displayName().isEmpty() || info->targetUri().isEmpty()) { // FileInfoJob j(info); // j.querySync(); // } // auto mount = VolumeManager::getMountFromUri(info->targetUri()); // //fix bug#52491, CDROM and DVD can format issue // //fix bug#140543, remote redirection volume can be format // if (nullptr != mount) { // auto volume = VolumeManager::getVolumeFromMount(mount); // if (mount && volume && ! isReddisk) { // QString unixDevice = FileUtils::getUnixDevice(info->uri()); // if (!unixDevice.isNull() && unixDevice.contains("/dev/sr") // &&!unixDevice.startsWith("/dev/bus/usb") // && info->isVolume()) { // auto fdMenu = menu.addAction(tr("Format")); // fdMenu->setEnabled(false); // #ifndef KY_UDF_BURN // DiscControl *discControl = new DiscControl(unixDevice); // if(discControl->work()){ // connect(discControl, &DiscControl::workFinished, [=](DiscControl *discCtrl){ // connect(fdMenu, &QAction::triggered, [=](){ // UdfFormatDialog *udfFormatDlg = FormatDlgCreateDelegate::getInstance()->createUdfDlg(uri, discCtrl); // udfFormatDlg->show(); // }); // qDebug() << unixDevice << "supportUdf values:" << discCtrl->supportUdf(); // fdMenu->setEnabled(discCtrl->supportUdf()); // }); // } // #else // UdfBurn::DiscControl *discControl = new UdfBurn::DiscControl(unixDevice); // if(discControl->work()){ // connect(discControl, &UdfBurn::DiscControl::workFinished, [=](UdfBurn::DiscControl *discCtrl){ // connect(fdMenu, &QAction::triggered, [=](){ // UdfBurn::UdfFormatDialogWrapper *udfFormatDlg = FormatDlgCreateDelegate::getInstance()->createUdfDlgWrapper(uri, discCtrl); // udfFormatDlg->show(); // }); // qDebug() << unixDevice << "KY_UDF_BURN supportUdf values:" << discCtrl->supportUdf(); // fdMenu->setEnabled(discControl->supportUdf()); // }); // } // #endif // } else if (! unixDevice.isNull() && ! unixDevice.contains("/dev/sr") // &&!unixDevice.startsWith("/dev/bus/usb") // && info->isVolume() && info->canUnmount()/* && info->targetUri() != "file:///data"*/) { // auto fdMenu = menu.addAction(tr("Format"), [=] () { // Format_Dialog *fd = FormatDlgCreateDelegate::getInstance()->createUDiskDlg(uri, nullptr); // fd->show(); // }); // if (! mount) { // fdMenu->setEnabled(false); // } // } // } // } else { // if (!unixDevice.isNull() && !unixDevice.contains("/dev/sr") // && !unixDevice.startsWith("/dev/bus/usb") // && info->isVolume() && !uri.isEmpty()) { // auto fdMenu = menu.addAction(tr("Format"), [=](){ // Format_Dialog *fd = FormatDlgCreateDelegate::getInstance()->createUDiskDlg(uri, nullptr); // fd->show(); // }); // } // } // QStringList fakeUrls; // fakeUrls<getComputerViewMenuPlugins(); // for (auto plugin : menuPlugins) { // menu.addActions(plugin->menuActions(Peony::MenuPluginInterface::SideBar, "computer:///", fakeUrls)); // } // if (qApp->property("deviceRenamePluginLoaded").toBool()) { // // add drive rename action, link to: #105070 // auto driveRenamePlugin = new DriveRename(this); // menu.addActions(driveRenamePlugin->menuActions(Peony::MenuPluginInterface::Type::SideBar, "computer:///", fakeUrls)); // } // if (!item->uri().startsWith("network://")) { // auto a = menu.addAction(tr("Property"), [=]() { // if (uri.isNull()) { // QMessageBox::warning(0, 0, tr("You have to mount this volume first")); // } else { // QProcess p; // p.setProgram("/usr/bin/peony"); // QStringList args; // args << "-p" << uri; // p.setArguments(args); // // p.startDetached(); // p.startDetached(p.program(), p.arguments()); // } // }); // auto targetUri = FileUtils::getTargetUri(info->uri()); // bool isMounted = FileUtils::isFileUnmountable(info->uri()); // if ("file:///" == uri || "file:///" == targetUri || "file:///data" == uri || "computer:///ukui-data-volume" == uri) { // isMounted = true; // } // a->setEnabled(!uri.isNull() && isMounted); // if(item->m_unixDeviceName.contains("/dev/sr")){/* 光盘在刻录数据、镜像等操作时,即若处于busy状态时,该菜单置灰不可用。link to bug#143293. */ // a->setDisabled(FileUtils::isBusyDevice(item->m_unixDeviceName)); // } // } // FMWindowIface *windowIface = dynamic_cast(this->topLevelWidget()); // menu.addAction(tr("Open In New Window"), [=](){ // auto targetUri = info->targetUri(); // if(targetUri.isEmpty() && "computer:///ukui-data-volume" == info->uri()){ // targetUri = "file:///data"; // } // auto newWindow = windowIface->create(targetUri); // dynamic_cast(newWindow)->show(); // }); // if (!qApp->property("tabletMode").toBool()) { // menu.addAction(tr("Open In New Tab"), [=](){ // auto targetUri = info->targetUri(); // if(targetUri.isEmpty() && "computer:///ukui-data-volume" == info->uri()){ // targetUri = "file:///data"; // } // windowIface->addNewTabs(QStringList()<uri() != "" && items.first()->uri() != "network:///"){ // qDebug() << "unable Property uri:" <uri(); // menu.addAction(tr("Property")); // menu.actions().first()->setEnabled(false); // } // if (!menu.isEmpty()) // menu.exec(mapToGlobal(pos)); // }); } static void ask_question_cb(GMountOperation *op, char *message, char **choices, Peony::ComputerViewContainer *p_this) { qDebug()<<"ask question cb:"<setIcon(QMessageBox::Information); msg_box->setText(message); char **choice = choices; int i = 0; while (*choice) { qDebug()<<*choice; QPushButton *button = msg_box->addButton(QString(*choice), QMessageBox::ActionRole); p_this->m_dlg->connect(button, &QPushButton::clicked, [=]() { g_mount_operation_set_choice(op, i); }); *choice++; i++; } //block ui msg_box->exec(); msg_box->deleteLater(); qDebug()<<"msg_box done"; g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED); } static void ask_password_cb(GMountOperation *op, const char *message, const char *default_user, const char *default_domain, GAskPasswordFlags flags, Peony::ComputerViewContainer *p_this) { Q_UNUSED(message); Q_UNUSED(default_user); Q_UNUSED(default_domain); Q_UNUSED(flags); Q_UNUSED(p_this); if(!p_this->m_serverLoginDlg){ g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED); return; } if (p_this->m_serverLoginDlg->property("IsRejected").toBool()) { g_mount_operation_reply(op, G_MOUNT_OPERATION_ABORTED); return; } if (p_this->m_serverLoginDlg->property("IsFirstAnonymous").toBool() && g_mount_operation_get_anonymous(op)) { p_this->m_serverLoginDlg->setProperty("IsFirstAnonymous", false); g_mount_operation_reply(op, G_MOUNT_OPERATION_HANDLED); return; } if ((flags & G_ASK_PASSWORD_NEED_PASSWORD) && (g_mount_operation_get_username(op) || g_mount_operation_get_password(op))) { QString str = ""; p_this->m_serverLoginDlg->setCurrentPasswd(str); p_this->m_serverLoginDlg->setCurrentUserName(str); } int code = p_this->m_serverLoginDlg->exec(); g_mount_operation_set_anonymous(op, p_this->m_serverLoginDlg->anonymous()); if (code == QDialog::Rejected) { g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED); p_this->m_serverLoginDlg->setProperty("IsRejected", true); return; } p_this->m_serverLoginDlg->syncRemoteServer(p_this->m_remote_uri); if (!p_this->m_serverLoginDlg->anonymous()) { g_mount_operation_set_username(p_this->m_op, p_this->m_serverLoginDlg->user().toUtf8().constData()); g_mount_operation_set_password(p_this->m_op, p_this->m_serverLoginDlg->password().toUtf8().constData()); g_mount_operation_set_domain(p_this->m_op, p_this->m_serverLoginDlg->domain().toUtf8().constData()); } else { p_this->m_serverLoginDlg->setProperty("IsFirstAnonymous", true); } g_mount_operation_set_password_save(p_this->m_op,/* dlgLogin.savePassword()? */G_PASSWORD_SAVE_NEVER/*: G_PASSWORD_SAVE_FOR_SESSION*/); p_this->m_serverLoginDlg->setProperty("IsRejected", false); g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED); } Peony::ComputerViewContainer::~ComputerViewContainer() { if (nullptr != m_op) { g_object_unref(m_op); } if (nullptr !=m_dlg) { delete m_dlg; m_dlg = nullptr; } if (nullptr != m_serverLoginDlg) { delete m_serverLoginDlg; m_serverLoginDlg = nullptr; } } const QStringList Peony::ComputerViewContainer::getSelections() { QStringList uris; // fix #260661 访问ftp时文管闪退并且产生core文件 // note: 从gdb查看core信息,发现m_view在挂载异常时为nullptr,增加非空判断避免空指针调用导致的崩溃 if (!m_view) { return uris; } auto model = static_cast(m_view->model()); for (auto index : m_view->selectionModel()->selectedIndexes()) { auto item = model->itemFromIndex(index); uris<uri(); } return uris; } void Peony::ComputerViewContainer::paintEvent(QPaintEvent *e) { DirectoryViewWidget::paintEvent(e); } void Peony::ComputerViewContainer::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { // if (m_enterAction) // m_enterAction->triggered(); e->accept(); return; } QWidget::keyPressEvent(e); } void Peony::ComputerViewContainer::bindModel(Peony::FileItemModel *model, Peony::FileItemProxyFilterSortModel *proxyModel) { //try fo fix model item update abnormal issue //related to task#224468 disconnect(m_model); disconnect(m_proxyModel); m_model = model; m_proxyModel = proxyModel; model->setRootUri("computer:///"); connect(model, &Peony::FileItemModel::findChildrenFinished, this, &Peony::DirectoryViewWidget::viewDirectoryChanged); if (m_view) m_view->deleteLater(); m_view = new ComputerView(this); auto layout = new QHBoxLayout; layout->addWidget(m_view); setLayout(layout); Q_EMIT viewDirectoryChanged(); auto selectionModel = m_view->selectionModel(); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &DirectoryViewWidget::viewSelectionChanged);; connect(this, &ComputerViewContainer::containerDoubleClicked, this, [=](const QModelIndex &index){ if (!index.parent().isValid() || m_view->getRightDoubleClickState()){ //qDebug() << "doubleClicked state" << m_view->getRightDoubleClickState(); m_view->setRightDoubleClickState(false); return; } auto model = static_cast(m_view->model()); auto item = model->itemFromIndex(index); if (!item->uri().isEmpty()) { item->check(); qDebug() << "doubleClicked updateWindowLocationRequest:"<uri(); this->updateWindowLocationRequest(item->uri()); } else { item->mount(); } }); connect(m_view, &QAbstractItemView::activated, this, [=](const QModelIndex &index) { //平板模式下,长按打开文件处理 if (m_view->m_touch_active_timer->isActive()) { auto costTime = m_view->m_touch_active_timer->interval() - m_view->m_touch_active_timer->remainingTime(); if (costTime > qApp->doubleClickInterval()) { m_view->m_touch_active_timer->stop(); return; } } //when selections is more than 1, let mainwindow to process if (m_view->selectionModel()->selectedIndexes().count() != 1) return; Q_EMIT containerDoubleClicked(m_view->selectionModel()->selectedIndexes().first()); m_view->m_touch_active_timer->stop(); }); connect(this, &QWidget::customContextMenuRequested, this, [=](const QPoint &pos){ auto selectedIndexes = m_view->selectionModel()->selectedIndexes(); auto index = m_view->indexAt(pos); if (!selectedIndexes.contains(index)) m_view->clearSelection(); if (index.isValid()) { m_view->selectionModel()->select(index, QItemSelectionModel::SelectCurrent); } QMenu menu(this); auto model = static_cast(m_view->model()); QStringList uris; QList items; for (auto index : m_view->selectionModel()->selectedIndexes()) { auto item = model->itemFromIndex(index); uris<uri(); if (item->uri() == "network:///") return; items<exec(); if (code == QDialog::Rejected) { return; } //GFile* m_volume = g_file_new_for_uri(m_dlg->uri().toUtf8().constData()); m_remote_uri = m_dlg->uri(); // deprecated, use updateWindowLocationRequest() instead. if (nullptr != m_serverLoginDlg) { delete m_serverLoginDlg; m_serverLoginDlg = nullptr; } m_serverLoginDlg = new ConnectServerLogin(m_remote_uri); updateWindowLocationRequest(m_remote_uri); //g_file_mount_enclosing_volume(m_volume, G_MOUNT_MOUNT_NONE, m_op, nullptr, GAsyncReadyCallback(mount_enclosing_volume_callback), this); }); } else if (items.count() == 1 // && (items.first()->uri() != "" ||items.first()->m_unixDeviceName.contains("/dev/sr")) && items.first()->uri() != "network:///" || items.first()->m_unixDeviceName.contains("/dev/sr")) { //bool isWayland = qApp->property("isWayland").toBool(); // related to #105070 auto item = items.first(); bool unmountable = item->canUnmount() && !item->property("isKydrive").toBool(); bool isReddisk = false; //fix bug#212689, 212690, 213120, 213121, hide reddisk format and unmount option //fix bug#333377, jc encrpt software change install path issue if (item->m_unixDeviceName.startsWith("/dev/dm") && (QFile::exists("/opt/AQTJ/Client/JC/MAIN/bin/jc_main_ui") || QFile::exists("/opt/apps/com.jc.bmt/files/MAIN/bin/jc_main_ui")) ) isReddisk = true; bool isCloud = QFile::exists("/etc/ecloud") || QFile::exists("/usr/local/share/Ecloud"); bool hideUnmount = false; bool hideFormat = false; //fix task#185121, sudo mount remove volume unmount has no effect issue //除samba, ftp, sftp 外的远程目录,云桌面环境下,不显示卸载和格式化选项 /* 云桌面重定向的盘,不显示卸载选项和格式化选项 bug#255725, task#185121 */ if (isCloud && AbstractComputerItem::RemoteVolume == item->itemType()) { hideFormat = true; if ( ! item->uri().startsWith("smb:///") && ! item->uri().startsWith("ftp:///") && ! item->uri().startsWith("sftp:///")) { hideUnmount = true; } } if(!item->canEject() && ! isReddisk && ! hideUnmount){ menu.addAction(tr("Unmount"), [=](){ item->unmount(G_MOUNT_UNMOUNT_NONE); }); menu.actions().first()->setEnabled(unmountable); } //fix bug#175330, wayland should be the same with mainline version /* else if (isWayland && ! isReddisk) { menu.addAction(tr("Unmount"), [=](){ item->unmount(G_MOUNT_UNMOUNT_NONE); }); menu.actions().first()->setEnabled(unmountable); }*/ /*eject function for volume. fix #18216*/ if(item->canEject()){ auto ejectAction = menu.addAction(tr("Eject"), [=](){ item->eject(G_MOUNT_UNMOUNT_NONE); }); QString unixDevice = item->m_unixDeviceName; if(unixDevice.contains("/dev/sr")){/* 光盘在刻录数据、镜像等操作时,即若处于busy状态时,将弹出菜单置灰不可用。link to bug#143293. */ menu.actions().first()->setDisabled(FileUtils::isBusyDevice(unixDevice)); } } auto uri = item->uri(); QString unixDevice = item->m_unixDeviceName; if (uri.isEmpty() && !unixDevice.isEmpty()) { uri = getComputerUriFromUnixDevice(unixDevice); } auto realUri = m_view->tryGetVolumeRealUriFromUri(uri); if (!realUri.isEmpty()) { uri = realUri; } if(uri.startsWith("file://") || !unixDevice.isEmpty()) { uri = getComputerUriFromUri(uri); } auto info = FileInfo::fromUri(uri); if (info->displayName().isEmpty() || info->targetUri().isEmpty()) { FileInfoJob j(info); j.querySync(); } auto mount = VolumeManager::getMountFromUri(info->targetUri()); //fix bug#52491, CDROM and DVD can format issue //fix bug#140543, remote redirection volume can be format if (nullptr != mount) { auto volume = VolumeManager::getVolumeFromMount(mount); if (mount && volume && ! isReddisk && ! hideFormat) { QString unixDevice = FileUtils::getUnixDevice(info->uri()); if (!unixDevice.isNull() && unixDevice.contains("/dev/sr") &&!unixDevice.startsWith("/dev/bus/usb") && info->isVolume()) { auto fdMenu = menu.addAction(tr("Format")); fdMenu->setEnabled(false); #ifndef KY_UDF_BURN DiscControl *discControl = new DiscControl(unixDevice); if(discControl->work()){ connect(discControl, &DiscControl::workFinished, [=](DiscControl *discCtrl){ connect(fdMenu, &QAction::triggered, [=](){ UdfFormatDialog *udfFormatDlg = FormatDlgCreateDelegate::getInstance()->createUdfDlg(uri, discCtrl); udfFormatDlg->show(); }); qDebug() << unixDevice << "supportUdf values:" << discCtrl->supportUdf(); fdMenu->setEnabled(discCtrl->supportUdf()); }); } #else UdfBurn::DiscControl *discControl = new UdfBurn::DiscControl(unixDevice); if(discControl->work()){ connect(discControl, &UdfBurn::DiscControl::workFinished, [=](UdfBurn::DiscControl *discCtrl){ connect(fdMenu, &QAction::triggered, [=](){ UdfBurn::UdfFormatDialogWrapper *udfFormatDlg = FormatDlgCreateDelegate::getInstance()->createUdfDlgWrapper(uri, discCtrl); udfFormatDlg->show(); }); qDebug() << unixDevice << "KY_UDF_BURN supportUdf values:" << discCtrl->supportUdf(); fdMenu->setEnabled(discControl->supportUdf()); }); } #endif } else if (! unixDevice.isNull() && ! unixDevice.contains("/dev/sr") &&!unixDevice.startsWith("/dev/bus/usb") && info->isVolume() && info->canUnmount()/* && info->targetUri() != "file:///data"*/) { auto fdMenu = menu.addAction(tr("Format"), [=] () { Format_Dialog *fd = FormatDlgCreateDelegate::getInstance()->createUDiskDlg(uri, nullptr); fd->show(); }); if (! mount) { fdMenu->setEnabled(false); } } } } else { if (!unixDevice.isNull() && !unixDevice.contains("/dev/sr") && !unixDevice.startsWith("/dev/bus/usb") && info->isVolume() && !uri.isEmpty()) { auto fdMenu = menu.addAction(tr("Format"), [=](){ Format_Dialog *fd = FormatDlgCreateDelegate::getInstance()->createUDiskDlg(uri, nullptr); fd->show(); }); } } QStringList fakeUrls; fakeUrls<getComputerViewMenuPlugins(); for (auto plugin : menuPlugins) { menu.addActions(plugin->menuActions(Peony::MenuPluginInterface::SideBar, "computer:///", fakeUrls)); } if (qApp->property("deviceRenamePluginLoaded").toBool()) { // add drive rename action, link to: #105070 auto driveRenamePlugin = new DriveRename(this); menu.addActions(driveRenamePlugin->menuActions(Peony::MenuPluginInterface::Type::SideBar, "computer:///", fakeUrls)); } if (!item->uri().startsWith("network://")) { auto a = menu.addAction(tr("Property"), [=]() { if (uri.isNull()) { QMessageBox::warning(0, 0, tr("You have to mount this volume first")); } else { QProcess p; p.setProgram("/usr/bin/peony"); QStringList args; args << "-p" << uri; p.setArguments(args); // p.startDetached(); p.startDetached(p.program(), p.arguments()); } }); auto targetUri = FileUtils::getTargetUri(info->uri()); bool isMounted = FileUtils::isFileUnmountable(info->uri()); if ("file:///" == uri || "file:///" == targetUri || "file:///data" == uri || "computer:///ukui-data-volume" == uri) { isMounted = true; } a->setEnabled(!uri.isNull() && isMounted); if(item->m_unixDeviceName.contains("/dev/sr")){/* 光盘在刻录数据、镜像等操作时,即若处于busy状态时,该菜单置灰不可用。link to bug#143293. */ a->setDisabled(FileUtils::isBusyDevice(item->m_unixDeviceName)); } } FMWindowIface *windowIface = dynamic_cast(this->topLevelWidget()); menu.addAction(tr("Open In New Window"), [=](){ auto targetUri = info->targetUri(); if(targetUri.isEmpty() && "computer:///ukui-data-volume" == info->uri()){ targetUri = "file:///data"; } auto newWindow = windowIface->create(targetUri); dynamic_cast(newWindow)->show(); }); if (!qApp->property("tabletMode").toBool()) { menu.addAction(tr("Open In New Tab"), [=](){ auto targetUri = info->targetUri(); if(targetUri.isEmpty() && "computer:///ukui-data-volume" == info->uri()){ targetUri = "file:///data"; } windowIface->addNewTabs(QStringList()<uri() != "" && items.first()->uri() != "network:///"){ qDebug() << "unable Property uri:" <uri(); menu.addAction(tr("Property")); menu.actions().first()->setEnabled(false); } if (!menu.isEmpty()) menu.exec(mapToGlobal(pos)); }); // m_enterAction = new QAction(this); // m_enterAction->setShortcut(Qt::Key_Enter); // addAction(m_enterAction); // connect(m_enterAction, &QAction::triggered, this, [=](){ // if (m_view->selectionModel()->selectedIndexes().count() == 1) { // Q_EMIT m_view->doubleClicked(m_view->selectionModel()->selectedIndexes().first()); // } // }); connect(m_view, &ComputerView::updateLocationRequest, this, &ComputerViewContainer::updateWindowLocationRequest); } void Peony::ComputerViewContainer::beginLocationChange() { Q_EMIT viewDirectoryChanged(); m_view->refresh(); } void Peony::ComputerViewContainer::stopLocationChange() { Q_EMIT viewDirectoryChanged(); } peony-extensions/peony-extension-computer-view/peony-extension-computer-view_tr.ts0000664000175000017500000004442715156143275030050 0ustar fengfeng ComputerItemDelegate You should mount volume first Önce bölümü bağlamalısınız ComputerNetworkItem Network Neighborhood Ağ Komşuları ComputerRemoteVolumeItem Remote Uzak ComputerUserShareItem Data ComputerVolumeItem Volume Bölüm System Disk File System Dosya Sistemi Data Intel::ComputerItemDelegate You should mount volume first Önce bölümü bağlamalısınız Intel::ComputerNetworkItem Network Neighborhood Ağ Komşuları Intel::ComputerRemoteVolumeItem Remote Uzak Intel::ComputerViewContainer Connect a server Servere bağlan Unmount Bağlantıyı kes Eject Çıkar Property Özellik You have to mount this volume first Önce bu bölümü bağlamalısınız Intel::ComputerVolumeItem Volume Bölüm System Disk User Disk LoginRemoteFilesystem Connect to Sever Sunucuya bağlan server information Sunucu bilgisi user information Kullanıcı bilgisi tag Etiket user Kullanıcı password Parola protocol Protokol server Sunucu directory Dizin SAMBA SAMBA FTP FTP / / port Port 20 21 137 138 139 445 ok Tamam cancel İptal Peony::ComputerViewContainer Connect a server Servere bağlan sftp://, etc... sftp://, gibi... Unmount Bağlantıyı kes Eject Çıkar Format Open In New Window Open In New Tab Property Özellik You have to mount this volume first Önce bu bölümü bağlamalısınız Peony::DriveRename Rename Device name: Warning Renaming cannot start with a decimal point, Please re-enter! The device name exceeds the character limit, rename failed! Renaming will unmount the device. Do you want to continue? QObject Computer View Bilgisayar Görünümü Show drives, network and personal directories Show drives, network and personal directories. Sürücüleri, ağı ve kişisel dizinleri göster. One or more programs prevented the unmount operation. Unmount failed Bağlantıyı kesme başarısız Error: %1 Do you want to unmount forcely? Hata: %1 Zorla ayrılmak ister misin? It need to synchronize before operating the device,place wait! The device has been mount successfully! Data synchronization is complete,the device has been unmount successfully! Error: %1 Eject device failed, the reason may be that the device has been removed, etc. Unable to unmount it, you may need to close some programs, such as: GParted etc. %1 Eject failed Çıkarma başarısız Cancel İptal Eject Anyway Yine de Çıkar Warning The device may not support the rename operation, rename failed! Message recipient disconnected from message bus without replying! log remote error peony-extensions/peony-extension-computer-view/peony-extension-computer-view_de.ts0000664000175000017500000004607215156143275030011 0ustar fengfeng ComputerItemDelegate You should mount volume first Sie sollten zuerst das Volume mounten ComputerNetworkItem Network Neighborhood Netzwerk-Nachbarschaft ComputerRemoteVolumeItem Remote Abgelegen ComputerUserShareItem User Share 本机共享 Data Daten ComputerVolumeItem Volume Volumen System Disk File System Dateisystem Data Daten Intel::ComputerItemDelegate You should mount volume first Sie sollten zuerst das Volume mounten Intel::ComputerNetworkItem Network Neighborhood Netzwerk-Nachbarschaft Intel::ComputerRemoteVolumeItem Remote Abgelegen Intel::ComputerViewContainer Connect a server Verbinden eines Servers Unmount Aushängen Eject Ausstoßen Property Eigentum You have to mount this volume first Sie müssen dieses Volume zuerst mounten Intel::ComputerVolumeItem Volume Volumen System Disk User Disk LoginRemoteFilesystem Connect to Sever Mit Server verbinden server information Server-Informationen user information Benutzerinformationen tag Etikett user Benutzer password Passwort protocol Protokoll server Server directory Verzeichnis SAMBA FTP FTP / / port Hafen 20 20 21 21 137 137 138 138 139 139 445 445 ok Okay cancel Abbrechen Peony::ComputerViewContainer Connect a server Verbinden eines Servers sftp://, etc... 如sftp://... Cancel 取消 OK 确定 Unmount Aushängen Eject Ausstoßen Format Open In New Window Open In New Tab format Format Property Eigentum You have to mount this volume first Sie müssen dieses Volume zuerst mounten Peony::DriveRename Rename Umbenennen Device name: Name des Geräts: Warning Warnung Renaming cannot start with a decimal point, Please re-enter! Die Umbenennung darf nicht mit einem Komma beginnen, bitte erneut eingeben! The device name exceeds the character limit, rename failed! Der Gerätename überschreitet die Zeichenbegrenzung, Umbenennung fehlgeschlagen! Renaming will unmount the device. Do you want to continue? Durch das Umbenennen wird die Bereitstellung des Geräts aufgehoben. Möchten Sie fortfahren? QObject Computer View Computer-Ansicht Show drives, network and personal directories Show drives, network and personal directories. Anzeigen von Laufwerken, Netzwerk- und persönlichen Verzeichnissen One or more programs prevented the unmount operation. Mindestens ein Programm verhinderte das Aufheben der Bereitstellung. Unmount failed Fehler beim Aufheben der Bereitstellung Error: %1 Do you want to unmount forcely? 错误:%1 是否强制卸载? It need to synchronize before operating the device,place wait! Es muss synchronisiert werden, bevor das Gerät in Betrieb genommen wird, warten Sie! The device has been mount successfully! Das Gerät wurde erfolgreich montiert! Data synchronization is complete,the device has been unmount successfully! Die Datensynchronisierung ist abgeschlossen, das Gerät wurde erfolgreich ausgemountet! Error: %1 Fehler: %1 Unable to unmount it, you may need to close some programs, such as: GParted etc. Da es nicht möglich ist, es auszuhängen, müssen Sie möglicherweise einige Programme schließen, wie z.B.: GParted usw. %1 %1 Eject device failed, the reason may be that the device has been removed, etc. Das Auswerfen des Geräts ist fehlgeschlagen, der Grund kann sein, dass das Gerät entfernt wurde usw. Eject failed Auswurf fehlgeschlagen Cancel 取消 Eject Anyway 无论如何弹出 Warning Warnung The device may not support the rename operation, rename failed! Das Gerät unterstützt den Umbenennungsvorgang möglicherweise nicht, Umbenennung fehlgeschlagen! Message recipient disconnected from message bus without replying! Message recipient disconnected from message bus without replying Nachrichtenempfänger vom Nachrichtenbus getrennt, ohne zu antworten! log remote error Remote-Fehler protokollieren peony-extensions/peony-extension-computer-view/peony-extension-computer-view_zh_HK.ts0000664000175000017500000004440315156143275030420 0ustar fengfeng ComputerItemDelegate You should mount volume first 您應該首先掛載卷 ComputerNetworkItem Network Neighborhood 網路鄰域 ComputerRemoteVolumeItem Remote 遠端 ComputerUserShareItem User Share 本机共享 Data 數據 ComputerVolumeItem Volume System Disk 系統盤 File System 檔案系統 Data 數據 Intel::ComputerItemDelegate You should mount volume first 您應該首先掛載卷 Intel::ComputerNetworkItem Network Neighborhood 網路鄰域 Intel::ComputerRemoteVolumeItem Remote 遠端 Intel::ComputerViewContainer Connect a server 連接伺服器 Unmount 卸載 Eject 彈出 Property 財產 You have to mount this volume first 您必須先掛載此卷 Intel::ComputerVolumeItem Volume System Disk 系統盤 User Disk 使用者磁碟 LoginRemoteFilesystem Connect to Sever 連接到 Sever server information 伺服器資訊 user information 用戶資訊 tag 標記 user 使用者 password 密碼 protocol 協定 server 伺服器 directory 目錄 SAMBA 桑巴舞 FTP FTP (自由貿易協定 / / port 港口 20 20 21 21 137 137 138 138 139 139 445 445 ok 還行 cancel 取消 Peony::ComputerViewContainer Connect a server 連接伺服器 sftp://, etc... 如sftp://... Cancel 取消 OK 确定 Unmount 卸載 Eject 彈出 format 格式化 Format 格式 Property 財產 You have to mount this volume first 您必須先掛載此卷 Open In New Window 在新視窗中打開 Open In New Tab 在新選項卡中打開 Peony::DriveRename Rename 重新命名 Device name: 裝置名稱: Warning 警告 Renaming cannot start with a decimal point, Please re-enter! 重命名不能以小數點開頭,請重新輸入! The device name exceeds the character limit, rename failed! 設備名稱超出字元限制,重命名失敗! Renaming will unmount the device. Do you want to continue? 重命名將卸載設備。是否要繼續? QObject Computer View 計算機檢視 Show drives, network and personal directories Show drives, network and personal directories. 顯示驅動器、網路和個人目錄 One or more programs prevented the unmount operation. 一個或多個程式阻止了卸載作。 Unmount failed 卸載失敗 Error: %1 Do you want to unmount forcely? 錯誤: %1 是否要強制卸載? It need to synchronize before operating the device,place wait! 作設備前需要同步,放置等待! The device has been mount successfully! 設備已成功掛載! Data synchronization is complete,the device has been unmount successfully! 數據同步完成,設備已成功卸載! Error: %1 錯誤: %1 Eject device failed, the reason may be that the device has been removed, etc. 彈出設備失敗,原因可能是設備已被刪除等。 Unable to unmount it, you may need to close some programs, such as: GParted etc. 無法卸載它,您可能需要關閉一些程式,例如:GParted 等。 %1 %1 Eject failed 彈出失敗 Cancel 取消 Eject Anyway 仍然彈出 Warning 警告 The device may not support the rename operation, rename failed! 設備可能不支援重命名作,重命名失敗! Message recipient disconnected from message bus without replying! 消息接收者與消息總線斷開連接,但未回復! log remote error 記錄遠程錯誤 peony-extensions/peony-extension-computer-view/peony-extension-computer-view_ug_CN.ts0000664000175000017500000005020715156143275030407 0ustar fengfeng ComputerItemDelegate You should mount volume first سىز ئاۋۋال ئاۋازنى ئۇلاشىڭىز كېرەك. ComputerNetworkItem Network Neighborhood تور قوشنىسى ComputerRemoteVolumeItem Remote يىراق ComputerUserShareItem User Share 本机共享 Data سانلىق مەلۇمات ComputerVolumeItem Volume ئاۋاز چوڭلۇقى System Disk File System ھۆججەت سىستېمىسى Data سانلىق مەلۇمات Intel::ComputerItemDelegate You should mount volume first سىز ئاۋۋال ئاۋازنى ئۇلاشىڭىز كېرەك. Intel::ComputerNetworkItem Network Neighborhood تور قوشنىسى Intel::ComputerRemoteVolumeItem Remote يىراق Intel::ComputerViewContainer Connect a server مۇلازىمېتىرنى ئۇلاش Unmount Unmount Eject چىقىرىش Property مال-مۈلۈك You have to mount this volume first ئالدى بىلەن بۇ ئاۋازنى ئۇلاش كېرەك Intel::ComputerVolumeItem Volume ئاۋاز چوڭلۇقى System Disk User Disk LoginRemoteFilesystem Connect to Sever Sever غا ئۇلاش server information مۇلازىمىتېر ئۇچۇرى user information ئىشلەتكۈچى ئۇچۇرلىرى tag خەتكۈچ user ئىشلەتكۈچى password پارول protocol كېلىشىم server مۇلازىمىتېر directory مۇندەرىجە SAMBA FTP FTP / / port پورت 20 20 21 21 137 137 138 138 139 139 445 445 ok ماقۇل cancel ئەمەلدىن قالدۇرۇش Peony::ComputerViewContainer Connect a server مۇلازىمېتىرنى ئۇلاش sftp://, etc... 如sftp://... Cancel 取消 OK 确定 Unmount Unmount Eject چىقىرىش Format Open In New Window Open In New Tab format فورمات Property مال-مۈلۈك You have to mount this volume first ئالدى بىلەن بۇ ئاۋازنى ئۇلاش كېرەك Peony::DriveRename Rename قايتا ئىسىم فامىلە قىلىش Device name: ئۈسكۈنە نامى: Warning دىققەت Renaming cannot start with a decimal point, Please re-enter! نامىنى ئۆزگەرتىشتە دېسكىلىق نۇقتىدىن باشلىنالمايدۇ، قايتا كىرىڭ! The device name exceeds the character limit, rename failed! ئۈسكۈنە نامى پېرسوناژ چەكلىمىدىن ئېشىپ كەتتى، قايتا ئىسىم قويۇش مەغلۇپ بولدى! Renaming will unmount the device. Do you want to continue? نامى ئۆزگەرتكەندە ئۈسكۈنىنى تىندۇرىدۇ. داۋام قىلغۇڭىز بارمۇ؟ The device may not support the rename operation, rename failed! ئۈسكۈنە بەلكىم قايتا ئىسىم ئۆزگەرتىش مەشخۇلاتى قوللىماسلىقى مۇمكىن، قايتا ئىسىم بېرىش مەغلۇپ بولدى! QObject Computer View كومپيۇتېر كۆرۈنۈشى Show drives, network and personal directories Show drives, network and personal directories. قوزغاتقۇچ، تور ۋە شەخسىي مۇندەرىجىلەرنى كۆرسىتىش. One or more programs prevented the unmount operation. بىر ياكى بىر نەچچە پروگرامما ئۈزۈلۈپ قېلىشنىڭ ئالدىنى ئالدى. Unmount failed Unmount مەغلۇپ بولدى Error: %1 Do you want to unmount forcely? 错误:%1 是否强制卸载? It need to synchronize before operating the device,place wait! ئۈسكۈنىنى مەشغۇلات قىلىشتىن بۇرۇن ماس قەدەمدە ماس قەدەمدە ئىشلىشى كېرەك، ئورۇن ساقلاپ تۇرۇڭ! The device has been mount successfully! بۇ ئۈسكۈنە ئوڭۇشلۇق قاچىلاندى! Data synchronization is complete,the device has been unmount successfully! سانلىق مەلۇمات ماس قەدەمدە تاماملاندى، ئۈسكۈنە مۇۋەپپەقىيەتلىك ئاخىرلاشتى! Error: %1 خاتالىق: ٪1 Eject device failed, the reason may be that the device has been removed, etc. Unable to unmount it, you may need to close some programs, such as: GParted etc. ئۈزەلمىسىڭىز بەلكىم بەزى پروگراممىلارنى تاقاشىڭىز مۇمكىن، مەسىلەن: GParted قاتارلىقلار. %1 %1 Eject failed چىقىرىش مەغلۇپ بولدى Cancel 取消 Eject Anyway 无论如何弹出 Warning دىققەت The device may not support the rename operation, rename failed! ئۈسكۈنە بەلكىم قايتا ئىسىم ئۆزگەرتىش مەشخۇلاتى قوللىماسلىقى مۇمكىن، قايتا ئىسىم بېرىش مەغلۇپ بولدى! Message recipient disconnected from message bus without replying! log remote error peony-extensions/peony-extension-computer-view/peony-extension-computer-view_mn.ts0000664000175000017500000005600715156143275030032 0ustar fengfeng ComputerItemDelegate You should mount volume first ᠠᠴᠢᠬᠤ ᠬᠡᠰᠡᠭ ᠢ᠋ ᠳᠡᠷᠢᠬᠦᠨ ᠰᠤᠩᠭᠤᠭᠳᠠᠬᠤᠨ ᠪᠤᠯᠭᠠᠬᠤ ᠱᠠᠭᠠᠷᠳᠠᠯᠭ᠎ᠠ ᠲᠠᠢ ComputerNetworkItem Network Neighborhood ᠰᠦᠯᠵᠢᠶᠡᠨ ᠳᠡᠭᠡᠷᠡᠬᠢ ᠬᠦᠷᠰᠢ ComputerRemoteVolumeItem Remote ᠠᠯᠤᠰ ComputerUserShareItem User Share 本机共享 Data ᠳ᠋ᠠᠢᠲ᠋ᠠ ᠵᠢᠨ ᠲᠠᠪᠠᠭ ComputerVolumeItem Volume ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ System Disk ᠰᠢᠰᠲ᠋ᠧᠮ᠎ᠦ᠋ᠨ ᠲᠠᠪᠠᠭ File System ᠹᠠᠢᠯ᠎ᠤᠨ ᠱᠢᠰᠲ᠋ᠧᠮ Data ᠳ᠋ᠠᠢᠲ᠋ᠠ ᠵᠢᠨ ᠲᠠᠪᠠᠭ Intel::ComputerItemDelegate You should mount volume first ᠠᠴᠢᠬᠤ ᠬᠡᠰᠡᠭ ᠢ᠋ ᠳᠡᠷᠢᠬᠦᠨ ᠰᠤᠩᠭᠤᠭᠳᠠᠬᠤᠨ ᠪᠤᠯᠭᠠᠬᠤ ᠱᠠᠭᠠᠷᠳᠠᠯᠭ᠎ᠠ ᠲᠠᠢ Intel::ComputerNetworkItem Network Neighborhood ᠰᠦᠯᠵᠢᠶᠡᠨ ᠳᠡᠭᠡᠷᠡᠬᠢ ᠬᠦᠷᠰᠢ Intel::ComputerRemoteVolumeItem Remote ᠠᠯᠤᠰ Intel::ComputerViewContainer Connect a server ᠠᠯᠤᠰ ᠦᠢᠯᠡᠴᠢᠯᠡᠬᠦᠡᠷ ᠲᠤ᠌ ᠴᠦᠷᠬᠡᠯᠡᠬᠦ Unmount ᠪᠠᠭᠤᠯᠭᠠᠬᠤ Eject ᠦᠰᠦᠷᠴᠤ ᠭᠠᠷᠬᠤ Property ᠬᠠᠷᠢᠶᠠᠳᠤ ᠴᠢᠨᠠᠷ You have to mount this volume first ᠠᠴᠢᠬᠤ ᠬᠡᠰᠡᠭ ᠢ᠋ ᠳᠡᠷᠢᠬᠦᠨ ᠰᠤᠩᠭᠤᠭᠳᠠᠬᠤᠨ ᠪᠤᠯᠭᠠᠬᠤ ᠱᠠᠭᠠᠷᠳᠠᠯᠭ᠎ᠠ ᠲᠠᠢ Intel::ComputerVolumeItem Volume ᠳᠠᠭᠤᠨ ᠤ᠋ ᠬᠡᠮᠵᠢᠶ᠎ᠡ System Disk ᠰᠢᠰᠲ᠋ᠧᠮ᠎ᠦ᠋ᠨ ᠲᠠᠪᠠᠭ User Disk ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ᠎ᠶ᠋ᠢᠨ ᠲᠠᠪᠠᠭ LoginRemoteFilesystem Connect to Sever ᠦᠢᠯᠡᠴᠢᠯᠡᠬᠦᠡᠷ ᠲᠤ᠌ ᠴᠦᠷᠬᠡᠯᠡᠬᠦ server information ᠦᠢᠯᠡᠴᠢᠯᠡᠬᠦᠷ ᠤ᠋ᠨ ᠮᠡᠳᠡᠭᠡ ᠵᠠᠩᠬᠢ user information ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠵᠢᠨ ᠮᠡᠳᠡᠭᠡ ᠵᠠᠩᠬᠢ tag ᠱᠤᠰᠢᠭ᠎ᠠ user ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ password ᠨᠢᠭᠤᠴᠠ ᠺᠤᠳ᠋ protocol ᠭᠡᠷ᠎ᠡ server ᠦᠢᠯᠡᠴᠢᠯᠡᠬᠡᠨ ᠤ᠋ ᠦᠵᠦᠬᠦᠷ ᠤ᠋ᠨ ᠬᠠᠶᠢᠭ directory ᠭᠠᠷᠴᠠᠭ SAMBA FTP FTP / / port ᠦᠵᠦᠬᠦᠷ 20 20 21 21 137 137 138 138 139 139 445 445 ok ᠴᠦᠷᠬᠡᠯᠡᠬᠦ cancel ᠦᠭᠡᠢᠰᠬᠡᠬᠦ Peony::ComputerViewContainer Connect a server ᠠᠯᠤᠰ ᠦᠢᠯᠡᠴᠢᠯᠡᠬᠦᠡᠷ ᠲᠤ᠌ ᠴᠦᠷᠬᠡᠯᠡᠬᠦ sftp://, etc... 如sftp://... Cancel 取消 OK 确定 Unmount ᠪᠠᠭᠤᠯᠭᠠᠬᠤ Eject ᠦᠰᠦᠷᠴᠤ ᠭᠠᠷᠬᠤ Format format ᠠᠩᠬᠠᠵᠢᠭᠤᠯᠬᠤ Property ᠬᠠᠷᠢᠶᠠᠳᠤ ᠴᠢᠨᠠᠷ You have to mount this volume first ᠠᠴᠢᠬᠤ ᠬᠡᠰᠡᠭ ᠢ᠋ ᠳᠡᠷᠢᠬᠦᠨ ᠰᠤᠩᠭᠤᠭᠳᠠᠬᠤᠨ ᠪᠤᠯᠭᠠᠬᠤ ᠱᠠᠭᠠᠷᠳᠠᠯᠭ᠎ᠠ ᠲᠠᠢ Open In New Window ᠰᠢᠨ᠎ᠡ ᠴᠤᠩᠬᠤᠨ ᠳᠡᠭᠡᠷ᠎ᠡ ᠨᠡᠬᠡᠬᠡᠬᠦ Open In New Tab ᠰᠢᠨ᠎ᠡ ᠱᠤᠰᠢᠭ᠎ᠠ ᠵᠢᠨ ᠨᠢᠭᠤᠷ ᠳᠡᠭᠡᠷ᠎ᠡ ᠨᠡᠬᠡᠬᠡᠬᠦ Peony::DriveRename Rename ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠬᠦ Device name: ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ᠎ᠶᠢᠨ ᠱᠢᠨᠵᠢ ᠳᠡᠮᠳᠡᠭ ᠄ Warning ᠠᠨᠭᠬᠠᠷᠠᠭᠤᠯᠬᠤ Renaming cannot start with a decimal point, Please re-enter! ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠦᠯ ᠪᠤᠳᠠᠷᠬᠠᠢ ᠲᠤᠭ᠎ᠠ ᠪᠡᠷ ᠡᠬᠢᠯᠡᠵᠤ ᠪᠤᠯᠬᠤ ᠦᠬᠡᠢ᠂ ᠳᠠᠬᠢᠵᠤ ᠤᠷᠤᠭᠤᠯᠤᠭᠠᠷᠠᠢ! The device name exceeds the character limit, rename failed! ᠲᠦᠬᠦᠬᠡᠷᠡᠮᠵᠢ ᠵᠢᠨ ᠨᠡᠷᠡᠢᠳᠦᠯ ᠦᠰᠦᠭ ᠤ᠋ᠨ ᠲᠤᠭᠠᠨ ᠤᠤ ᠬᠢᠵᠠᠭᠠᠷᠯᠠᠯ ᠡᠴᠡ ᠬᠡᠳᠦᠷᠡᠭᠰᠡᠨ᠂ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠴᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ! Renaming will unmount the device. Do you want to continue? ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠬᠦ ᠳ᠋ᠤ᠌ ᠬᠦᠷᠪᠡᠯ ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢ ᠪᠠᠭᠤᠯᠭᠠᠵᠤ ᠮᠡᠳᠡᠨ᠎ᠡ᠂ ᠦᠷᠬᠦᠯᠵᠢᠯᠡᠨ ᠬᠦᠢᠴᠡᠳᠬᠡᠬᠦ ᠤᠤ? The device may not support the rename operation, rename failed! ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠬᠦ ᠵᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠬᠡᠢ ᠪᠠᠢᠵᠤ ᠮᠡᠳᠡᠨ᠎ᠡ᠂ ᠳᠠᠬᠢᠨ ᠨᠡᠷᠡᠢᠳᠴᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ! QObject Computer View ᠺᠤᠮᠫᠢᠦᠲᠸᠷ ᠤ᠋ᠨ ᠬᠠᠷᠠᠭᠠᠨ ᠵᠢᠷᠤᠭ Show drives, network and personal directories Show drives, network and personal directories. ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢᠨ ᠬᠡᠰᠡᠭ᠂ ᠲᠤᠤᠷ ᠰᠦᠯᠵᠢᠶᠡᠨ ᠤ᠋ ᠭᠠᠷᠴᠠᠭ ᠪᠤᠯᠤᠨ ᠬᠤᠪᠢ ᠬᠦᠮᠦᠨ ᠤ᠋ ᠭᠠᠷᠴᠠᠭ ᠢ᠋ ᠢᠯᠡᠷᠡᠬᠦᠯᠬᠦ. One or more programs prevented the unmount operation. ᠨᠢᠭᠡ ᠪᠤᠶᠤ ᠤᠯᠠᠨ ᠫᠡᠷᠦᠭᠷᠡᠮ ᠪᠠᠭᠤᠯᠭᠠᠬᠤ ᠠᠵᠢᠯᠯᠠᠬᠤᠢ ᠵᠢ ᠬᠤᠷᠢᠭᠯᠠᠵᠤ ᠪᠠᠢᠨ᠎ᠠ. Unmount failed ᠪᠠᠭᠤᠯᠭᠠᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Error: %1 Do you want to unmount forcely? ᠪᠤᠷᠤᠭᠤ: %1 ᠠᠯᠪᠠ ᠪᠡᠷ ᠪᠠᠭᠤᠯᠭᠠᠬᠤ ᠤᠤ? It need to synchronize before operating the device,place wait! ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢ ᠠᠵᠢᠯᠯᠠᠭᠤᠯᠬᠤ ᠡᠴᠡ ᠡᠮᠦᠨ᠎ᠡ ᠳ᠋ᠠᠢᠲ᠋ᠠ ᠵᠢ ᠢᠵᠢᠯ ᠠᠯᠬᠤᠮᠴᠢᠯᠠᠬᠤ ᠱᠠᠭᠠᠷᠳᠠᠯᠭ᠎ᠠ ᠲᠠᠢ, ᠲᠦᠷ ᠬᠦᠯᠢᠶᠡᠬᠡᠷᠡᠢ! The device has been mount successfully! ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢ ᠠᠴᠢᠪᠠ! Data synchronization is complete,the device has been unmount successfully! ᠳ᠋ᠠᠢᠲ᠋ᠠ ᠵᠢ ᠢᠵᠢᠯ ᠠᠯᠬᠤᠮᠴᠢᠯᠠᠪᠠ, ᠳᠦᠬᠦᠬᠡᠷᠦᠮᠵᠢ ᠵᠢ ᠨᠢᠭᠡᠨᠳᠡ ᠪᠠᠭᠤᠯᠭᠠᠪᠠ! Error: %1 ᠪᠤᠷᠤᠭᠤ: %1 Unable to unmount it, you may need to close some programs, such as: GParted etc. ᠪᠠᠭᠤᠯᠭᠠᠬᠤ ᠵᠢᠨ ᠠᠷᠭ᠎ᠠ ᠦᠬᠡᠢ, ᠲᠠ ᠤᠷᠢᠳᠠᠪᠠᠷ ᠨᠢᠭᠡ ᠪᠦᠯᠦᠭ ᠫᠡᠷᠦᠭᠷᠡᠮ ᠢ᠋ ᠬᠠᠭᠠᠬᠤ ᠴᠢᠬᠤᠯᠠᠳᠠᠢ, ᠬᠤᠪᠢᠶᠠᠷᠢ ᠬᠡᠰᠡᠭ ᠤ᠋ᠨ ᠨᠠᠢᠷᠠᠭᠤᠯᠤᠭᠤᠷ ᠵᠡᠷᠭᠡ. %1 %1 Eject device failed, the reason may be that the device has been removed, etc. ᠰᠤᠮᠤ ᠭᠠᠷᠭᠠᠬᠤ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠢᠯᠠᠭᠳᠠᠭᠰᠠᠨ ᠠᠴᠠ ᠪᠣᠯᠵᠤ ᠂ ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠨᠢᠭᠡᠨᠲᠡ ᠠᠷᠢᠯᠭᠠᠭᠳᠠᠭᠰᠠᠨ ᠵᠡᠷᠭᠡ ᠮᠠᠭᠠᠳ ᠃ Eject failed ᠦᠰᠦᠷᠴᠤ ᠭᠠᠷᠴᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Cancel ᠦᠭᠡᠢᠰᠬᠡᠬᠦ Eject Anyway ᠶᠠᠭᠠᠬᠢᠭᠰᠠᠨ ᠴᠤ ᠰᠤᠮᠤ ᠭᠠᠷᠭᠠᠭᠰᠠᠨ ᠴᠤ Warning ᠠᠨᠭᠬᠠᠷᠠᠭᠤᠯᠬᠤ The device may not support the rename operation, rename failed! ᠲᠥᠬᠥᠭᠡᠷᠦᠮᠵᠢ ᠬᠦᠨᠳᠦ ᠨᠡᠷᠡᠶᠢᠳᠦᠨ ᠠᠵᠢᠯᠯᠠᠬᠤ ᠶᠢ ᠳᠡᠮᠵᠢᠬᠦ ᠦᠭᠡᠢ ᠮᠠᠭᠠᠳ ᠂ ᠬᠦᠨᠳᠦ ᠪᠡᠷ ᠨᠡᠷᠡᠶᠢᠳᠦᠯ ᠢᠯᠠᠭᠳᠠᠵᠠᠢ ! Message recipient disconnected from message bus without replying! ᠮᠡᠳᠡᠭᠡ ᠬᠦᠯᠢᠶᠡᠨ ᠠᠪᠤᠭᠴᠢ ᠬᠠᠷᠢᠭᠤ ᠦᠭᠡᠢ ᠪᠠᠢᠳᠠᠯ ᠳᠣᠣᠷ᠎ᠠ ᠮᠡᠳᠡᠭᠡᠨ᠎ᠦ᠌ ᠶᠡᠷᠦᠩᠬᠡᠢ ᠤᠲᠠᠰᠤ᠎ᠲᠠᠢ ᠵᠠᠯᠭᠠᠯᠳᠤᠬᠤ᠎ᠪᠠᠨ ᠲᠠᠰᠤᠯᠤᠭᠠᠷᠠᠢ ! log remote error ᠠᠯᠤᠰ ᠠᠴᠠ ᠪᠤᠷᠤᠭᠤ ᠲᠡᠮᠳᠡᠭᠯᠡᠵᠡᠢ ᠃ peony-extensions/peony-extension-computer-view/login-remote-filesystem.h0000664000175000017500000000234315156143137025743 0ustar fengfeng/* * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef LOGINREMOTEFILESYSTEM_H #define LOGINREMOTEFILESYSTEM_H #include namespace Ui { class LoginRemoteFilesystem; } class LoginRemoteFilesystem : public QDialog { Q_OBJECT public: explicit LoginRemoteFilesystem(QWidget *parent = nullptr); ~LoginRemoteFilesystem(); QString user(); QString password(); QString domain(); QString uri(); private: Ui::LoginRemoteFilesystem *ui; }; #endif // LOGINREMOTEFILESYSTEM_H peony-extensions/peony-extension-computer-view/login-remote-filesystem.cpp0000664000175000017500000000425115156143137026276 0ustar fengfeng/* * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "login-remote-filesystem.h" #include "ui_login-remote-filesystem.h" #include LoginRemoteFilesystem::LoginRemoteFilesystem(QWidget *parent) : QDialog(parent), ui(new Ui::LoginRemoteFilesystem) { ui->setupUi(this); ui->pwd_lineEdit->setEchoMode(QLineEdit::Password); ui->port_comboBox->setEditText("445"); // connect(ui->type_comboBox, &QComboBox::currentTextChanged, [=](QString& tp) { // if ("SAMBA" == tp) { // ui->port_comboBox->setEditText("445"); // } else if ("FTP" == tp) { // ui->port_comboBox->setEditText("21"); // } // }); } LoginRemoteFilesystem::~LoginRemoteFilesystem() { disconnect(); delete ui; } QString LoginRemoteFilesystem::user() { return ui->user_lineEdit->text(); } QString LoginRemoteFilesystem::password() { return ui->pwd_lineEdit->text(); } QString LoginRemoteFilesystem::domain() { return ui->ip_edit->text() + ":" + ui->port_comboBox->currentText(); } QString LoginRemoteFilesystem::uri() { QString uuri = ""; if (ui->type_comboBox->currentText() == "SAMBA") { uuri = "smb://" + ui->ip_edit->text() + ":" + ui->port_comboBox->currentText() + ui->file_lineEdit->text(); } else if (ui->type_comboBox->currentText() == "FTP") { uuri = "ftp://" + ui->ip_edit->text() + ":" + ui->port_comboBox->currentText() + ui->file_lineEdit->text(); } return uuri; } peony-extensions/peony-extension-computer-view/computer-view-container.h0000664000175000017500000000666415156143137025760 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef COMPUTERVIEWCONTAINER_H #define COMPUTERVIEWCONTAINER_H #include #include #include #include #include class ComputerView; namespace Peony { class ComputerViewContainer : public DirectoryViewWidget { Q_OBJECT public: explicit ComputerViewContainer(QWidget *parent = nullptr); ~ComputerViewContainer(); const virtual QString viewId() {return "Computer View";} //location const virtual QString getDirectoryUri() {return "computer:///";} //selections const virtual QStringList getSelections(); //children const virtual QStringList getAllFileUris() {return QStringList();} virtual int getSortType() {return 0;} virtual Qt::SortOrder getSortOrder() {return Qt::AscendingOrder;} //zoom virtual int currentZoomLevel() {return -1;} virtual int minimumZoomLevel() {return -1;} virtual int maximumZoomLevel() {return -1;} virtual bool supportZoom() {return false;} protected: void paintEvent(QPaintEvent *e); void keyPressEvent(QKeyEvent *e); public Q_SLOTS: virtual void bindModel(FileItemModel *model, FileItemProxyFilterSortModel *proxyModel); //location //virtual void open(const QStringList &uris, bool newWindow) {} virtual void setDirectoryUri(const QString &uri) {} virtual void beginLocationChange(); virtual void stopLocationChange(); virtual void closeDirectoryView() {} //selections virtual void setSelections(const QStringList &uris) {} virtual void invertSelections() {} virtual void scrollToSelection(const QString &uri) {} //clipboard //cut items should be drawn differently. virtual void setCutFiles(const QStringList &uris) {} virtual void setSortType(int sortType) {} virtual void setSortOrder(int sortOrder) {} virtual void editUri(const QString &uri) {} virtual void editUris(const QStringList uris) {} virtual void repaintView() {} virtual void clearIndexWidget() {} //zoom virtual void setCurrentZoomLevel(int zoomLevel) {} Q_SIGNALS: void containerDoubleClicked(const QModelIndex &index); public: QString m_remote_uri; GMountOperation* m_op = nullptr; ConnectServerDialog* m_dlg = nullptr; ConnectServerLogin* m_serverLoginDlg = nullptr; private: ComputerView *m_view = nullptr; //QAction *m_enterAction = nullptr; Peony::FileItemModel *m_model = nullptr; Peony::FileItemProxyFilterSortModel *m_proxyModel = nullptr; }; } #endif // COMPUTERVIEWCONTAINER_H peony-extensions/peony-extension-computer-view/peony-extension-computer-view_cs.ts0000664000175000017500000004407615156143275030030 0ustar fengfeng ComputerItemDelegate You should mount volume first Svazek je třeba nejprve připojit (mount) ComputerNetworkItem Network Neighborhood Okolní počítače ComputerRemoteVolumeItem Remote Vzdálené ComputerUserShareItem Data ComputerVolumeItem Volume Svazek System Disk File System Souborový systém Data Intel::ComputerItemDelegate You should mount volume first Svazek je třeba nejprve připojit (mount) Intel::ComputerNetworkItem Network Neighborhood Okolní počítače Intel::ComputerRemoteVolumeItem Remote Vzdálené Intel::ComputerViewContainer Connect a server Připojit k serveru Unmount Odpojit Eject Property Vlastnost You have to mount this volume first Tento svazek je třeba nejprve připojit (mount) Intel::ComputerVolumeItem Volume Svazek System Disk User Disk LoginRemoteFilesystem Connect to Sever server information user information tag user password protocol server directory SAMBA FTP / port 20 21 137 138 139 445 ok cancel Peony::ComputerViewContainer Connect a server Připojit k serveru sftp://, etc... sftp://, atd… Unmount Odpojit Eject Format Open In New Window Open In New Tab Property Vlastnost You have to mount this volume first Tento svazek je třeba nejprve připojit (mount) Peony::DriveRename Rename Device name: Warning Renaming cannot start with a decimal point, Please re-enter! The device name exceeds the character limit, rename failed! Renaming will unmount the device. Do you want to continue? QObject Computer View Tento počítač Show drives, network and personal directories Show drives, network and personal directories. Zobrazit disky, síť a osobní složky. It need to synchronize before operating the device,place wait! The device has been mount successfully! Data synchronization is complete,the device has been unmount successfully! Eject device failed, the reason may be that the device has been removed, etc. One or more programs prevented the unmount operation. Unmount failed Error: %1 Error: %1 Do you want to unmount forcely? Unable to unmount it, you may need to close some programs, such as: GParted etc. %1 Eject failed Cancel Eject Anyway Warning The device may not support the rename operation, rename failed! Message recipient disconnected from message bus without replying! log remote error peony-extensions/peony-extension-computer-view/peony-extension-computer-view_kk_KZ.ts0000664000175000017500000004762315156143275030435 0ustar fengfeng ComputerItemDelegate You should mount volume first Алдымен көлемді орнату керек ComputerNetworkItem Network Neighborhood Желілік шағын аудан ComputerRemoteVolumeItem Remote Қашықтан ComputerUserShareItem User Share 本机共享 Data Деректер ComputerVolumeItem Volume Көлемі System Disk File System Файлдар жүйесі Data Деректер Intel::ComputerItemDelegate You should mount volume first Алдымен көлемді орнату керек Intel::ComputerNetworkItem Network Neighborhood Желілік шағын аудан Intel::ComputerRemoteVolumeItem Remote Қашықтан Intel::ComputerViewContainer Connect a server Серверді қосу Unmount Еңсерілмеу Eject Эжек Property Сипат You have to mount this volume first Бұл көлемді алдымен орнату керек Intel::ComputerVolumeItem Volume Көлемі System Disk User Disk LoginRemoteFilesystem Connect to Sever Северге қосылу server information сервер ақпараты user information пайдаланушы ақпараты tag Тег user пайдаланушы password құпиясөз protocol хаттама server сервер directory каталог SAMBA FTP FTP / / port порт 20 20 21 21 137 137 138 138 139 139 445 445 ok Жақсы cancel Болдырмау Peony::ComputerViewContainer Connect a server Серверді қосу sftp://, etc... 如sftp://... Cancel 取消 OK 确定 Unmount Еңсерілмеу Eject Эжек Format Open In New Window Open In New Tab format пішімі Property Сипат You have to mount this volume first Бұл көлемді алдымен орнату керек Peony::DriveRename Rename Атын өзгерту Device name: Құрылғының атауы: Warning Ескерту Renaming cannot start with a decimal point, Please re-enter! Атауын өзгерту ондық нүктеден басталмауы мүмкін, Қайта енгізуіңізді сұраймын! The device name exceeds the character limit, rename failed! Құрылғының атауы таңба шегінен асып түседі, атын өзгерту сәтсіз аяқталды! Renaming will unmount the device. Do you want to continue? Атын өзгерту құрылғыны қайта атауға мүмкіндік береді. Жалғастырғыңыз келе ме? The device may not support the rename operation, rename failed! Құрылғы атын өзгерту операциясын қолдамауы мүмкін, атын өзгерту жаңылысы! QObject Computer View Компьютер көрінісі Show drives, network and personal directories Show drives, network and personal directories. Дискілерді, желілік және жеке каталогтарды көрсету. One or more programs prevented the unmount operation. Бір немесе бірнеше бағдарлама еңсерілмейтін операцияға кедергі келтірді. Unmount failed Ð Ð°Ñ Ð1/2аÐ1/2 Error: %1 Do you want to unmount forcely? 错误:%1 是否强制卸载? It need to synchronize before operating the device,place wait! Құрылғыны пайдаланбас бұрын синхрондау керек, күте тұрыңыз! The device has been mount successfully! Құрылғы сәтті орнатылды! Data synchronization is complete,the device has been unmount successfully! Деректерді синхрондау аяқталды, құрылғы сәтті өтті! Error: %1 Қате:% 1 Eject device failed, the reason may be that the device has been removed, etc. Unable to unmount it, you may need to close some programs, such as: GParted etc. Оны болдырмау мүмкін емес, кейбір бағдарламаларды жабу қажет болуы мүмкін, мысалы: GParted және т.б. %1 %1 Eject failed Шығарылу жаңылысы Cancel 取消 Eject Anyway 无论如何弹出 Warning Ескерту The device may not support the rename operation, rename failed! Құрылғы атын өзгерту операциясын қолдамауы мүмкін, атын өзгерту жаңылысы! Message recipient disconnected from message bus without replying! log remote error peony-extensions/peony-extension-computer-view/peony-computer-view-plugin.h0000664000175000017500000000430615156143137026413 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef PEONYCOMPUTERVIEWPLUGIN_H #define PEONYCOMPUTERVIEWPLUGIN_H #include "peony-extension-computer-view_global.h" #include namespace Peony { class PEONYQTEXTENSIONCOMPUTERVIEW_EXPORT PeonyComputerViewPlugin : public QObject, public Peony::DirectoryViewPluginIface2 { Q_OBJECT Q_PLUGIN_METADATA(IID DirectoryViewPluginIface2_iid FILE "common.json") Q_INTERFACES(Peony::DirectoryViewPluginIface2) public: explicit PeonyComputerViewPlugin(QObject *parent = nullptr); //common PluginType pluginType() {return PluginType::DirectoryViewPlugin2;} const QString name() {return QObject::tr("Computer View");} const QString description() {return QObject::tr("Show drives, network and personal directories");} const QIcon icon() {return QIcon::fromTheme("computer");} void setEnable(bool enable) {} bool isEnable() {return true;} //view QString viewIdentity() {return "Computer View";} QString viewName() {return name();} QIcon viewIcon() {return icon();} bool supportUri(const QString &uri) {return uri == "computer:///";} int zoom_level_hint() {return -1;} int minimumSupportedZoomLevel() {return -1;} int maximumSupportedZoomLevel() {return -1;} int priority(const QString &directoryUri); bool supportZoom() {return false;} DirectoryViewWidget *create(); }; } #endif // PEONYCOMPUTERVIEWPLUGIN_H peony-extensions/peony-extension-computer-view/peony-computer-view-plugin.cpp0000664000175000017500000000344415156143137026750 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "peony-computer-view-plugin.h" #include "computer-view-container.h" #include "intel-computer-view-container.h" #ifdef KYLIN_COMMON #include #endif #define V10_SP1_EDU "V10SP1-edu" #include #include #include Peony::PeonyComputerViewPlugin::PeonyComputerViewPlugin(QObject *parent) : QObject(parent) { QTranslator *t = new QTranslator(this); t->load(":/peony-extension-computer-view_"+QLocale::system().name()); QApplication::installTranslator(t); } int Peony::PeonyComputerViewPlugin::priority(const QString &directoryUri) { if (directoryUri == "computer:///") return 1; return -1; } Peony::DirectoryViewWidget *Peony::PeonyComputerViewPlugin::create() { #ifdef KYLIN_COMMON if (QString::fromStdString(KDKGetPrjCodeName()) == V10_SP1_EDU) { return new Intel::ComputerViewContainer; } else { return new ComputerViewContainer; } #else return new ComputerViewContainer; #endif } peony-extensions/peony-extension-computer-view/computer-view-intel/0000775000175000017500000000000015156143275024727 5ustar fengfengpeony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-view-style.h0000664000175000017500000000371415156143137032157 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #ifndef INTEL_COMPUTERVIEWSTYLE_H #define INTEL_COMPUTERVIEWSTYLE_H #include #include namespace Intel { class ComputerViewStyle : public QProxyStyle { public: static ComputerViewStyle *getStyle(); void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override; void drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override; void drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const override; void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const override; private: explicit ComputerViewStyle(QStyle *style = nullptr); ~ComputerViewStyle() override {} }; } #endif // COMPUTERVIEWSTYLE_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-view-style.cpp0000664000175000017500000000705415156143137032513 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #include "intel-computer-view-style.h" #include #include #include using namespace Intel; static ComputerViewStyle *global_instance = nullptr; ComputerViewStyle::ComputerViewStyle(QStyle *style) : QProxyStyle(style) { } ComputerViewStyle *ComputerViewStyle::getStyle() { if (!global_instance) global_instance = new ComputerViewStyle; return global_instance; } void ComputerViewStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == PE_Frame) { return; } return QProxyStyle::drawPrimitive(element, option, painter, widget); } void ComputerViewStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == QStyle::CE_RubberBand) { //qDebug()<<"draw control rubber band"; if (qstyleoption_cast(option)) { auto rect = option->rect; QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight); painter->save(); QColor penColor = highlight; penColor.setAlpha(180); painter->setPen(penColor); QColor dimHighlight(qMin(highlight.red()/2 + 110, 255), qMin(highlight.green()/2 + 110, 255), qMin(highlight.blue()/2 + 110, 255)); dimHighlight.setAlpha(widget && widget->isTopLevel() ? 255 : 80); QLinearGradient gradient(rect.topLeft(), QPoint(rect.bottomLeft().x(), rect.bottomLeft().y())); gradient.setColorAt(0, dimHighlight.lighter(120)); gradient.setColorAt(1, dimHighlight); painter->setRenderHint(QPainter::Antialiasing, true); painter->translate(0.5, 0.5); painter->setBrush(dimHighlight); painter->drawRoundedRect(option->rect.adjusted(0, 0, -1, -1), 1, 1); QColor innerLine = Qt::white; innerLine.setAlpha(40); painter->setPen(innerLine); painter->drawRoundedRect(option->rect.adjusted(1, 1, -2, -2), 1, 1); painter->restore(); } return; } QProxyStyle::drawControl(element, option, painter, widget); } void ComputerViewStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const { //qDebug()<<"drawItemPixmap"; QProxyStyle::drawItemPixmap(painter, rect, alignment, pixmap); } void ComputerViewStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const { //qDebug()<<"drawItemText"; QProxyStyle::drawItemText(painter, rect, flags, pal, enabled, text, textRole); } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-item-delegate.h0000664000175000017500000000376015156143137032556 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERITEMDELEGATE_H #define INTEL_COMPUTERITEMDELEGATE_H #include class QListView; class AbstractComputerItem; namespace Intel { class ComputerItemDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit ComputerItemDelegate(QObject *parent = nullptr); ~ComputerItemDelegate(); protected: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paintVolumeItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const; void paintRemoteItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const; void paintNetworkItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const; void drawTab(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const; void drawStyledItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; private: QListView *m_styleIconView; }; } #endif // COMPUTERITEMDELEGATE_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-view.cpp0000664000175000017500000003624015156143275031357 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-computer-view.h" #include "intel-computer-proxy-model.h" #include "intel-abstract-computer-item.h" #include "intel-computer-item-delegate.h" #include "intel-computer-view-style.h" #include #include #include #include #include using namespace Intel; ComputerView::ComputerView(QWidget *parent) : QAbstractItemView(parent) { this->verticalScrollBar()->setProperty("drawScrollBarGroove", false); setAttribute(Qt::WA_TranslucentBackground); viewport()->setAttribute(Qt::WA_TranslucentBackground); setItemDelegate(new ComputerItemDelegate(this)); m_model = ComputerProxyModel::globalInstance(); setModel(m_model); setStyle(ComputerViewStyle::getStyle()); setStyle(ComputerViewStyle::getStyle()); m_rubberBand = new QRubberBand(QRubberBand::Shape::Rectangle, this); connect(this, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &index){ qDebug()<selectionModel(), &QItemSelectionModel::selectionChanged, this, [=](){ this->viewport()->update(); }); //fix #18184 auto volumeManager = Peony::VolumeManager::getInstance(); connect(volumeManager,&Peony::VolumeManager::volumeRemoved,this,[=](const std::shared_ptr volume){ this->viewport()->update(); }); setSelectionMode(QAbstractItemView::ExtendedSelection); this->viewport()->setMouseTracking(true); this->viewport()->installEventFilter(this); // for (int row = 0; row < m_model->rowCount(); row++) { // m_items.push_back(ComputerViewItem(row, 0, false)); // } } bool ComputerView::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::MouseMove) { if (!m_isLeftButtonPressed) { auto pos = mapFromGlobal(QCursor::pos()); auto newIndex = indexAt(pos); if (newIndex != m_hoverIndex) { m_hoverIndex = newIndex; this->viewport()->update(); } } else { m_hoverIndex = QModelIndex(); } } return false; } QRect ComputerView::visualRect(const QModelIndex &index) const { return m_rect_cache.value(index); } void ComputerView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint) { //return; //FIXME: scroll to the index more accurecely. //auto y = m_rect_cache.value(index).y(); //verticalScrollBar()->setValue(y); } QModelIndex ComputerView::indexAt(const QPoint &point) const { auto pos = point + QPoint(horizontalOffset(), verticalOffset()); for (auto index : m_rect_cache.keys()) { auto rect = m_rect_cache.value(index); if (rect.contains(pos)) return index; } return QModelIndex(); } QModelIndex ComputerView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { return QModelIndex(); } int ComputerView::horizontalOffset() const { return horizontalScrollBar()->value()*m_scrollStep; } int ComputerView::verticalOffset() const { return verticalScrollBar()->value()*m_scrollStep; } bool ComputerView::isIndexHidden(const QModelIndex &index) const { return false; } void ComputerView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) { //caculate logic rubber band rect in mouse move event. //qDebug()<<"setSelection"<isVisible()) { auto realRect = m_rubberBand->geometry(); realRect.adjust(horizontalOffset(), verticalOffset(), horizontalOffset(), verticalOffset()); for (auto index : m_rect_cache.keys()) { auto indexRect = m_rect_cache.value(index); if (realRect.contains(indexRect.center())) { selectionModel()->select(index, QItemSelectionModel::Select); } else { selectionModel()->select(index, QItemSelectionModel::Deselect); } } } else { auto pos = rect.center(); auto index = indexAt(pos); if (!index.isValid()) { clearSelection(); return; } if (!selectedIndexes().contains(index)) selectionModel()->select(index, QItemSelectionModel::SelectCurrent); } } QRegion ComputerView::visualRegionForSelection(const QItemSelection &selection) const { return QRegion(); } QString ComputerView::tryGetVolumeUriFromMountTarget(const QString &mountTargetUri) { return m_model->tryGetVolumeUriFromMountTarget(mountTargetUri); } void ComputerView::refresh() { m_model->refresh(); } void ComputerView::updateEditorGeometries() { QAbstractItemView::updateEditorGeometries(); m_totalHeight = 0; m_totalWidth = this->viewport()->width(); m_rect_cache.clear(); for (int row = 0; row < m_model->rowCount(); row++) { auto index = m_model->index(row, 0); auto item = m_model->itemFromIndex(index); switch (item->itemType()) { case AbstractComputerItem::Volume: { layoutVolumeIndexes(index); break; } case AbstractComputerItem::RemoteVolume: { layoutRemoteIndexes(index); break; } case AbstractComputerItem::Network: { layoutNetworkIndexes(index); break; } default: break; } } //confirm total width for (auto rect : m_rect_cache.values()) { if (rect.right() > m_totalWidth) m_totalWidth = rect.right(); } horizontalScrollBar()->setRange(0, qMax(0, m_totalWidth - viewport()->width())); verticalScrollBar()->setRange(0, qMax(0, (m_totalHeight - viewport()->height() + 200)/m_scrollStep)); //update tab index rect width for (auto index : m_rect_cache.keys()) { if (!index.parent().isValid()) { auto rect = m_rect_cache.value(index); rect.setWidth(m_totalWidth); m_rect_cache.remove(index); m_rect_cache.insert(index, rect); } } } void ComputerView::resizeEvent(QResizeEvent *event) { QAbstractItemView::resizeEvent(event); updateEditorGeometries(); } void ComputerView::paintEvent(QPaintEvent *e) { QPainter p(this->viewport()); // p.fillRect(this->rect(), palette().base()); //p.fillRect(QRect(0, 0, m_totalWidth, m_totalHeight), Qt::blue); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::transparent); p.setBrush(this->palette().base()); p.drawRoundedRect(this->rect(),24,24, Qt::AbsoluteSize); p.drawRect(0, 0, 24, 24); p.drawRect(0, rect().height() - 24, 24, 24); p.drawRect(rect().width() - 24, 0, 24, 24); auto w = window(); QPoint l = this->mapTo(w,QPoint(this->rect().right(),this->rect().height())); if(w->rect().right()-l.x()>100||window()->isMaximized()){ p.drawRect(rect().width()-24,rect().height()-24,24,24); } p.save(); p.translate(-horizontalOffset(), -verticalOffset()); for (auto rect : m_rect_cache) { //qDebug()<paint(&p, opt, index); } p.restore(); } void ComputerView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_isLeftButtonPressed = true; m_rubberBand->hide(); m_lastPressedPoint = event->pos(); m_lastPressedLogicPoint = event->pos() + QPoint(horizontalOffset(), verticalOffset()); auto index = indexAt(event->pos()); if (!index.parent().isValid() && index.isValid()) { if (isExpanded(index)) { expand(index, false); } else { expand(index, true); } updateEditorGeometries(); this->viewport()->update(); return; } } else m_rubberBand->hide(); QAbstractItemView::mousePressEvent(event); this->viewport()->update(); } void ComputerView::mouseMoveEvent(QMouseEvent *event) { QAbstractItemView::mouseMoveEvent(event); if (m_isLeftButtonPressed) { auto pos = event->pos(); auto offset = QPoint(horizontalOffset(), verticalOffset()); auto logicPos = pos + offset; QRect logicRect = QRect(logicPos, m_lastPressedLogicPoint); m_logicRect = logicRect.normalized(); int dx = -horizontalOffset(); int dy = -verticalOffset(); auto realRect = m_logicRect.adjusted(dx, dy, dx ,dy); if (!m_rubberBand->isVisible()) m_rubberBand->show(); m_rubberBand->setGeometry(realRect); } else { m_rubberBand->hide(); } } void ComputerView::mouseReleaseEvent(QMouseEvent *event) { m_rubberBand->hide(); m_isLeftButtonPressed = false; QAbstractItemView::mouseReleaseEvent(event); } void ComputerView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QAbstractItemView::dataChanged(topLeft, bottomRight, roles); this->viewport()->update(); } void ComputerView::layoutVolumeIndexes(const QModelIndex &volumeParentIndex) { int rowCount = m_model->rowCount(volumeParentIndex); if (rowCount <= 0) return; //add tab m_rect_cache.insert(volumeParentIndex, QRect(QPoint(0, m_totalHeight), QSize(this->viewport()->width(), m_tabPadding))); m_totalHeight += m_tabPadding + 10; if (!isExpanded(volumeParentIndex)) return; //layout indexes int tmpX = 0; int maxColumnCount = 0; //if view's width is not enough, expand the m_totalWidth to ensure scroallble if (m_totalWidth < 2 * (m_hSpacing + m_volumeItemFixedSize.width())) { maxColumnCount = 1; } else { maxColumnCount = this->viewport()->width()/(m_hSpacing + m_volumeItemFixedSize.width());// - 1; } int cloumn = 0; for (int row = 0; row < rowCount; row++) { //layout next row if (cloumn > maxColumnCount - 1) { //m_totalWidth = tmpX + m_hSpacing + m_volumeItemFixedSize.width(); cloumn = 1; tmpX = m_hSpacing; m_totalHeight = m_totalHeight + m_volumeItemFixedSize.height() + m_vSpacing; } else { tmpX = m_hSpacing + cloumn*(m_hSpacing+m_volumeItemFixedSize.width()); cloumn++; } auto index = m_model->index(row, 0, volumeParentIndex); m_rect_cache.insert(index, QRect(QPoint(tmpX, m_totalHeight), m_volumeItemFixedSize)); } m_totalHeight = m_totalHeight + m_volumeItemFixedSize.height() + m_vSpacing; } void ComputerView::layoutRemoteIndexes(const QModelIndex &remoteParentIndex) { int rowCount = m_model->rowCount(remoteParentIndex); if (rowCount <= 0) return; //add tab m_rect_cache.insert(remoteParentIndex, QRect(QPoint(0, m_totalHeight), QSize(this->viewport()->width(), m_tabPadding))); m_totalHeight += m_tabPadding + 10; if (!isExpanded(remoteParentIndex)) return; //layout indexes int tmpX = 0; int maxColumnCount = 0; //if view's width is not enough, expand the m_totalWidth to ensure scroallble if (m_totalWidth < 2 * (m_hSpacing + m_remoteItemFixedSize.width())) { //m_totalWidth = m_hSpacing + m_remoteItemFixedSize.width(); maxColumnCount = 1; } else { maxColumnCount = this->viewport()->width()/(m_hSpacing + m_remoteItemFixedSize.width());// - 1; } int cloumn = 0; for (int row = 0; row < rowCount; row++) { //layout next row if (cloumn > maxColumnCount - 1) { //m_totalWidth = tmpX + m_hSpacing + m_volumeItemFixedSize.width(); cloumn = 1; tmpX = m_hSpacing; m_totalHeight = m_totalHeight + m_remoteItemFixedSize.height() + m_vSpacing; } else { tmpX = m_hSpacing + cloumn*(m_hSpacing+m_remoteItemFixedSize.width()); cloumn++; } auto index = m_model->index(row, 0, remoteParentIndex); m_rect_cache.insert(index, QRect(QPoint(tmpX, m_totalHeight), m_remoteItemFixedSize)); } m_totalHeight = m_totalHeight + m_remoteItemFixedSize.height() + m_vSpacing; } void ComputerView::layoutNetworkIndexes(const QModelIndex &networkParentIndex) { int rowCount = m_model->rowCount(networkParentIndex); if (rowCount <= 0) return; //add tab m_rect_cache.insert(networkParentIndex, QRect(QPoint(0, m_totalHeight), QSize(this->viewport()->width(), m_tabPadding))); m_totalHeight += m_tabPadding + 10; if(!isExpanded(networkParentIndex)) return; //layout indexes int tmpX = 0; int maxColumnCount = 0; //if view's width is not enough, expand the m_totalWidth to ensure scroallble if (m_totalWidth < 2 * (m_hSpacing + m_networkItemFixedSize.width())) { maxColumnCount = 1; } else { maxColumnCount = this->viewport()->width()/(m_hSpacing + m_networkItemFixedSize.width());// - 1; } int cloumn = 0; for (int row = 0; row < rowCount; row++) { //layout next row if (cloumn > maxColumnCount - 1) { //m_totalWidth = tmpX + m_hSpacing + m_volumeItemFixedSize.width(); cloumn = 1; tmpX = m_hSpacing; m_totalHeight = m_totalHeight + m_networkItemFixedSize.height() + m_vSpacing; } else { tmpX = m_hSpacing + cloumn*(m_hSpacing+m_networkItemFixedSize.width()); cloumn++; } auto index = m_model->index(row, 0, networkParentIndex); m_rect_cache.insert(index, QRect(QPoint(tmpX, m_totalHeight), m_networkItemFixedSize)); } m_totalHeight = m_totalHeight + m_networkItemFixedSize.height() + m_vSpacing; } bool ComputerView::isExpanded(const QModelIndex &index) { auto item = m_model->itemFromIndex(index); return item->isExpanded(); } void ComputerView::expand(const QModelIndex &index, bool expand) { auto item = m_model->itemFromIndex(index); if (item == nullptr) return; item->expand(expand); } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-view-container.cpp0000664000175000017500000002361215156143275033336 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-computer-view-container.h" #include "intel-computer-view.h" #include "intel-computer-proxy-model.h" #include "intel-abstract-computer-item.h" #include "../login-remote-filesystem.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Peony; using namespace Intel; static void ask_question_cb(GMountOperation *op, char *message, char **choices, ComputerViewContainer *p_this); static void ask_password_cb(GMountOperation *op, const char *message, const char *default_user, const char *default_domain, GAskPasswordFlags flags, ComputerViewContainer *p_this); static GAsyncReadyCallback mount_enclosing_volume_callback(GFile *volume, GAsyncResult *res, ComputerViewContainer *p_this) { GError *err = nullptr; g_file_mount_enclosing_volume_finish (volume, res, &err); if ((nullptr == err) || (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED))) { qDebug() << "login successful!"; Q_EMIT p_this->updateWindowLocationRequest(p_this->m_remote_uri); } else { qDebug() << "login remote error: " <code<message<domain; QMessageBox::warning(nullptr, QObject::tr("log remote error"), err->message, QMessageBox::Ok); } if (nullptr != err) { g_error_free(err); } return nullptr; } void aborted_cb(GMountOperation *op, ComputerViewContainer *p_this) { g_mount_operation_reply(op, G_MOUNT_OPERATION_ABORTED); p_this->disconnect(); } ComputerViewContainer::ComputerViewContainer(QWidget *parent) : DirectoryViewWidget(parent) { setContentsMargins(0, 0, 0, 0); setAttribute(Qt::WA_TranslucentBackground); setContextMenuPolicy(Qt::NoContextMenu); m_op = g_mount_operation_new(); g_signal_connect (m_op, "aborted", G_CALLBACK (aborted_cb), this); connect(this, &QWidget::customContextMenuRequested, this, [=](const QPoint &pos){ auto selectedIndexes = m_view->selectionModel()->selectedIndexes(); auto index = m_view->indexAt(pos); if (!selectedIndexes.contains(index)) m_view->clearSelection(); if (index.isValid()) { m_view->selectionModel()->select(index, QItemSelectionModel::SelectCurrent); } QMenu menu; auto model = static_cast(m_view->model()); QStringList uris; QList items; for (auto index : m_view->selectionModel()->selectedIndexes()) { auto item = model->itemFromIndex(index); uris<uri(); items<deleteLater(); auto code = dlg->exec(); if (code == QDialog::Rejected) { return; } QUrl url = dlg->uri(); ConnectServerLogin* dlgLogin = new ConnectServerLogin(url.host()); dlgLogin->deleteLater(); code = dlgLogin->exec(); if (code == QDialog::Rejected) { return; } g_mount_operation_set_username(m_op, dlgLogin->user().toUtf8().constData()); g_mount_operation_set_password(m_op, dlgLogin->password().toUtf8().constData()); // g_mount_operation_set_domain(m_op, dlg->domain().toUtf8().constData()); g_mount_operation_set_anonymous(m_op, dlgLogin->anonymous()); g_mount_operation_set_password_save(m_op, dlgLogin->savePassword()? G_PASSWORD_SAVE_NEVER: G_PASSWORD_SAVE_FOR_SESSION); GFile* m_volume = g_file_new_for_uri(dlg->uri().toUtf8().constData()); m_remote_uri = dlg->uri(); g_file_mount_enclosing_volume(m_volume, G_MOUNT_MOUNT_NONE, m_op, nullptr, GAsyncReadyCallback(mount_enclosing_volume_callback), this); g_signal_connect (m_op, "ask-question", G_CALLBACK(ask_question_cb), this); g_signal_connect (m_op, "ask-password", G_CALLBACK (ask_password_cb), this); }); #endif } else if (items.count() == 1 && items.first()->uri() != "") { auto item = items.first(); bool unmountable = item->canUnmount(); menu.addAction(tr("Unmount"), [=](){ item->unmount(G_MOUNT_UNMOUNT_NONE); }); menu.actions().first()->setEnabled(unmountable); /*eject function for volume. fix #18216*/ auto ejectAction = menu.addAction(tr("Eject"), [=](){ item->eject(G_MOUNT_UNMOUNT_NONE); }); ejectAction->setEnabled(item->canEject()); auto uri = item->uri(); auto realUri = m_view->tryGetVolumeUriFromMountTarget(uri); if (!realUri.isEmpty()) { uri = realUri; } auto a = menu.addAction(tr("Property"), [=](){ if (uri.isNull()) { QMessageBox::warning(0, 0, tr("You have to mount this volume first")); } else { QProcess p; p.setProgram("peony"); QStringList args; args<<"-p"<setEnabled(!uri.isNull()); } else if(items.first()->uri() != ""){ qDebug() << "unable Property uri:" <uri(); menu.addAction(tr("Property")); menu.actions().first()->setEnabled(false); } menu.exec(QCursor::pos()); }); } static void ask_question_cb(GMountOperation *op, char *message, char **choices, ComputerViewContainer *p_this) { g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED); } static void ask_password_cb(GMountOperation *op, const char *message, const char *default_user, const char *default_domain, GAskPasswordFlags flags, ComputerViewContainer *p_this) { Q_UNUSED(message); Q_UNUSED(default_user); Q_UNUSED(default_domain); Q_UNUSED(flags); Q_UNUSED(p_this); g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED); } ComputerViewContainer::~ComputerViewContainer() { if (nullptr != m_op) { g_object_unref(m_op); } } const QStringList ComputerViewContainer::getSelections() { QStringList uris; auto model = static_cast(m_view->model()); for (auto index : m_view->selectionModel()->selectedIndexes()) { auto item = model->itemFromIndex(index); uris<uri(); } return uris; } void ComputerViewContainer::paintEvent(QPaintEvent *e) { DirectoryViewWidget::paintEvent(e); } void ComputerViewContainer::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (m_enterAction) m_enterAction->triggered(); e->accept(); return; } QWidget::keyPressEvent(e); } void ComputerViewContainer::bindModel(FileItemModel *model, FileItemProxyFilterSortModel *proxyModel) { m_model = model; m_proxyModel = proxyModel; model->setRootUri("computer:///"); connect(model, &FileItemModel::findChildrenFinished, this, &DirectoryViewWidget::viewDirectoryChanged); if (m_view) m_view->deleteLater(); m_view = new ComputerView(this); auto layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_view); setLayout(layout); Q_EMIT viewDirectoryChanged(); auto selectionModel = m_view->selectionModel(); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &DirectoryViewWidget::viewSelectionChanged);; connect(m_view, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &index){ if (!index.parent().isValid()) return; auto model = static_cast(m_view->model()); auto item = model->itemFromIndex(index); if (!item->uri().isEmpty()) { item->check(); this->updateWindowLocationRequest(item->uri()); } else { item->mount(); } }); m_enterAction = new QAction(this); m_enterAction->setShortcut(Qt::Key_Enter); addAction(m_enterAction); connect(m_enterAction, &QAction::triggered, this, [=](){ if (m_view->selectionModel()->selectedIndexes().count() == 1) { Q_EMIT m_view->doubleClicked(m_view->selectionModel()->selectedIndexes().first()); } }); } void ComputerViewContainer::beginLocationChange() { Q_EMIT viewDirectoryChanged(); m_view->refresh(); } void ComputerViewContainer::stopLocationChange() { Q_EMIT viewDirectoryChanged(); } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-view.h0000664000175000017500000000637715156143137031031 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERVIEW_H #define INTEL_COMPUTERVIEW_H #include #include class QRubberBand; namespace Intel { class ComputerProxyModel; class ComputerView : public QAbstractItemView { Q_OBJECT public: explicit ComputerView(QWidget *parent = nullptr); bool eventFilter(QObject *object, QEvent *event); virtual QRect visualRect(const QModelIndex &index) const; virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); virtual QModelIndex indexAt(const QPoint &point) const; virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); virtual int horizontalOffset() const; virtual int verticalOffset() const; virtual bool isIndexHidden(const QModelIndex &index) const; virtual void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); virtual QRegion visualRegionForSelection(const QItemSelection &selection) const; QString tryGetVolumeUriFromMountTarget(const QString &mountTargetUri); void refresh(); protected: void updateEditorGeometries(); void resizeEvent(QResizeEvent *event); void paintEvent(QPaintEvent *e); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()); void layoutVolumeIndexes(const QModelIndex &volumeParentIndex); void layoutRemoteIndexes(const QModelIndex &remoteParentIndex); void layoutNetworkIndexes(const QModelIndex &networkParentIndex); // add by wwn bool isExpanded(const QModelIndex& index); void expand(const QModelIndex& index, bool expand); private: ComputerProxyModel *m_model; QRubberBand *m_rubberBand; QPoint m_lastPressedPoint; QPoint m_lastPressedLogicPoint; QRect m_logicRect; bool m_isLeftButtonPressed = false; QModelIndex m_hoverIndex; int m_scrollStep = 100; int m_totalHeight = 0; int m_totalWidth = 0; int m_hSpacing = 20; int m_vSpacing = 20; int m_tabPadding = 36; QSize m_volumeItemFixedSize = QSize(256, 108); QSize m_remoteItemFixedSize = QSize(108, 144); QSize m_networkItemFixedSize = QSize(108, 144); QHash m_rect_cache; }; } #endif // COMPUTERVIEW_H peony-extensions/peony-extension-computer-view/computer-view-intel/img/0000775000175000017500000000000015156143137025500 5ustar fengfengpeony-extensions/peony-extension-computer-view/computer-view-intel/img/go-down-symbolic.png0000664000175000017500000000052715156143137031403 0ustar fengfengPNG  IHDRasRGBDeXIfMM*i4UqIDAT8c`NXbE;_ķxxx,|||ރl"˗@wDDDTABAHqqڗ/_y_~]~O̫WV)5FF=bbb 1@qH6i g)WAu~}QlllAAA \Ʉ`"XP@0l:pjժXң.IAP IENDB`peony-extensions/peony-extension-computer-view/computer-view-intel/img/go-previous-symbolic.png0000664000175000017500000000053015156143137032302 0ustar fengfengPNG  IHDRasRGBDeXIfMM*i4UqIDAT8c``Ŋg!C&< *^z}˖-x03]|rbݺuj i##F_~Xr 6Cp^LLh4F7Z*+g% _BA^B8 x@t E' 6 1|a!..O3.C8Q;M IENDB`peony-extensions/peony-extension-computer-view/computer-view-intel/img/go-previous-symbolic.svg0000664000175000017500000000130015156143137032311 0ustar fengfeng peony-extensions/peony-extension-computer-view/computer-view-intel/img/go-down-symbolic.svg0000664000175000017500000000124615156143137031415 0ustar fengfeng peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-proxy-model.cpp0000664000175000017500000000416415156143137032661 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-computer-proxy-model.h" #include "intel-computer-model.h" #include "intel-abstract-computer-item.h" using namespace Intel; static ComputerProxyModel *global_instance = nullptr; ComputerProxyModel::ComputerProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { auto computerModel = new ComputerModel(this); setSourceModel(computerModel); m_model = computerModel; } ComputerProxyModel *ComputerProxyModel::globalInstance() { if (!global_instance) { global_instance = new ComputerProxyModel; } return global_instance; } AbstractComputerItem *ComputerProxyModel::itemFromIndex(const QModelIndex &proxyIndex) { auto index = mapToSource(proxyIndex); return static_cast(index.internalPointer()); } QString ComputerProxyModel::tryGetVolumeUriFromMountTarget(const QString &mountTargetUri) { return m_model->tryGetVolumeUriFromMountRoot(mountTargetUri); } void ComputerProxyModel::refresh() { m_model->refresh(); } bool ComputerProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { auto item = static_cast(m_model->index(source_row, 0, source_parent).internalPointer()); if (item->itemType() == AbstractComputerItem::RemoteVolume) { return !item->isHidden(); } return true; } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-network-item.h0000664000175000017500000000437715156143137032502 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERNETWORKITEM_H #define INTEL_COMPUTERNETWORKITEM_H #include "intel-abstract-computer-item.h" #include #include namespace Intel { class ComputerNetworkItem : public AbstractComputerItem { Q_OBJECT public: explicit ComputerNetworkItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); ~ComputerNetworkItem(); Type itemType() override {return Network;} const QString uri() override {return m_uri;} const QString displayName() override; const QIcon icon() override {return m_icon;} void findChildren() override; void updateInfo() override; QModelIndex itemIndex() override; public Q_SLOTS: void reloadDirectory(const QString &uri); void onFileAdded(const QString &uri); void onFileRemoved(const QString &uri); void onFileChanged(const QString &uri); protected: //enumeration static void enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this); static void find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerNetworkItem *p_this); //info static void query_info_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this); private: QString m_uri; QString m_displayName; QIcon m_icon; GCancellable *m_cancellable; Peony::FileWatcher *m_watcher = nullptr; }; } #endif // COMPUTERNETWORKITEM_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-model.h0000664000175000017500000000444415156143137031150 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERMODEL_H #define INTEL_COMPUTERMODEL_H #include #include namespace Intel { class AbstractComputerItem; class ComputerModel : public QAbstractItemModel { friend class ComputerNetworkItem; friend class ComputerVolumeItem; friend class ComputerRemoteVolumeItem; Q_OBJECT public: explicit ComputerModel(QObject *parent = nullptr); void beginInsertItem(const QModelIndex &parent, int row); void endInsterItem(); void beginRemoveItem(const QModelIndex &parent, int row); void endRemoveItem(); QModelIndex createItemIndex(int row, QObject *item); // Basic functionality: QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // Editable: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; QString tryGetVolumeUriFromMountRoot(const QString &mountRootUri); void refresh(); private: AbstractComputerItem *m_parentNode; QMap m_volumeTargetMap; }; } #endif // COMPUTERMODEL_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-item-delegate.cpp0000664000175000017500000002425015156143137033106 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include #include "intel-computer-view.h" #include "intel-computer-proxy-model.h" #include "intel-computer-item-delegate.h" #include "intel-abstract-computer-item.h" #include #include #include #include #include #include using namespace Intel; ComputerItemDelegate::ComputerItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { m_styleIconView = new QListView; m_styleIconView->setViewMode(QListView::IconMode); } ComputerItemDelegate::~ComputerItemDelegate() { m_styleIconView->deleteLater(); } void ComputerItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); //use rounded rect primitive in ukui style. if (index.parent().isValid()) { opt.decorationPosition = QStyleOptionViewItem::Top; opt.decorationSize = QSize(64, 64); opt.features = QStyleOptionViewItem::WrapText; opt.displayAlignment = Qt::AlignHCenter|Qt::AlignTop; opt.rect.adjust(2, 2, -2, -2); //opt.features.setFlag(QStyleOptionViewItem::WrapText); } auto view = qobject_cast(parent()); auto model = qobject_cast(view->model()); auto item = model->itemFromIndex(index); if (!item) return; switch (item->itemType()) { case AbstractComputerItem::Volume: paintVolumeItem(painter, opt, index, item); break; case AbstractComputerItem::RemoteVolume: paintRemoteItem(painter, opt, index, item); break; case AbstractComputerItem::Network: paintNetworkItem(painter, opt, index, item); break; default: break; } } void ComputerItemDelegate::paintVolumeItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const { auto opt = option; auto bg = opt.palette.highlight().color(); int hue = bg.hue(); //int s = bg.saturation(); // int v = bg.value(); bg.setHsv(hue, 10, 127); opt.palette.setColor(QPalette::Highlight, bg); //qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, m_styleIconView); bool isHover = (option.state & QStyle::State_MouseOver) && (option.state & ~QStyle::State_Selected); bool isSelected = option.state & QStyle::State_Selected; bool enable = option.state & QStyle::State_Enabled; QColor color = option.palette.color(enable? QPalette::Active: QPalette::Disabled, QPalette::Highlight); color.setAlpha(0); if (isHover && !isSelected) { int h = color.hsvHue(); //int s = color.hsvSaturation(); auto base = option.palette.base().color(); int v = color.value(); color.setHsv(h, base.lightness(), v, 64); } if (isSelected) { color.setAlpha(127); } painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->setPen(Qt::transparent); painter->setBrush(color); painter->drawRoundedRect(option.rect, 6, 6); painter->restore(); if (index.parent().isValid()) { painter->save(); painter->setRenderHint(QPainter::Antialiasing); //draw pix map QIcon icon = option.icon; bool enable = option.state & QStyle::State_Enabled; bool selected = option.state &QStyle::State_Selected; QPixmap pix = icon.pixmap(QSize(64, 64), enable? selected? QIcon::Selected: QIcon::Normal: QIcon::Disabled); qApp->style()->drawItemPixmap(painter, option.rect.adjusted(5, 0, 0, 0), Qt::AlignVCenter|Qt::AlignLeft, pix); //draw text auto textRect = option.rect; textRect.adjust(84, 10, -5, -10); textRect.translate(0, -option.fontMetrics.ascent()); qApp->style()->drawItemText(painter, textRect, Qt::AlignLeft|Qt::AlignVCenter, option.palette, enable, option.text, QPalette::Text); //space bool shouldDrawProgress = false; QString spaceInfo; auto total = item->totalSpace(); auto used = item->usedSpace(); if (total > 0) { auto totalFormat = g_format_size(total); auto usedFormat = g_format_size(used); spaceInfo = QString("%1/%2").arg(usedFormat).arg(totalFormat); g_free(totalFormat); g_free(usedFormat); shouldDrawProgress = true; } else { if (!item->isMount()) { spaceInfo = tr("You should mount volume first"); } } qApp->style()->drawItemText(painter, textRect.translated(0, 2*option.fontMetrics.ascent()), Qt::AlignLeft|Qt::AlignVCenter|Qt::TextWordWrap, option.palette, enable, spaceInfo, QPalette::WindowText); if (shouldDrawProgress) { painter->save(); QPainterPath clipPath; clipPath.addRoundedRect(option.rect, 6, 6); painter->setClipPath(clipPath); qreal percent = used*1.0/total*1.0; int progressBarWidth = option.rect.width() * percent; painter->save(); QPen pen; pen.setCapStyle(Qt::PenCapStyle::RoundCap); pen.setJoinStyle(Qt::PenJoinStyle::RoundJoin); pen.setWidth(6); pen.setColor(option.palette.midlight().color()); painter->setPen(pen); painter->drawLine(option.rect.bottomLeft() + QPoint(5, -5), option.rect.bottomRight() + QPoint(-5, -5)); //pen.setColor(percent < 0.8?Qt::blue:Qt::red); if (percent < 0.8) { pen.setColor(option.palette.highlight().color()); } else { pen.setColor(Qt::red); } painter->setPen(pen); auto pos = option.rect.bottomLeft(); pos.setX(pos.x() + progressBarWidth); pos.setY(pos.y() - 5); painter->drawLine(option.rect.bottomLeft() + QPoint(5, -5), pos); painter->restore(); painter->restore(); } painter->restore(); } else { //auto textRect = qApp->style()->subElementRect(QStyle::SE_ItemViewItemText, &option, m_styleIconView); drawTab(painter, option, index, item); } } void ComputerItemDelegate::paintRemoteItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const { if (index.parent().isValid()) { qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); drawStyledItem(painter, option, index); } else { drawTab(painter, option, index, item); } } void ComputerItemDelegate::paintNetworkItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const { if (index.parent().isValid()) { qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); drawStyledItem(painter, option, index); } else { drawTab(painter, option, index, item); } } void ComputerItemDelegate::drawTab(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, AbstractComputerItem *item) const { auto opt = option; auto titleFont = opt.font; // Modify tab name is smaller if (titleFont.pixelSize() > 0) { titleFont.setPixelSize(titleFont.pixelSize()*1.2); } else { titleFont.setPointSizeF(titleFont.pointSizeF()*1.2); } opt.icon = QIcon(); opt.decorationPosition = QStyleOptionViewItem::Right; opt.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; opt.font = titleFont; opt.fontMetrics = QFontMetrics(opt.font); qApp->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter); //auto textRect = qApp->style()->subElementRect(QStyle::SE_ItemViewItemText, &opt, m_styleIconView); //qApp->style()->drawItemText(painter, opt.rect.adjusted(5, 0, 0, 0), Qt::AlignTop|Qt::AlignVCenter, option.palette, enable, option.text, selected? QPalette::HighlightedText: QPalette::Text); // add by wwn painter->setPen(QPen(QColor(125,125,125,125), 1)); painter->drawLine(opt.rect.bottomLeft().x() + 110, opt.rect.center().y() + 3 , opt.rect.bottomRight().x() - 50, opt.rect.center().y() + 3); QRect rect = option.rect; rect.setTop(rect.top() + 12); rect.setX(rect.right() - 40); rect.setSize(QSize(16, 16)); if (item->isExpanded()) painter->drawPixmap(rect, QPixmap(":/img/view_show")); else painter->drawPixmap(rect, QPixmap(":/img/view_hide")); } void ComputerItemDelegate::drawStyledItem(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); //draw pix map QIcon icon = option.icon; bool enable = option.state & QStyle::State_Enabled; bool selected = option.state &QStyle::State_Selected; QPixmap pix = icon.pixmap(QSize(64, 64), enable? selected? QIcon::Selected: QIcon::Normal: QIcon::Disabled); qApp->style()->drawItemPixmap(painter, option.rect.adjusted(0, 5, 0, 0), Qt::AlignTop|Qt::AlignHCenter, pix); //draw text auto textRect = option.rect.adjusted(2, 74, -2, -2); qApp->style()->drawItemText(painter, textRect, Qt::ElideRight|Qt::TextWrapAnywhere|Qt::AlignTop|Qt::AlignHCenter, option.palette, enable, option.text, selected? QPalette::HighlightedText: QPalette::Text); painter->restore(); } peony-extensions/peony-extension-computer-view/computer-view-intel/image.qrc0000664000175000017500000000030015156143137026506 0ustar fengfeng img/go-previous-symbolic.png img/go-down-symbolic.png peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-personal-item.h0000664000175000017500000000273615156143137032631 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERPERSONALITEM_H #define INTEL_COMPUTERPERSONALITEM_H #include "intel-abstract-computer-item.h" namespace Intel { class ComputerPersonalItem : public AbstractComputerItem { Q_OBJECT public: explicit ComputerPersonalItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); Type itemType() override {return Personal;} const QString uri() override {return m_uri;} const QString displayName() override; bool hasChildren() override {return !m_parentNode;} void findChildren() override; void clearChildren() override; private: QString m_uri; }; } #endif // COMPUTERPERSONALITEM_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-network-item.cpp0000664000175000017500000001526015156143137033026 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-computer-network-item.h" #include "intel-computer-model.h" #include using namespace Intel; ComputerNetworkItem::ComputerNetworkItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { m_cancellable = g_cancellable_new(); m_uri = uri; updateInfo(); } ComputerNetworkItem::~ComputerNetworkItem() { g_cancellable_cancel(m_cancellable); g_object_unref(m_cancellable); } const QString ComputerNetworkItem::displayName() { if (m_uri == "network:///") return tr("Network Neighborhood"); return m_displayName; } void ComputerNetworkItem::findChildren() { if (m_uri != "network:///") return; GFile *file = g_file_new_for_uri("network:///"); g_file_enumerate_children_async(file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, m_cancellable, GAsyncReadyCallback(enumerate_async_callback), this); g_object_unref(file); } void ComputerNetworkItem::updateInfo() { GFile *file = g_file_new_for_uri(m_uri.toUtf8().constData()); g_file_query_info_async(file, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 0, m_cancellable, GAsyncReadyCallback(query_info_async_callback), this); g_object_unref(file); } QModelIndex ComputerNetworkItem::itemIndex() { if (!m_parentNode) return m_model->createItemIndex(0, this); return m_model->createItemIndex(m_parentNode->m_children.indexOf(this), this); } void ComputerNetworkItem::reloadDirectory(const QString &uri) { if (m_uri != "network:///") return; m_model->beginResetModel(); for (auto item : m_children) { item->deleteLater(); } m_children.clear(); findChildren(); m_model->endResetModel(); } void ComputerNetworkItem::onFileAdded(const QString &uri) { for (auto item : m_children) { if (item->uri() == uri) return; } m_model->beginInsertItem(itemIndex(), m_children.count()); auto item = new ComputerNetworkItem(uri, m_model, this); m_children<endInsterItem(); } void ComputerNetworkItem::onFileRemoved(const QString &uri) { int row = -1; for (auto item : m_children) { if (item->uri() == uri) { row = m_children.indexOf(item); break; } } if (row < 0) return; m_model->beginRemoveItem(itemIndex(), row); auto item = m_children.takeAt(row); item->deleteLater(); m_model->endRemoveItem(); } void ComputerNetworkItem::onFileChanged(const QString &uri) { for (auto item : m_children) { if (item->uri() == uri) { item->updateInfo(); return; } } } void ComputerNetworkItem::enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this) { GError *err = nullptr; auto enumerator = g_file_enumerate_children_finish(file, res, &err); if (enumerator) { g_file_enumerator_next_files_async(enumerator, 9999, 0, p_this->m_cancellable, GAsyncReadyCallback(find_children_async_callback), p_this); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } } void ComputerNetworkItem::find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerNetworkItem *p_this) { GError *err = nullptr; auto infos = g_file_enumerator_next_files_finish(enumerator, res, &err); GList *l = infos; while (l) { auto info = G_FILE_INFO(l->data); l = l->next; if (!info) continue; auto file = g_file_enumerator_get_child(enumerator, info); if (!file) continue; auto uri = g_file_get_uri(file); if (!uri) continue; p_this->m_model->beginInsertItem(p_this->itemIndex(), p_this->m_children.count()); auto item = new ComputerNetworkItem(uri, p_this->m_model, p_this); p_this->m_children<m_model->endInsterItem(); g_free(uri); g_object_unref(file); } if (infos) g_list_free_full(infos, g_object_unref); if (enumerator) { g_file_enumerator_close(enumerator, nullptr, nullptr); g_object_unref(enumerator); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } if (p_this->m_watcher) { p_this->m_watcher->deleteLater(); } p_this->m_watcher = new Peony::FileWatcher("network:///", p_this); connect(p_this->m_watcher, &Peony::FileWatcher::directoryDeleted, p_this, &ComputerNetworkItem::reloadDirectory); connect(p_this->m_watcher, &Peony::FileWatcher::fileCreated, p_this, &ComputerNetworkItem::onFileAdded); connect(p_this->m_watcher, &Peony::FileWatcher::fileDeleted, p_this, &ComputerNetworkItem::onFileRemoved); connect(p_this->m_watcher, &Peony::FileWatcher::fileChanged, p_this, &ComputerNetworkItem::onFileChanged); p_this->m_watcher->startMonitor(); } void ComputerNetworkItem::query_info_async_callback(GFile *file, GAsyncResult *res, ComputerNetworkItem *p_this) { GError *err = nullptr; GFileInfo *info = g_file_query_info_finish(file, res, &err); if (info) { p_this->m_displayName = g_file_info_get_attribute_string(info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); GIcon *icon = g_file_info_get_icon(info); const gchar * const * names = g_themed_icon_get_names(G_THEMED_ICON(icon)); if (names) { const char *name = *names; if (name) { p_this->m_icon = QIcon::fromTheme(name); } } p_this->m_model->dataChanged(p_this->itemIndex(), p_this->itemIndex()); g_object_unref(info); } if (err) { //QMessageBox::critical(0, 0, err->message); g_error_free(err); } } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-abstract-computer-item.cpp0000664000175000017500000000237315156143137033141 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-abstract-computer-item.h" #include using namespace Intel; AbstractComputerItem::AbstractComputerItem(ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) { m_model = model; m_parentNode = parentNode; } AbstractComputerItem::~AbstractComputerItem() { for (auto child : m_children) { child->deleteLater(); } } QModelIndex AbstractComputerItem::itemIndex() { return QModelIndex(); } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-proxy-model.h0000664000175000017500000000276615156143137032334 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERPROXYMODEL_H #define INTEL_COMPUTERPROXYMODEL_H #include namespace Intel { class ComputerModel; class AbstractComputerItem; class ComputerProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit ComputerProxyModel(QObject *parent = nullptr); static ComputerProxyModel *globalInstance(); AbstractComputerItem *itemFromIndex(const QModelIndex &proxyIndex); QString tryGetVolumeUriFromMountTarget(const QString &mountTargetUri); void refresh(); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: ComputerModel *m_model; }; } #endif // COMPUTERPROXYMODEL_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-personal-item.cpp0000664000175000017500000000272715156143137033164 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-computer-personal-item.h" #include "intel-computer-model.h" #include using namespace Intel; ComputerPersonalItem::ComputerPersonalItem(const QString &uri, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { if (!parentNode) { m_uri = "file://" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation); } else { m_uri = uri; } } const QString ComputerPersonalItem::displayName() { //FIXME: return nullptr; } void ComputerPersonalItem::findChildren() { //FIXME: } void ComputerPersonalItem::clearChildren() { //FIXME: } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-model.cpp0000664000175000017500000001117415156143137031501 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-computer-model.h" #include "intel-abstract-computer-item.h" #include "intel-computer-volume-item.h" #include "intel-computer-remote-volume-item.h" #include "intel-computer-network-item.h" using namespace Intel; ComputerModel::ComputerModel(QObject *parent) : QAbstractItemModel(parent) { beginResetModel(); m_parentNode = new AbstractComputerItem(this, nullptr, this); auto computerItem = new ComputerVolumeItem(nullptr, this, m_parentNode); m_parentNode->m_children<findChildren(); computerItem->expand(true); // auto remoteItem = new ComputerRemoteVolumeItem("computer:///", this, m_parentNode); // m_parentNode->m_children<findChildren(); auto networkItem = new ComputerNetworkItem("network:///", this, m_parentNode); m_parentNode->m_children<findChildren(); endResetModel(); } void ComputerModel::beginInsertItem(const QModelIndex &parent, int row) { beginInsertRows(parent, row, row); } void ComputerModel::endInsterItem() { endInsertRows(); } void ComputerModel::beginRemoveItem(const QModelIndex &parent, int row) { beginRemoveRows(parent, row, row); } void ComputerModel::endRemoveItem() { endRemoveRows(); } QModelIndex ComputerModel::createItemIndex(int row, QObject *item) { return createIndex(row, 0, item); } QModelIndex ComputerModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { auto item = m_parentNode->m_children.at(row); return createIndex(row, column, item); } auto parentNode = static_cast(parent.internalPointer()); if (parentNode->m_children.count() < row) return QModelIndex(); return createIndex(row, column, parentNode->m_children.at(row)); } QModelIndex ComputerModel::parent(const QModelIndex &index) const { auto item = static_cast(index.internalPointer()); if (item->m_parentNode) { return item->m_parentNode->itemIndex(); } return QModelIndex(); } int ComputerModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) return m_parentNode->m_children.count(); auto item = static_cast(parent.internalPointer()); return item->m_children.count(); } int ComputerModel::columnCount(const QModelIndex &parent) const { return 1; } QVariant ComputerModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: return item->displayName(); case Qt::DecorationRole: { if (item->m_parentNode == m_parentNode) return QVariant(); return item->icon(); } case Qt::ToolTipRole: return item->displayName(); default: return QVariant(); } } bool ComputerModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (data(index, role) != value) { // FIXME: Implement me! Q_EMIT dataChanged(index, index, QVector() << role); return true; } return false; } Qt::ItemFlags ComputerModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return QAbstractItemModel::flags(index); // FIXME: Implement me! } QString ComputerModel::tryGetVolumeUriFromMountRoot(const QString &mountRootUri) { auto value = m_volumeTargetMap.value(mountRootUri); return value; } void ComputerModel::refresh() { for (auto child : m_parentNode->m_children) { for (auto child2 : child->m_children) { if (auto item = qobject_cast(child2)) { item->updateInfo(); } } } } peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-volume-item.h0000664000175000017500000001005415156143137032305 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERVOLUMEITEM_H #define INTEL_COMPUTERVOLUMEITEM_H #include "intel-abstract-computer-item.h" #include #include #include namespace Peony { class FileInfo; } namespace Intel { class ComputerVolumeItem : public AbstractComputerItem { Q_OBJECT public: explicit ComputerVolumeItem(GVolume *volume, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent = nullptr); explicit ComputerVolumeItem(const QString uri,ComputerModel *model,AbstractComputerItem *parentNode,QObject *parent = nullptr); ~ComputerVolumeItem(); void updateInfoAsync(); Type itemType() override {return Volume;} const QString uri() override {return m_uri;} const QString displayName() override; const QIcon icon() override; bool isMount() override; void findChildren() override; void updateInfo() override {updateInfoAsync();} void check() override; bool canEject() override; void eject(GMountUnmountFlags ejectFlag) override; bool canUnmount() override; void unmount(GMountUnmountFlags unmountFlag) override; void mount() override; QModelIndex itemIndex() override; qint64 usedSpace() override {return m_usedSpace;} qint64 totalSpace() override {return m_totalSpace;} protected: //monitor static void volume_changed_callback(GVolume *volume, ComputerVolumeItem *p_this); static void volume_removed_callback(GVolume *volume, ComputerVolumeItem *p_this); static void mount_changed_callback(GVolumeMonitor* volumeMonitor, GMount *gmount, ComputerVolumeItem *p_this); //info static void qeury_info_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this); static void query_root_info_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this); //mount op static void mount_async_callback(GVolume *volume, GAsyncResult *res, ComputerVolumeItem *p_this); static void unmount_async_callback(GObject *object, GAsyncResult *res, ComputerVolumeItem *p_this); static void eject_async_callback(GObject *object, GAsyncResult *res, ComputerVolumeItem *p_this); //watcher void collectInfoWhenGpartedOpen(QString uri); void onFileAdded(const QString &uri); void onFileRemoved(const QString &uri); //gparted void findChildrenWhenGPartedOpen(); static void enumerate_async_callback(GFile *file, GAsyncResult *res, ComputerVolumeItem *p_this); static void find_children_async_callback(GFileEnumerator *enumerator, GAsyncResult *res, ComputerVolumeItem *p_this); private Q_SLOTS: void onVolumeAdded(const std::shared_ptr volume); private: QString m_uri; std::shared_ptr m_volume = nullptr; std::shared_ptr m_mount = nullptr; GCancellable *m_cancellable = nullptr; GCancellable *m_tmpCancellable = nullptr; gulong m_volumeChangedHandle = 0; gulong m_mountChangedHandle = 0; gulong m_volumeRemovedHandle = 0; //info QString m_displayName; QIcon m_icon; qint64 m_totalSpace = 0; qint64 m_usedSpace = 0; Peony::FileWatcher *m_watcher = nullptr; QString m_targetUri; std::shared_ptr m_info = nullptr; }; } #endif // COMPUTERVOLUMEITEM_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-view-container.h0000664000175000017500000000637415156143137033006 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef INTEL_COMPUTERVIEWCONTAINER_H #define INTEL_COMPUTERVIEWCONTAINER_H #include #include #include #include namespace Intel { class ComputerView; } namespace Intel { class ComputerViewContainer : public Peony::DirectoryViewWidget { Q_OBJECT public: explicit ComputerViewContainer(QWidget *parent = nullptr); ~ComputerViewContainer(); const virtual QString viewId() {return "Computer View";} //location const virtual QString getDirectoryUri() {return "computer:///";} //selections const virtual QStringList getSelections(); //children const virtual QStringList getAllFileUris() {return QStringList();} virtual int getSortType() {return 0;} virtual Qt::SortOrder getSortOrder() {return Qt::AscendingOrder;} //zoom virtual int currentZoomLevel() {return -1;} virtual int minimumZoomLevel() {return -1;} virtual int maximumZoomLevel() {return -1;} virtual bool supportZoom() {return false;} protected: void paintEvent(QPaintEvent *e); void keyPressEvent(QKeyEvent *e); public Q_SLOTS: virtual void bindModel(Peony::FileItemModel *model, Peony::FileItemProxyFilterSortModel *proxyModel); //location //virtual void open(const QStringList &uris, bool newWindow) {} virtual void setDirectoryUri(const QString &uri) {} virtual void beginLocationChange(); virtual void stopLocationChange(); virtual void closeDirectoryView() {} //selections virtual void setSelections(const QStringList &uris) {} virtual void invertSelections() {} virtual void scrollToSelection(const QString &uri) {} //clipboard //cut items should be drawn differently. virtual void setCutFiles(const QStringList &uris) {} virtual void setSortType(int sortType) {} virtual void setSortOrder(int sortOrder) {} virtual void editUri(const QString &uri) {} virtual void editUris(const QStringList uris) {} virtual void repaintView() {} virtual void clearIndexWidget() {} //zoom virtual void setCurrentZoomLevel(int zoomLevel) {} public: QString m_remote_uri; private: Intel::ComputerView *m_view = nullptr; GMountOperation* m_op = nullptr; QAction *m_enterAction = nullptr; Peony::FileItemModel *m_model = nullptr; Peony::FileItemProxyFilterSortModel *m_proxyModel = nullptr; }; } #endif // COMPUTERVIEWCONTAINER_H peony-extensions/peony-extension-computer-view/computer-view-intel/intel-computer-volume-item.cpp0000664000175000017500000005721515156143137032652 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2020, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "intel-computer-volume-item.h" #include #include "intel-computer-model.h" #include #include #include #include #include #include using namespace Intel; ComputerVolumeItem::ComputerVolumeItem(GVolume *volume, ComputerModel *model, AbstractComputerItem *parentNode, QObject *parent) : AbstractComputerItem(model, parentNode, parent) { if (parentNode->itemType() != Volume) { m_displayName = tr("Volume"); return; } m_cancellable = g_cancellable_new(); if (!volume) { m_icon = QIcon::fromTheme("drive-harddisk-system"); m_uri = "file:///"; m_displayName = tr("System Disk"); auto file = g_file_new_for_uri("file:///"); g_file_query_filesystem_info_async(file, "*", 0, m_cancellable, GAsyncReadyCallback(query_root_info_async_callback), this); return; } m_volume = std::make_shared(volume, true); updateInfoAsync(); m_volumeChangedHandle = g_signal_connect(volume, "changed", G_CALLBACK(volume_changed_callback), this); m_volumeRemovedHandle = g_signal_connect(volume, "removed", G_CALLBACK(volume_removed_callback), this); m_mountChangedHandle = g_signal_connect(g_volume_monitor_get(), "mount_changed", G_CALLBACK(mount_changed_callback), this); } ComputerVolumeItem::ComputerVolumeItem(const QString uri,ComputerModel *model,AbstractComputerItem *parentNode,QObject *parent) : AbstractComputerItem(model,parentNode,parent){ if(uri.isNull() || uri.isEmpty()) return; auto info = Peony::FileInfo::fromUri(uri); if (info.get()->isEmptyInfo()) { Peony::FileInfoJob j(info); j.querySync(); } m_info = info; collectInfoWhenGpartedOpen(uri); } ComputerVolumeItem::~ComputerVolumeItem() { g_signal_handler_disconnect(g_volume_monitor_get(), m_mountChangedHandle); if(m_volume){ g_signal_handler_disconnect(m_volume->getGVolume(), m_volumeChangedHandle); g_signal_handler_disconnect(m_volume->getGVolume(), m_volumeRemovedHandle); } g_cancellable_cancel(m_cancellable); g_object_unref(m_cancellable); if(m_watcher){ m_watcher->stopMonitor(); delete m_watcher; } } void ComputerVolumeItem::updateInfoAsync() { //! update home volume if (m_uri == "file:///home") { m_icon = QIcon::fromTheme("drive-harddisk-system"); m_uri = "file:///home"; m_displayName = tr("User Disk"); auto file = g_file_new_for_uri("file:///home"); g_file_query_filesystem_info_async(file, "*", 0, m_cancellable, GAsyncReadyCallback(query_root_info_async_callback), this); return; } if (!m_volume) { m_icon = QIcon::fromTheme("drive-harddisk-system"); m_uri = "file:///"; m_displayName = tr("System Disk"); auto file = g_file_new_for_uri("file:///"); g_file_query_filesystem_info_async(file, "*", 0, m_cancellable, GAsyncReadyCallback(query_root_info_async_callback), this); return; } char *deviceName; QString unixDeviceName; m_displayName = m_volume->name(); m_icon = QIcon::fromTheme(m_volume->iconName()); //qDebug()<getGVolume()); if (mount) { m_mount = std::make_shared(mount, true); auto active_root = g_mount_get_root(mount); if (active_root) { auto uri = g_file_get_uri(active_root); if (uri) { m_uri = uri; g_free(uri); } g_file_query_filesystem_info_async(active_root, "*", 0, m_cancellable, GAsyncReadyCallback(qeury_info_async_callback), this); g_object_unref(active_root); } } else { //m_mount = nullptr; //mount first //FIXME: check auto mount // this->mount(); } //Handle the Chinese name of fat32 udisk. deviceName = g_volume_get_identifier(m_volume->getGVolume(),G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); if(deviceName){ unixDeviceName = QString(deviceName); Peony::FileUtils::handleVolumeLabelForFat32(m_displayName,unixDeviceName); g_free(deviceName); } auto index = this->itemIndex(); m_model->dataChanged(index, index); } const QString ComputerVolumeItem::displayName() { return m_displayName; } const QIcon ComputerVolumeItem::icon() { return m_icon.isNull()? AbstractComputerItem::icon(): m_icon; } bool ComputerVolumeItem::isMount() { return Peony::FileUtils::isMountPoint(m_uri); } void ComputerVolumeItem::findChildren() { //add root m_model->beginInsertItem(this->itemIndex(), m_children.count()); auto root = new ComputerVolumeItem(nullptr, m_model, this); m_children<

You can manually add files or modify the space conditions.

"), this); textLabel2->setOpenExternalLinks(false); textLabel2->setAlignment(Qt::AlignCenter); // Center the text contentLayout->addWidget(textLabel2); connect(textLabel2, &QLabel::linkActivated, this, &Spaceview::handleLinkActivated); // Add stretchable spacers to center the content layout->addStretch(); layout->addWidget(contentWidget); layout->addStretch(); this->setLayout(layout); m_signalTransInterface = new QDBusInterface("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller"); } void Spaceview::setDirectoryUri(QString uri) { QString pre = "idm:///"; m_uri = uri; m_spaceName = Peony::FileUtils::urlDecode(m_uri).remove(pre); } void Spaceview::handleLinkActivated(const QString &link) { if (link == "#addfiles") { QFileDialog fileDialog; // fixme: 居中窗口 //fileDialog.windowHandle()->setTransientParent(qApp->topLevelWindows().first()); fileDialog.setWindowTitle(tr("Choose file to add into space")); fileDialog.setAcceptMode(QFileDialog::AcceptOpen); fileDialog.setFileMode(QFileDialog::AnyFile); QDir DocumentDir(g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS)); fileDialog.setDirectory(DocumentDir); if (fileDialog.exec()) { auto urls = fileDialog.selectedUrls(); if (!urls.isEmpty()) { //userAddFilesToSpace QStringList filePaths; for (auto url : urls) { filePaths.append(url.path()); } userAddFilesToSpaceBySpaceplugin(filePaths, m_spaceName,true); } } } else if (link == "#modifyspace") { //Q_EMIT IDMDBusManager::getInstance()->editSpaceConditionSignal(m_uri); //onModifySpaceConditions(); editSpaceConditionBySpaceplugin(m_uri); } } void Spaceview::userAddFilesToSpaceBySpaceplugin(const QStringList &filePath, const QString &spaceName, bool isUserAdd) { QDBusReply reply = m_signalTransInterface->call("userAddFilesToSpaceBySpaceplugin",filePath,spaceName,isUserAdd); } void Spaceview::editSpaceConditionBySpaceplugin(const QString &uri) { QDBusReply reply = m_signalTransInterface->call("editSpaceConditionBySpaceplugin",uri); } void Spaceview::dragEnterEvent(QDragEnterEvent *event) { auto action = Qt::CopyAction; if (event->mimeData()->hasUrls()) { event->setDropAction(action); event->accept(); } } void Spaceview::dragMoveEvent(QDragMoveEvent *event) { auto action = Qt::CopyAction; if (event->mimeData()->hasUrls()) { event->setDropAction(action); event->accept(); } return QWidget::dragMoveEvent(event); } void Spaceview::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { QList urlList = event->mimeData()->urls(); QStringList filePaths; for (const QUrl &url : urlList) { QString filePath = url.toLocalFile(); filePaths< QObject Intelligent Space View 智能空间视图 Use customized Intelligent Data Manager space views when the spaces file content is empty 当空间文件内容为空时使用自定义的智能数据管理空间视图 Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 当系统中存在符合该空间条件的文件时,将自动显示在空间内 <html><head/><body><p>You can manually <a href='#addfiles' style='text-decoration:none;'>add files</a> or <a href='#modifyspace' style='text-decoration:none;'>modify the space conditions.</a></p></body></html> <html><head/><body><p>您可以手动 <a href='#addfiles' style='text-decoration:none;'>添加文件</a> ,或 <a href='#modifyspace' style='text-decoration:none;'>修改空间条件</a></p></body></html> Choose file to add into space 选择文件添加至空间 peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view_bo_CN.ts0000664000175000017500000000516415156143275027456 0ustar fengfeng QObject Intelligent Space View རིག་ནུས་བར་སྟོང་གི་རི་མོ། Use customized Intelligent Data Manager space views when the spaces file content is empty བར་སྟོང་ཡིག་ཆའི་ནང་དོན་ནི་བར་སྣང་གི་ནང་དོན་ཡིན་པ་དང་། མཚན་ཉིད་ཀྱི་མཚན་ཉིད་སྤྱད་པའི་Indaanaeereanaerrereaerreanarearerearerrererrerr Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space མ་ལག་ཁྲོད་བར་སྟོང་གི་ཆ་རྐྱེན་དང་མཐུན་པའི་ཡིག་ཆ་ཡོད་ཚེ་རང་འགུལ་གྱིས་བར་སྟོང་དུ་མངོན་ངེས་ཡིན། <html><head/><body><p>You can manually <a href='#addfiles' style='text-decoration:none;'>add files</a> or <a href='#modifyspace' style='text-decoration:none;'>modify the space conditions.</a></p></body></html> <html><head/><body><p>ཁྱེད་ཀྱིས་X39Xལ་ཡིག་ཆ་ཁ་སྣོན་བྱས་ཆོག་པ་དང་། X98Xའམ། ཡང་ན་X106X〕བཟོ་བཅོས་རྒྱག་པའི་བར་སྟོང་ཆ་རྐྱེན་བཟོ་བཅོས་བྱས་ཆོག། </a></p></body></html> Choose file to add into space བར་སྟོང་ནང་གི་ཡིག་ཆ་ཁ་སྣོན་རྒྱག་དགོས། peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view_ug.ts0000664000175000017500000000461015156143275027104 0ustar fengfeng QObject Intelligent Space View ئەقلىي ئىقتىدارلىق بوشلۇق كۆرۈنۈشى Use customized Intelligent Data Manager space views when the spaces file content is empty بوشلۇق ھۆججىتى مەزمۇنى بوش بولغاندا ئىختىيارى ئەقلىي ئىقتىدارلىق سانلىق مەلۇمات باشقۇرغۇچنىڭ بوشلۇق كۆرۈنۈشىنى ئىشلىتىش Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space سىستېمىدىكى بوشلۇق شەرتىگە ئۇيغۇن كېلىدىغان ھۆججەتلەر بولسا، بوشلۇقتا ئاپتوماتىك كۆرۈنىدۇ <html><head/><body><p>You can manually <a href='#addfiles' style='text-decoration:none;'>add files</a> or <a href='#modifyspace' style='text-decoration:none;'>modify the space conditions.</a></p></body></html> <html><head/><body><p>بوشلۇق شەرتلىرىنى قولدا <a href='#addfiles' style='text-decoration:none;'>ھۆججەت قوشۇپ</a> ياكى <a href='#modifyspace' style='text-decoration:none;'> بوشلۇق شەرتلىرىنى ئۆزگەرتەلەيسىز.</a></p></body></html> Choose file to add into space بوشلۇققا قوشۇش ئۈچۈن ھۆججەتنى تاللاڭ peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view-plugin.cpp0000664000175000017500000000446715156143275030053 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #include "peony-space-view-plugin.h" #include "space-view-container.h" #include #include #include #include using namespace Peony; PeonyIntelligentSpaceViewPlugin::PeonyIntelligentSpaceViewPlugin(QObject *parent) { //加载翻译 QTranslator *t = new QTranslator(this); t->load(":/peony-space-view_"+QLocale::system().name()); QApplication::installTranslator(t); } int PeonyIntelligentSpaceViewPlugin::priority(const QString &directoryUri) { qDebug() << "=======" << __func__ << directoryUri; if(directoryUri.startsWith("idm:///")&&m_currentUri!="idm:///"){ QString prestr = "idm:///"; QString uri = FileUtils::urlDecode(directoryUri); if (uri.startsWith(prestr)) { m_spacename = uri.mid(prestr.length()); } QDBusInterface* m_signalTransInterface = new QDBusInterface("com.peony.idm.service", "/org/peony/idm/space/service", "org.peony.idm.modelcontroller"); QDBusReply reply = m_signalTransInterface->call("getFilesInSpaceByName",m_spacename,false); QDBusReply names = m_signalTransInterface->call("getAllSpacesName"); if(reply.value().isEmpty()&& names.value().contains(m_spacename)){ return 1; } } return -1; } DirectoryViewWidget *PeonyIntelligentSpaceViewPlugin::create() { return new SpaceViewContainer(nullptr,m_currentUri,m_spacename); } peony-extensions/peony-spaceview-plugin/peony-space-view/space-view-container.cpp0000664000175000017500000000470715156143275027404 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #include "space-view-container.h" #include "file-item-model.h" #include "file-item-proxy-filter-sort-model.h" #include #include #include #include #include using namespace Peony; SpaceViewContainer::SpaceViewContainer(QWidget *parent,QString uri,QString Spacename) :m_currentUri(uri),m_Spacename(Spacename) { auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->setObjectName("_spaceview_layout"); setBackgroundRole(QPalette::Base); setAutoFillBackground(true); m_view = new Spaceview(this,m_currentUri,m_Spacename); layout->addWidget(m_view); setLayout(layout); } SpaceViewContainer::~SpaceViewContainer() { } const QString SpaceViewContainer::getDirectoryUri() { return m_currentUri; } void SpaceViewContainer::bindModel(FileItemModel *model, FileItemProxyFilterSortModel *proxyModel) { auto layout = findChild("_spaceview_layout"); bool ok = false; if (parentWidget()) { int statusBarHeight = parentWidget()->property("statusBarHeight").toInt(&ok); if (ok) { layout->setContentsMargins(0, 0, 0, statusBarHeight); } } } void SpaceViewContainer::setDirectoryUri(const QString &uri) { if (m_currentUri != uri) { m_currentUri = uri; m_view->setDirectoryUri(uri); } } void SpaceViewContainer::beginLocationChange() { Q_EMIT viewDirectoryChanged(); } void SpaceViewContainer::stopLocationChange() { Q_EMIT viewDirectoryChanged(); } void SpaceViewContainer::contextMenuEvent(QContextMenuEvent *event) { Q_EMIT menuRequest(event->globalPos()); } peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view.qrc0000664000175000017500000000013615156143275026547 0ustar fengfeng data/nodata.svg peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view_mn.ts0000664000175000017500000000545315156143275027111 0ustar fengfeng QObject Intelligent Space View ᠤᠶᠤᠨᠲᠤ ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢᠨ ᠬᠠᠷᠠᠭ᠎ᠠ ᠃ Use customized Intelligent Data Manager space views when the spaces file content is empty ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢᠨ ᠪᠢᠴᠢᠭ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠠᠭᠤᠯᠭ᠎ᠠ ᠨᠢ ᠬᠣᠭᠣᠰᠣᠨ ᠦᠶ᠎ᠡ ᠳᠦ ᠂ ᠥᠪᠡᠷ ᠦᠨ ᠲᠣᠮᠢᠶᠠᠯᠠᠯ ᠤᠨ ᠢᠨᠲ᠋ᠧᠷᠨᠧᠲ᠋ ᠳ᠋ᠠᠲ᠋ᠠᠲ᠋ᠠ ᠶᠢᠨ ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢᠨ ᠬᠠᠷᠠᠭ᠎ᠠ ᠶᠢ ᠬᠡᠷᠡᠭᠯᠡᠨ᠎ᠡ ᠃ Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space ᠰᠢᠰᠲ᠋ᠧᠮ ᠳᠣᠲᠣᠷ᠎ᠠ ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢᠨ ᠨᠥᠬᠥᠴᠡᠯ ᠳᠦ ᠨᠡᠶᠢᠴᠡᠬᠦ ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠪᠠᠢ᠌ᠬᠤ ᠦᠶ᠎ᠡ ᠳᠦ ᠣᠷᠣᠨ ᠵᠠᠢ ᠳᠤ ᠠᠶᠠᠨᠳᠠᠭᠠᠨ ᠢᠯᠡᠷᠡᠨ᠎ᠡ ᠃ <html><head/><body><p>You can manually <a href='#addfiles' style='text-decoration:none;'>add files</a> or <a href='#modifyspace' style='text-decoration:none;'>modify the space conditions.</a></p></body></html> <html><head/><body><p>ᠲᠠ ᠭᠠᠷ ᠢᠶᠠᠷ ᠢᠶᠠᠨ 〔 X39X 〕 ᠨᠡᠮᠡᠯᠲᠡ ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ 〔 X98X 〕 ᠪᠤᠶᠤ 〔 X106X 〕 ᠶᠢ ᠨᠡᠮᠡᠵᠦ ᠣᠷᠣᠨ ᠵᠠᠶ ᠶᠢᠨ ᠨᠥᠬᠥᠴᠡᠯ ᠳᠦ ᠵᠠᠰᠠᠪᠤᠷᠢ ᠣᠷᠣᠭᠤᠯᠵᠤ ᠪᠣᠯᠣᠨ᠎ᠠ ᠃ </a></p></body></html> Choose file to add into space ᠣᠷᠣᠨ ᠵᠠᠢ ᠳᠤ ᠨᠡᠮᠡᠬᠦ ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠢ ᠰᠣᠩᠭᠣᠬᠤ ᠬᠡᠷᠡᠭ᠍ᠲᠡᠢ ᠃ peony-extensions/peony-spaceview-plugin/peony-space-view/space-view.h0000664000175000017500000000312715156143275025064 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #ifndef SPACEVIEW_H #define SPACEVIEW_H #include #include #include #include class Spaceview: public QWidget{ Q_OBJECT public: explicit Spaceview(QWidget *parent = nullptr,QString uri="",QString Spacename=""); void setDirectoryUri(QString uri); public Q_SLOTS: void handleLinkActivated(const QString &link); void userAddFilesToSpaceBySpaceplugin(const QStringList &filePath,const QString &spaceName,bool isUserAdd); void editSpaceConditionBySpaceplugin(const QString &uri); private: QString m_uri; QString m_spaceName; QDBusInterface* m_signalTransInterface; protected: void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; }; #endif // peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view.pro0000664000175000017500000000456215156143275026571 0ustar fengfengQT += widgets dbus TARGET = peony-space-view TEMPLATE = lib DEFINES += PEONYSPACEVIEW_LIBRARY include(../../common.pri) CONFIG += debug c++11 no_keywords link_pkgconfig plugin embed_translations PKGCONFIG +=gio-2.0 glib-2.0 gio-unix-2.0 peony libnotify # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #TRANSLATIONS = peony-extension-computer-view_zh_CN.ts \ # peony-extension-computer-view_de.ts \ # peony-extension-computer-view_es.ts \ # peony-extension-computer-view_fr.ts \ # peony-extension-computer-view_kk_KZ.ts \ # peony-extension-computer-view_ug_CN.ts \ # peony-extension-computer-view_ky_KG.ts \ # peony-extension-computer-view_cs.ts \ # peony-extension-computer-view_tr.ts \ # peony-extension-computer-view_bo_CN.ts \ # peony-extension-computer-view_mn.ts \ # peony-extension-computer-view_zh_HK.ts #include(computer-view/computer-view.pri) TRANSLATIONS = peony-space-view_zh_CN.ts\ peony-space-view_zh_HK.ts\ peony-space-view_mn.ts\ peony-space-view_ug.ts\ peony-space-view_kk.ts\ peony-space-view_ky.ts\ peony-space-view_bo_CN.ts CONFIG += link_pkgconfig PKGCONFIG += gio-unix-2.0 RESOURCES += peony-space-view.qrc SOURCES += \ peony-space-view-plugin.cpp \ space-view-container.cpp \ space-view.cpp\ HEADERS += \ peony-space-view-plugin.h \ peony-space-view-plugin_global.h \ space-view.h\ space-view-container.h\ #DESTDIR += ../testdir # Default rules for deployment. unix { target.path = $$[QT_INSTALL_LIBS]/peony-extensions } !isEmpty(target.path): INSTALLS += target CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = / peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view_ky.ts0000664000175000017500000000467215156143275027124 0ustar fengfeng QObject Intelligent Space View روحىي جۅندۅمدۉلۉك بوشتۇق گۅرۉنۉشۉ Use customized Intelligent Data Manager space views when the spaces file content is empty بوش ،بەكەر تۇق ۅجۅتۉۉ مازمۇنۇ بوش ،بەكەر بولعوندو ۅزەركى مەنەن روحىي جۅندۅمدۉلۉك ساندۇۇ بايانداما باشقارۇۇچۇنۇن بوش ،بەكەر تۇق گۅرۉنۉشۉن ىشتەتىش Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space سەستىماداعى بوشتۇق شارتىنا شاي گەلەتۇرعان ۅجۅتتۅر بولسو، بوشتۇقتا اپتوماتتىك كۅرۉنۅت <html><head/><body><p>You can manually <a href='#addfiles' style='text-decoration:none;'>add files</a> or <a href='#modifyspace' style='text-decoration:none;'>modify the space conditions.</a></p></body></html> <html><head/><body><p>بوشتۇق شارتتارىن قولدو <a href='#addfiles' style='text-decoration:none;'>ۅجۅت قوشۇپ </a> كۅرۉنۉشتۅرۉ <a href='#modifyspace' style='text-decoration:none;'> بوشتۇق شارتتارىن ۅزگۅرتۅ الاسىز.</a></p></body></html> Choose file to add into space بوشتۇققا قوشۇۇ ۉچۉن ۅجۅتۉن تانداڭ peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view-plugin.h0000664000175000017500000000455515156143275027516 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #ifndef PEONYINTELLIGENTSEARCHVIEWPLUGIN_H #define PEONYINTELLIGENTSEARCHVIEWPLUGIN_H #include "peony-space-view-plugin_global.h" #include #include namespace Peony { class PEONYINTELLIGENTSEARCHVIEW_EXPORT PeonyIntelligentSpaceViewPlugin : public QObject, public DirectoryViewPluginIface2 { Q_OBJECT public: Q_PLUGIN_METADATA(IID DirectoryViewPluginIface2_iid FILE "common.json") Q_INTERFACES(Peony::DirectoryViewPluginIface2) public: explicit PeonyIntelligentSpaceViewPlugin(QObject *parent = nullptr); PluginType pluginType() {return PluginType::DirectoryViewPlugin2;} const QString name() {return QObject::tr("Intelligent Space View");} const QString description() {return QObject::tr("Use customized Intelligent Data Manager space views when the spaces file content is empty");} const QIcon icon() {return QIcon::fromTheme("kylin-data-manager");} void setEnable(bool enable) {} bool isEnable() {return true;} //view QString viewIdentity() { return "Space View"; } QString viewName() {return name();} QIcon viewIcon() {return icon();} bool supportUri(const QString &uri) {return uri.startsWith("idm:///");} int zoom_level_hint() {return -1;} int minimumSupportedZoomLevel() {return -1;} int maximumSupportedZoomLevel() {return -1;} int priority(const QString &directoryUri); bool supportZoom() {return false;} DirectoryViewWidget *create(); Q_SIGNALS: private: QString m_currentUri; QString m_spacename; }; } #endif // PEONYINTELLIGENTSEARCHVIEWPLUGIN_H peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view_kk.ts0000664000175000017500000000450215156143275027076 0ustar fengfeng QObject Intelligent Space View وي قابٸلەتتٸ بوستٸق كورىنۋى Use customized Intelligent Data Manager space views when the spaces file content is empty بوستٸق حۇجاتى مازمۇنى بوس بولعاندا ەرٸكتٸ وي قابٸلەتتٸ ساندىق مالىمەت باسقارۋشنىڭ بوستٸق كورىنۋدى ٸستەتۋ Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space سەستاماداعى بوستٸق شارتقا سايكەس كەلەتىن حۇجاتتار بولسا، بوستٸقتا اۆتوماتتى كورىنەدى <html><head/><body><p>You can manually <a href='#addfiles' style='text-decoration:none;'>add files</a> or <a href='#modifyspace' style='text-decoration:none;'>modify the space conditions.</a></p></body></html> <html><head/><body><p>بوستٸق شارتتاردى قولدا <a href='#addfiles' style='text-decoration:none;'>حۇجات قوسىپ</a> ياكي <a href='#modifyspace' style='text-decoration:none;'> بوستٸق شارتتاردى وزگەرتەالاسٸز.</a></p></body></html> Choose file to add into space بوستٸققا قوسۋ ٷشٸن حۇجاتتى تالدا peony-extensions/peony-spaceview-plugin/peony-space-view/peony-space-view_zh_HK.ts0000664000175000017500000000375015156143275027500 0ustar fengfeng QObject Intelligent Space View 智慧空間檢視 Use customized Intelligent Data Manager space views when the spaces file content is empty 當空間檔內容為空時使用自定義的智能數據管理空間檢視 Spaceview When there are files in the system that meet the space conditions, they will be automatically displayed in the space 當系統中存在符合該空間條件的檔時,將自動顯示在空間內 <html><head/><body><p>You can manually <a href='#addfiles' style='text-decoration:none;'>add files</a> or <a href='#modifyspace' style='text-decoration:none;'>modify the space conditions.</a></p></body></html> <html><p><body><head/>您可以手動 <a href='#addfiles' style='text-decoration:none;'>添加檔</a> ,或 <a href='#modifyspace' style='text-decoration:none;'>修改空間條件</a></p></body></html> Choose file to add into space 選擇檔案添加至空間 peony-extensions/peony-spaceview-plugin/peony-space-view/data/0000775000175000017500000000000015156143275023556 5ustar fengfengpeony-extensions/peony-spaceview-plugin/peony-space-view/data/nodata.svg0000664000175000017500000000751115156143275025551 0ustar fengfengpeony-extensions/peony-spaceview-plugin/peony-space-view/space-view-container.h0000664000175000017500000000625615156143275027052 0ustar fengfeng/* * UKUI Intelligent Data Manager * * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: JinQin He * */ #ifndef SPACEVIEWCONTAINER_H #define SPACEVIEWCONTAINER_H #include #include #include #include "space-view.h" #include #include #include #include namespace Peony { class SpaceViewContainer : public DirectoryViewWidget { public: explicit SpaceViewContainer(QWidget *parent = nullptr,QString uri="",QString Spacename=""); ~SpaceViewContainer(); const virtual QString viewId() { return "Space View"; } const virtual QString getDirectoryUri(); //selections const virtual QStringList getSelections() { return QStringList(); } //children const virtual QStringList getAllFileUris(){ return QStringList(); } virtual int getSortType() { return 0; } virtual Qt::SortOrder getSortOrder() { return Qt::AscendingOrder; } //zoom virtual int currentZoomLevel() { return -1; } virtual int minimumZoomLevel() { return -1; } virtual int maximumZoomLevel() { return -1; } virtual bool supportZoom() { return false; } public Q_SLOTS: virtual void bindModel(FileItemModel *model, FileItemProxyFilterSortModel *proxyModel); //location //virtual void open(const QStringList &uris, bool newWindow) {} virtual void setDirectoryUri(const QString &uri); virtual void beginLocationChange(); virtual void stopLocationChange(); virtual void closeDirectoryView() {} //selections virtual void setSelections(const QStringList &uris) {} virtual void invertSelections() {} virtual void scrollToSelection(const QString &uri) {} //clipboard //cut items should be drawn differently. virtual void setCutFiles(const QStringList &uris) {} virtual void setSortType(int sortType) {} virtual void setSortOrder(int sortOrder) {} virtual void editUri(const QString &uri) {} virtual void editUris(const QStringList uris) {} virtual void repaintView() {} virtual void clearIndexWidget() {} //zoom virtual void setCurrentZoomLevel(int zoomLevel) {} protected: virtual void contextMenuEvent(QContextMenuEvent *event) override; private: QString m_currentUri; QString m_Spacename; Spaceview *m_view = nullptr; }; } #endif // SPACEVIEWCONTAINER_H peony-extensions/peony-spaceview-plugin/peony-spaceview-plugin.pro0000664000175000017500000000007315156143275024606 0ustar fengfengTEMPLATE = subdirs dbus SUBDIRS += \ peony-space-view peony-extensions/test-computer-view/0000775000175000017500000000000015156143137016620 5ustar fengfengpeony-extensions/test-computer-view/mainwindow.cpp0000664000175000017500000000200715156143137021477 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #include "mainwindow.h" #include "ui_mainwindow.h" #include "computer-view.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); setCentralWidget(new ComputerView); } MainWindow::~MainWindow() { delete ui; } peony-extensions/test-computer-view/test-computer-view.pro0000664000175000017500000000201615156143137023124 0ustar fengfengQT += core gui dbus network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += debug c++11 no_keywords link_pkgconfig plugin PKGCONFIG +=gio-2.0 glib-2.0 gio-unix-2.0 peony udisks2 libnotify # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS include(../common.pri) include(../peony-extension-computer-view/computer-view/computer-view.pri) # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ mainwindow.cpp HEADERS += \ mainwindow.h FORMS += \ mainwindow.ui peony-extensions/test-computer-view/main.cpp0000664000175000017500000000162315156143137020252 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #include "mainwindow.h" #include int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } peony-extensions/test-computer-view/mainwindow.h0000664000175000017500000000205215156143137021144 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H peony-extensions/test-computer-view/mainwindow.ui0000664000175000017500000000103015156143137021325 0ustar fengfeng MainWindow 0 0 800 600 MainWindow peony-extensions/peony-menu-plugin-mate-terminal/0000775000175000017500000000000015156143275021165 5ustar fengfengpeony-extensions/peony-menu-plugin-mate-terminal/peony-menu-plugin-mate-terminal.pro0000664000175000017500000000466115156143275030043 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2019-11-05T10:40:52 # #------------------------------------------------- QT += widgets concurrent dbus TARGET = peony-menu-plugin-mate-terminal TEMPLATE = lib DEFINES += PEONYMENUPLUGINMATETERMINAL_LIBRARY include(../common.pri) # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 PKGCONFIG +=gio-2.0 glib-2.0 gio-unix-2.0 peony CONFIG += debug c++11 link_pkgconfig no_keywords plugin #DESTDIR += ../testdir SOURCES += \ mate-terminal-menu-plugin.cpp HEADERS += \ mate-terminal-menu-plugin.h \ peony-menu-plugin-mate-terminal_global.h TRANSLATIONS += translations/peony-mate-terminal-extension_zh_CN.ts \ translations/peony-mate-terminal-extension_de.ts \ translations/peony-mate-terminal-extension_es.ts \ translations/peony-mate-terminal-extension_fr.ts \ translations/peony-mate-terminal-extension_kk_KZ.ts \ translations/peony-mate-terminal-extension_ug_CN.ts \ translations/peony-mate-terminal-extension_ky_KG.ts \ translations/peony-mate-terminal-extension_tr.ts \ translations/peony-mate-terminal-extension_cs.ts \ translations/peony-mate-terminal-extension_bo_CN.ts \ translations/peony-mate-terminal-extension_mn.ts \ translations/peony-mate-terminal-extension_zh_HK.ts unix { target.path = $$[QT_INSTALL_LIBS]/peony-extensions INSTALLS += target } #RESOURCES += \ # peony-menu-plugin-mate-terminal.qrc CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = /translations/ SKIP_TEST = $$(EXTENSIONS_SKIP_TEST) isEmpty(SKIP_TEST) { message("build with tests") QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage LIBS += -lgcov } peony-extensions/peony-menu-plugin-mate-terminal/mate-terminal-menu-plugin.h0000664000175000017500000000407515156143137026336 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef MATETERMINALMENUPLUGIN_H #define MATETERMINALMENUPLUGIN_H #include "peony-menu-plugin-mate-terminal_global.h" #include #include #include namespace Peony { class PEONYQTMENUPLUGINMATETERMINALSHARED_EXPORT MateTerminalMenuPlugin : public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit MateTerminalMenuPlugin(QObject *parent = nullptr); PluginInterface::PluginType pluginType() override {return PluginInterface::MenuPlugin;} const QString name() override {return tr("Peony-Qt Mate Terminal Menu Extension");} const QString description() override {return tr("Open Terminal with menu");} const QIcon icon() override {return QIcon::fromTheme("utilities-terminal");} void setEnable(bool enable) override {m_enable = enable;} bool isEnable() override {return m_enable;} void openTerminal(); void tryOpenAgain(); void updateTerminalCmd(); QString testPlugin() override {return "test";} QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; private: bool m_enable; }; } #endif // MATETERMINALMENUPLUGIN_H peony-extensions/peony-menu-plugin-mate-terminal/translations/0000775000175000017500000000000015156143275023706 5ustar fengfengpeony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_fr.ts0000664000175000017500000000464315156143275032655 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal Ouvrir le répertoire dans le terminal Peony-Qt Mate Terminal Menu Extension Peony-Qt Mate Terminal Menu Extension Open Terminal with menu Ouvrir le terminal avec le menu Open Terminal with menu. 使用右键菜单打开终端。 Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail Échec de l’ouverture du terminal Open terminal failed, did you removed the default terminal? If it's true please reinstall it. Échec de l’ouverture du terminal, avez-vous supprimé le terminal par défaut ? Si c’est vrai, veuillez le réinstaller. Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_de.ts0000664000175000017500000000466515156143275032642 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal Öffnen Sie das Verzeichnis im Terminal Peony-Qt Mate Terminal Menu Extension Peony-Qt Mate Terminal-Menüerweiterung Open Terminal with menu Öffnen Sie das Terminal mit Menü Open Terminal with menu. 使用右键菜单打开终端。 Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail Offenes Terminal fehlgeschlagen Open terminal failed, did you removed the default terminal? If it's true please reinstall it. Das Öffnen des Terminals ist fehlgeschlagen, haben Sie das Standardterminal entfernt? Wenn dies der Fall ist, installieren Sie es bitte neu. Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_cs.ts0000664000175000017500000000310615156143275032644 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal Otevřít složku v Terminálu Peony-Qt Mate Terminal Menu Extension Rozšíření do Peony-Qt pro nabídku Mate Terminal Open Terminal with menu Open Terminal with menu. Otevřít Terminál z nabídky. QObject Open terminal fail Open terminal failed, did you removed the default terminal? If it's true please reinstall it. peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_bo_CN.ts0000664000175000017500000000372315156143275033224 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal མཐའ་སྣེ་ཁ་འབྱེད་པ། Peony-Qt Mate Terminal Menu Extension UKUIཡིག་ཆ་དོ་དམ་ཆས་མཐའ་སྣེ་རྒྱ་བསྐྱེད་པ། Open Terminal with menu Open Terminal with menu. གཡས་མཐེབ་ཀྱི་འདེམས་བྱང་བཀོལ་སྤྱོད་བྱས་ནས་མཐའ་སྣེའི་ཁ་འབྱེད་པ། Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail Open terminal failed, did you removed the default terminal? If it's true please reinstall it. peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_kk_KZ.ts0000664000175000017500000000505415156143275033254 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal ورتاسندا ايقٸن باس مازمۇن Peony-Qt Mate Terminal Menu Extension مودەنگۈل-Qt Mate سوڭعٸ ۇشٸ تٸزٸمدٸكدٸ ۇزارتۋ Open Terminal with menu Open Terminal with menu. Мәзірмен Терминалды ашыңыз. Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail ايقٸن سوڭعٸ ۇشٸ جەڭىلىپ قالدى Open terminal failed, did you removed the default terminal? If it's true please reinstall it. ايقٸن سوڭعٸ ۇشٸ جەڭىلىپ قالدى، سۈكۈتتىكى سوڭعٸ ۇشٸنى چىقىرىۋەتتىڭىزمۇ؟ قيسٸق شىن بولسا قاتە تۇسىرىڭىز. Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_ug_CN.ts0000664000175000017500000000506615156143275033241 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal تېرمىنالدا ئوچۇق مۇندەرىجە Peony-Qt Mate Terminal Menu Extension مودەنگۈل-Qt Mate تېرمىنال تىزىملىكىنى ئۇزارتىش Open Terminal with menu Open Terminal with menu. تىزىملىك بىلەن تېرمىنالنى ئېچىش. Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail ئوچۇق تېرمىنال مەغلۇپ بولدى Open terminal failed, did you removed the default terminal? If it's true please reinstall it. ئوچۇق تېرمىنال مەغلۇپ بولدى، سۈكۈتتىكى تېرمىنالنى چىقىرىۋەتتىڭىزمۇ؟ ئەگەر راست بولسا قايتا قاچىلاڭ. Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_mn.ts0000664000175000017500000000560615156143275032660 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal ᠦᠵᠦᠬᠦᠷ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠬᠦ Peony-Qt Mate Terminal Menu Extension UKUI ᠹᠠᠢᠯ ᠤ᠋ᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠭᠤᠷ ᠤ᠋ᠨ ᠦᠵᠦᠬᠦᠷ ᠤ᠋ᠨ ᠦᠷᠭᠡᠳᠬᠡᠯ Open Terminal with menu Open Terminal with menu. ᠪᠠᠷᠠᠭᠤᠨ ᠳᠠᠷᠤᠭᠤᠯ ᠤ᠋ᠨ ᠲᠤᠪᠶᠤᠭ ᠢ᠋ ᠬᠡᠷᠡᠭᠯᠡᠵᠤ ᠦᠵᠦᠬᠦᠷ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠬᠦ. Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail ᠦᠵᠦᠬᠦᠷ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ Open terminal failed, did you removed the default terminal? If it's true please reinstall it. ᠦᠵᠦᠬᠦᠷ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠵᠤ ᠴᠢᠳᠠᠭᠰᠠᠨ ᠦᠬᠡᠢ᠂ ᠲᠠ ᠠᠶᠠᠳᠠᠯ ᠦᠵᠦᠬᠦᠷ ᠢ᠋ ᠪᠠᠭᠤᠯᠭᠠᠵᠤ ᠦᠩᠭᠡᠷᠡᠭᠰᠡᠨ ᠤᠤ? ᠬᠡᠷᠪᠡ ᠪᠠᠭᠤᠯᠭᠠᠭᠰᠠᠨ ᠪᠤᠯ ᠳᠠᠬᠢᠵᠤ ᠤᠭᠰᠠᠷᠠᠬᠤ ᠴᠢᠬᠤᠯᠠᠳᠠᠢ. Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_zh_CN.ts0000664000175000017500000000451215156143275033242 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal 打开终端 Peony-Qt Mate Terminal Menu Extension UKUI文件管理器终端扩展 Open Terminal with menu 使用右键菜单打开终端 Open Terminal with menu. 使用右键菜单打开终端。 Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail 打开终端失败 Open terminal failed, did you removed the default terminal? If it's true please reinstall it. 打开终端失败,您是否卸载过默认终端?如果是的请重新安装。 Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_zh_HK.ts0000664000175000017500000000454115156143275033246 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal 在終端中打開目錄 Peony-Qt Mate Terminal Menu Extension Peony-Qt Mate 終端功能表擴展 Open Terminal with menu 打開帶功能表的終端 Open Terminal with menu. 使用右键菜单打开终端。 Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail 打開終端失敗 Open terminal failed, did you removed the default terminal? If it's true please reinstall it. 打開終端失敗,您是否刪除了默認終端? 如果這是真的,請重新安裝它。 Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_ky_KG.ts0000664000175000017500000000504115156143275033243 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal تېرمىنالدا اچىق تىزىمدىك Peony-Qt Mate Terminal Menu Extension مودەنگۈل-Qt Mate تىرمىنال تىزىمدىگىن ۇزارتۇۇ Open Terminal with menu Open Terminal with menu. Меню менен Терминал ачуу. Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail اچىق تىرمىنال جەڭىلۉۉ بولدۇ Open terminal failed, did you removed the default terminal? If it's true please reinstall it. اچىق تىرمىنال جەڭىلۉۉ بولدۇ ، سۈكۈتتىكى تىرمىنالنى چىقىرىۋەتتىڭىزمۇ؟ ەگەر ىراس بولسو قايرا قاچالاڭ. Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_es.ts0000664000175000017500000000460415156143275032652 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal Abrir directorio en la terminal Peony-Qt Mate Terminal Menu Extension Extensión del menú del terminal Peony-Qt Mate Open Terminal with menu Abrir Terminal con menú Open Terminal with menu. 使用右键菜单打开终端。 Open Directory in T&erminal 打开终端(&E) QObject Open terminal fail Error de terminal abierto Open terminal failed, did you removed the default terminal? If it's true please reinstall it. Error en la apertura del terminal, ¿eliminó el terminal predeterminado? Si es cierto, vuelva a instalarlo. Open terminal failed, did you removed the default terminal? If you have reinstalled it please logout and relogin. 打开终端失败,您是否卸载过默认终端?如果已经重新安装了,请注销并重新登录后生效。 Open terminal failed, did you removed the default terminal? 打开终端失败,您是否卸载了默认的终端应用? peony-extensions/peony-menu-plugin-mate-terminal/translations/peony-mate-terminal-extension_tr.ts0000664000175000017500000000304715156143275032670 0ustar fengfeng Peony::MateTerminalMenuPlugin Open Directory in Terminal Dizini Terminalde Aç Peony-Qt Mate Terminal Menu Extension Peony-Qt Mate Terminal Menü Eklentisi Open Terminal with menu Open Terminal with menu. Terminali menü ile aç QObject Open terminal fail Open terminal failed, did you removed the default terminal? If it's true please reinstall it. peony-extensions/peony-menu-plugin-mate-terminal/peony-menu-plugin-mate-terminal_global.h0000664000175000017500000000223715156143137031004 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #ifndef PEONYQTMENUPLUGINMATETERMINAL_GLOBAL_H #define PEONYQTMENUPLUGINMATETERMINAL_GLOBAL_H #include #if defined(PEONYMENUPLUGINMATETERMINAL_LIBRARY) # define PEONYQTMENUPLUGINMATETERMINALSHARED_EXPORT Q_DECL_EXPORT #else # define PEONYQTMENUPLUGINMATETERMINALSHARED_EXPORT Q_DECL_IMPORT #endif #endif // PEONYQTMENUPLUGINMATETERMINAL_GLOBAL_H peony-extensions/peony-menu-plugin-mate-terminal/mate-terminal-menu-plugin.cpp0000664000175000017500000002124115156143275026666 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Yue Lan * */ #include "mate-terminal-menu-plugin.h" #include "global-settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KYLIN_COMMON #include #endif #define V10_SP1_EDU "V10SP1-edu" using namespace Peony; static QString terminal_cmd = nullptr; static QString m_uri = nullptr; MateTerminalMenuPlugin::MateTerminalMenuPlugin(QObject *parent) : QObject (parent) { QTranslator *t = new QTranslator(this); qDebug()<<"\n\n\n\n\n\n\ntranslate:"<load(":/translations/peony-mate-terminal-extension_"+QLocale::system().name()); QApplication::installTranslator(t); QtConcurrent::run([=]{ updateTerminalCmd(); // GList *infos = g_app_info_get_all(); // GList *l = infos; // while (l) { // const char *cmd = g_app_info_get_executable(static_cast(l->data)); // QString tmp = cmd; // qDebug() << "============tmp:" << tmp; // if (tmp.contains("terminal") || tmp.contains("terminator")) { // qDebug() << "terminal cmd:" <next; // continue; // } // else // terminal_cmd = tmp; // } // l = l->next; // } // g_list_free_full(infos, g_object_unref); }); } void MateTerminalMenuPlugin::openTerminal(){ //qDebug()<<"triggered"<message; g_error_free(err); err = nullptr; //try again to open terminal //maybe this method is better,but still need more test to confirm tryOpenAgain(); } g_strfreev (argv); } void MateTerminalMenuPlugin::tryOpenAgain() { GList *infos = g_app_info_get_all(); GList *l = infos; while (l) { const char *cmd = g_app_info_get_executable(static_cast(l->data)); QString tmp = cmd; if (tmp.contains("terminal") || tmp.contains("terminator")) { qDebug() << "terminal cmd:" <next; continue; } else terminal_cmd = tmp; } l = l->next; } g_list_free_full(infos, g_object_unref); QUrl url = m_uri; auto absPath = url.path(); qDebug() << "tryOpenAgain terminal url:" < MateTerminalMenuPlugin::menuActions(Types types, const QString &uri, const QStringList &selectionUris) { #ifdef KYLIN_COMMON if (QString::fromStdString(KDKGetPrjCodeName()) == V10_SP1_EDU) { return QList(); } #endif QList actions; qDebug() << "terminal_cmd:" <getValue(SHOW_OPEN_TERMINAL).toBool()) { return actions; } //set default terminal_cmd value for unfind terminal has no option issue //FIXME for unknow reason, did not find any terminal but it is actually exist if (terminal_cmd.isNull()) { qWarning("open terminal option has not find any terminal, set as default value."); terminal_cmd = "mate-terminal"; } if (types == MenuPluginInterface::DirectoryView || types == MenuPluginInterface::DesktopWindow) { if (selectionUris.isEmpty()) { m_uri = uri; //virtual path not show this option auto info = FileInfo::fromUri(uri); if (info->isVirtual()) return actions; QAction *dirAction = new QAction(QIcon::fromTheme("utilities-terminal"), tr("Open Directory in Terminal"), nullptr); dirAction->connect(dirAction, &QAction::triggered, [=](){ openTerminal(); }); actions<isVirtual()) return actions; if (info->isDir()) { m_uri = selectionUris.first(); QAction *dirAction = new QAction(QIcon::fromTheme("utilities-terminal"), tr("Open Directory in Terminal"), nullptr); dirAction->connect(dirAction, &QAction::triggered, [=](){ openTerminal(); }); actions<. * * Authors: Meihong * */ #ifndef PEONYQTSETWALLPAPER_GLOBAL_H #define PEONYQTSETWALLPAPER_GLOBAL_H #include #if defined(PEONYSETWALLPAPER_LIBRARY) # define PEONYQTSETWALLPAPERSHARED_EXPORT Q_DECL_EXPORT #else # define PEONYQTSETWALLPAPERSHARED_EXPORT Q_DECL_IMPORT #endif #endif // PEONYQTSETWALLPAPER_GLOBAL_H peony-extensions/peony-set-wallpaper/translations/0000775000175000017500000000000015156143275021471 5ustar fengfengpeony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_kk_KZ.ts0000664000175000017500000000201015156143137031042 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension مودەن-Qt بىر جۇرىس تام قاعازدى ۇزارتۋ Set wallpaper Extension set wallpaper Extension. тұсқағаздар кеңейтілімін орнатыңыз. Set as wallpaper تام قاعازى ورىنداپ بەكٸتۋ peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_zh_CN.ts0000664000175000017500000000205515156143275031046 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension 文件管理器设置壁纸插件 Set wallpaper Extension 设置壁纸插件 set wallpaper Extension 设置壁纸插件 set wallpaper Extension. 设置壁纸插件。 Set as wallpaper 设为壁纸 peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_mn.ts0000664000175000017500000000240215156143137030450 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension ᠹᠠᠢᠯ ᠤ᠋ᠨ ᠬᠠᠮᠢᠶᠠᠷᠤᠭᠤᠷ ᠬᠠᠨ᠎ᠠ ᠵᠢᠨ ᠴᠠᠭᠠᠰᠤᠨ ᠤ᠋ ᠤᠭᠯᠤᠷᠭ᠎ᠠ ᠲᠤᠨᠤᠭ ᠢ᠋ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ Set wallpaper Extension set wallpaper Extension. ᠬᠠᠨ᠎ᠠ ᠵᠢᠨ ᠴᠠᠭᠠᠰᠤᠨ ᠤ᠋ ᠤᠭᠯᠤᠷᠭ᠎ᠠ ᠲᠤᠨᠤᠭ ᠢ᠋ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ. Set as wallpaper ᠬᠠᠨ᠎ᠠ ᠵᠢᠨ ᠴᠠᠭᠠᠰᠤ ᠪᠤᠯᠭᠠᠵᠤ ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_es.ts0000664000175000017500000000211515156143137030446 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension Peony-Qt set fondo de pantalla Extensión Set wallpaper Extension set wallpaper Extension set wallpaper Extensión set wallpaper Extension. 设置壁纸插件。 Set as wallpaper Establecer como fondo de pantalla peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_bo_CN.ts0000664000175000017500000000222215156143137031016 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension ཡིག་ཆའི་དོ་དམ་ཆས་ལྡེབས་ཤོག་བསྒར་ལྷུ་སྒྲིག་འགོད་བྱེད་པ། Set wallpaper Extension set wallpaper Extension. ལྡེབས་ཤོག་བསྒར་ལྷུ་སྒྲིག་འགོད། Set as wallpaper ལྡེབས་ཤོག་ཏུ་བཙུགས་པ། peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_ug_CN.ts0000664000175000017500000000200615156143137031031 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension مودەن-Qt يۈرۈشلۈك تام قەغىزىنى ئۇزارتىش Set wallpaper Extension set wallpaper Extension. تام قەغىزىنى ئۇزارتىشنى بەلگىلەش. Set as wallpaper تام قەغىزى قىلىپ بېكىتىش peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_zh_HK.ts0000664000175000017500000000205015156143275031043 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension Peony-Qt 套裝壁紙 擴展 Set wallpaper Extension 設置壁紙擴展 set wallpaper Extension 设置壁纸插件 set wallpaper Extension. 设置壁纸插件。 Set as wallpaper 設置為壁紙 peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_ky_KG.ts0000664000175000017500000000177015156143137031051 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension مودەن-Qt ۅتۉمدۉ تام قاعازىن ۇزارتۇۇ Set wallpaper Extension set wallpaper Extension. орнотуу дубал кагаз кеңейтүү. Set as wallpaper تام قاعازى جاساپ بەكىتۉۉ peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_de.ts0000664000175000017500000000210615156143137030427 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension Pfingstrose-Qt Set Tapete Erweiterung Set wallpaper Extension set wallpaper Extension Set Wallpaper Erweiterung set wallpaper Extension. 设置壁纸插件。 Set as wallpaper Als Hintergrundbild festlegen peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_tr.ts0000664000175000017500000000167715156143137030500 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension Peony-Qt duvar kağıdı ayarlama eklentisi Set wallpaper Extension set wallpaper Extension. Duvarkağıdı eklentisi ayarla. Set as wallpaper Duvar kağıdı olarak ayarla peony-extensions/peony-set-wallpaper/translations/peony-set-wallpaper-extension_fr.ts0000664000175000017500000000211415156143137030445 0ustar fengfeng Peony::SetWallPaperPlugin Peony-Qt set wallpaper Extension Peony-Qt set fond d’écran Extension Set wallpaper Extension set wallpaper Extension set fond d’écran Extension set wallpaper Extension. 设置壁纸插件。 Set as wallpaper Définir comme fond d’écran peony-extensions/peony-set-wallpaper/peony-set-wallpaper.pro0000664000175000017500000000437715156143275023415 0ustar fengfeng#------------------------------------------------- # # Project created by QtCreator 2020-06-11T13:45:26 # #------------------------------------------------- QT += widgets TARGET = peony-set-wallpaper TEMPLATE = lib DEFINES += PEONYSETWALLPAPER_LIBRARY include(../common.pri) # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 CONFIG += debug link_pkgconfig plugin PKGCONFIG += peony gsettings-qt6 gio-2.0 TRANSLATIONS = translations/peony-set-wallpaper-extension_zh_CN.ts \ translations/peony-set-wallpaper-extension_de.ts \ translations/peony-set-wallpaper-extension_es.ts \ translations/peony-set-wallpaper-extension_fr.ts \ translations/peony-set-wallpaper-extension_kk_KZ.ts \ translations/peony-set-wallpaper-extension_ug_CN.ts \ translations/peony-set-wallpaper-extension_ky_KG.ts \ translations/peony-set-wallpaper-extension_tr.ts \ translations/peony-set-wallpaper-extension_bo_CN.ts \ translations/peony-set-wallpaper-extension_mn.ts \ translations/peony-set-wallpaper-extension_zh_HK.ts #DESTDIR += ../testdir SOURCES += \ set-wallpaper-plugin.cpp HEADERS += \ set-wallpaper-plugin.h \ peony-set-wallpaper_global.h unix { target.path = $$[QT_INSTALL_LIBS]/peony-extensions INSTALLS += target } #RESOURCES += \ # peony-set-wallpaper.qrc CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = /translations/ SKIP_TEST = $$(EXTENSIONS_SKIP_TEST) isEmpty(SKIP_TEST) { message("build with tests") QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage LIBS += -lgcov } peony-extensions/peony-set-wallpaper/set-wallpaper-plugin.cpp0000664000175000017500000000627015156143275023535 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Meihong * */ #include "set-wallpaper-plugin.h" #include #include #include #include #include #include #include #include #include #include #define BACKGROUND_SETTINGS "org.mate.background" #define PICTRUE "picture-filename" #define BACKGROUND_SETTINGS_PATH "/org/mate/desktop/background/" #include using namespace Peony; SetWallPaperPlugin::SetWallPaperPlugin(QObject *parent) : QObject (parent) { QTranslator *t = new QTranslator(this); QString path = ":/translations/peony-set-wallpaper-extension_"+QLocale::system().name(); qDebug()<<"system().name and path:"<load(path); QApplication::installTranslator(t); } QList SetWallPaperPlugin::menuActions(Types types, const QString &uri, const QStringList &selectionUris) { QList actions; if (types == MenuPluginInterface::DirectoryView || types == MenuPluginInterface::DesktopWindow) { if (selectionUris.count() == 1 && is_picture_file(selectionUris.first())) { if (selectionUris.first().contains("trash:///")) return actions; g_autoptr (GSettings) background_settings = g_settings_new_with_path(BACKGROUND_SETTINGS, BACKGROUND_SETTINGS_PATH); if (background_settings && g_settings_is_writable(background_settings, PICTRUE)) { QAction *set_action = new QAction(tr("Set as wallpaper"), nullptr); actions<trySet("pictureFilename", url.path()); qDebug() << "set as wallpaper result:" <. * * Authors: Meihong * */ #ifndef SETWALLPAPERPLUGIN_H #define SETWALLPAPERPLUGIN_H #include "peony-set-wallpaper_global.h" #include #include namespace Peony { class PEONYQTSETWALLPAPERSHARED_EXPORT SetWallPaperPlugin: public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit SetWallPaperPlugin(QObject *parent = nullptr); PluginInterface::PluginType pluginType() override {return PluginInterface::MenuPlugin;} const QString name() override {return tr("Peony-Qt set wallpaper Extension");} const QString description() override {return tr("Set wallpaper Extension");} const QIcon icon() override {return QIcon::fromTheme("preferences-desktop-wallpaper-symbolic");} void setEnable(bool enable) override {m_enable = enable;} bool isEnable() override {return m_enable;} QString testPlugin() override {return "test set wallpaper";} QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; bool is_picture_file(QString file_name); private: bool m_enable; QStringList m_picture_type_list = {"png", "jpg", "jpeg", "bmp", "dib", "jfif", "jpe", "gif", "tif", "tiff", "wdp"}; QGSettings *m_bg_settings = nullptr; }; } #endif // SETWALLPAPERPLUGIN_H peony-extensions/tests/0000775000175000017500000000000015156143275014202 5ustar fengfengpeony-extensions/tests/extensions-test/0000775000175000017500000000000015156143275017356 5ustar fengfengpeony-extensions/tests/extensions-test/main.cpp0000664000175000017500000000173115156143275021010 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2024, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang */ #include #include int main(int argc, char *argv[]) { QApplication app(argc, argv); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } peony-extensions/tests/extensions-test/extensions-test.pro0000664000175000017500000000531315156143275023256 0ustar fengfengQT += core widgets dbus gui widgets concurrent testlib TEMPLATE = app CONFIG += console c++14 DEFINES += QT_STATICPLUGIN CONFIG += link_pkgconfig plugin c++14 testcase no_testcase_installs no_keywords lrelease debug PKGCONFIG += peony gio-2.0 udisks2 glib-2.0 gio-unix-2.0 libnotify LIBS += -lpeony -lgcov DEFINES += PEONYENGRAMPAMENUPLUGIN_LIBRARY PLUGIN_INSTALL_DIRS = $$OUT_PWD/../../ DEFINES += PLUGIN_INSTALL_DIRS='\\"$${PLUGIN_INSTALL_DIRS}\\"' VERSION = 3.2.2 DEFINES += VERSION='\\"$${VERSION}\\"' include(../../common.pri) include(../test-utils.pri) include(../../peony-extension-computer-view/computer-view/computer-view.pri) include(../../peony-extension-computer-view/computer-view-intel/computer-view-intel.pri) #PEONY_EXTENSIONS_PATH = $$PWD/../../ #HEADERS += \ # $$files($$PEONY_EXTENSIONS_PATH/peony-share/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-drive-rename/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-send-to-device/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-set-wallpaper/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-bluetooth-plugin/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-engrampa-menu-plugin/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-menu-plugin-mate-terminal/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/computer-view/*.h) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/computer-view-intel/*.h) \ SOURCES += \ tst_extensionstest.cpp \ main.cpp \ # $$files($$PEONY_EXTENSIONS_PATH/peony-share/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-drive-rename/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-send-to-device/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-set-wallpaper/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-bluetooth-plugin/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-engrampa-menu-plugin/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-menu-plugin-mate-terminal/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/computer-view/*.cpp) \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/computer-view-intel/*.cpp) \ #FORMS += \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/*.ui) #RESOURCES += \ # $$files($$PEONY_EXTENSIONS_PATH/peony-extension-computer-view/computer-view-intel/*.qrc) exists("/usr/include/libkyudfburn/udfburn_global.h") { DEFINES += KY_UDF_BURN } # kyudfburn contains(DEFINES, "KY_UDF_BURN") { PKGCONFIG += kyudfburn } peony-extensions/tests/extensions-test/tst_extensionstest.cpp0000664000175000017500000002702415156143275024060 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2024, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Wenjie Xiang */ #include #include #include #include #include "stubext.h" #include "menu-plugin-iface.h" #include "file-info-job.h" #include "file-info.h" #include "directory-view-plugin-iface2.h" #include "properties-window-tab-page-plugin-iface.h" using namespace testing; class extensionsTest : public testing::Test { public: QPluginLoader loader; QObject *plugin = nullptr; Peony::MenuPluginInterface *iface = nullptr; Peony::DirectoryViewPluginIface2 *viewIface = nullptr; Peony::PropertiesWindowTabPagePluginIface *tabPageIface = nullptr; private: //打桩声明 stub_ext::StubExt stub; }; TEST_F(extensionsTest, bluetoothPlugin) { QString path = PLUGIN_INSTALL_DIRS + QString("/peony-bluetooth-plugin/libpeony-bluetooth-plugin.so"); loader.setFileName(path); ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); plugin = loader.instance(); iface = dynamic_cast(plugin); ASSERT_NE(iface, nullptr); QString directory = "file:///data/usershare"; QStringList selectUris = (QStringList() << (directory + "/test.txt")); auto job = new Peony::FileInfoJob(Peony::FileInfo::fromUri(selectUris.first())); job->querySync(); QList actions = iface->menuActions(Peony::MenuPluginInterface::DirectoryView, directory, selectUris); ASSERT_GT(actions.size(), 0); Peony::PluginInterface::PluginType type = iface->pluginType(); ASSERT_EQ(type, Peony::PluginInterface::MenuPlugin); QString name = iface->name(); ASSERT_GT(name.size(), 0); QString description = iface->description(); ASSERT_GT(description.size(), 0); QIcon icon = iface->icon(); ASSERT_GT(icon.name().size(), 0); iface->setEnable(true); EXPECT_TRUE(iface->isEnable()); ASSERT_GT(iface->testPlugin().size(), 0); actions.first()->trigger(); } TEST_F(extensionsTest, terminalPlugin) { QString path = PLUGIN_INSTALL_DIRS + QString("/peony-menu-plugin-mate-terminal/libpeony-menu-plugin-mate-terminal.so"); loader.setFileName(path); ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); plugin = loader.instance(); iface = dynamic_cast(plugin); ASSERT_NE(iface, nullptr); QString directory = "file:///data/usershare"; QStringList selectUris = (QStringList() << (directory)); auto job = new Peony::FileInfoJob(Peony::FileInfo::fromUri(selectUris.first())); job->querySync(); QList actions = iface->menuActions(Peony::MenuPluginInterface::DirectoryView, directory, selectUris); ASSERT_GT(actions.size(), 0); Peony::PluginInterface::PluginType type = iface->pluginType(); ASSERT_EQ(type, Peony::PluginInterface::MenuPlugin); QString name = iface->name(); ASSERT_GT(name.size(), 0); QString description = iface->description(); ASSERT_GT(description.size(), 0); QIcon icon = iface->icon(); ASSERT_GT(icon.name().size(), 0); iface->setEnable(true); EXPECT_TRUE(iface->isEnable()); ASSERT_GT(iface->testPlugin().size(), 0); actions.first()->trigger(); } TEST_F(extensionsTest, driveRenamePlugin) { QString path = PLUGIN_INSTALL_DIRS + QString("/peony-drive-rename/libpeony-drive-rename.so"); loader.setFileName(path); ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); plugin = loader.instance(); iface = dynamic_cast(plugin); ASSERT_NE(iface, nullptr); QString directory = "computer:///SKHynix_HFS512GD9TNI-L2B0B-3.drive"; QStringList selectUris = (QStringList() << (directory)); QList actions = iface->menuActions(Peony::MenuPluginInterface::SideBar, directory, selectUris); ASSERT_GT(actions.size(), 0); Peony::PluginInterface::PluginType type = iface->pluginType(); ASSERT_EQ(type, Peony::PluginInterface::MenuPlugin); QString name = iface->name(); ASSERT_GT(name.size(), 0); QString description = iface->description(); ASSERT_GT(description.size(), 0); QIcon icon = iface->icon(); ASSERT_GT(icon.name().size(), 0); iface->setEnable(true); EXPECT_TRUE(iface->isEnable()); ASSERT_EQ(iface->testPlugin().size(), 0); actions.first()->trigger(); } //TEST_F(extensionsTest, computerViewPlugin) //{ // QString path = PLUGIN_INSTALL_DIRS + QString("/peony-extension-computer-view/libpeony-computer-view-plugin.so"); // loader.setFileName(path); // ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); // plugin = loader.instance(); // viewIface = dynamic_cast(plugin); // ASSERT_NE(viewIface, nullptr); // Peony::PluginInterface::PluginType type = viewIface->pluginType(); // ASSERT_EQ(type, Peony::PluginInterface::DirectoryViewPlugin2); // QString name = viewIface->name(); // ASSERT_GT(name.size(), 0); // QString description = viewIface->description(); // ASSERT_GT(description.size(), 0); // QIcon icon = viewIface->icon(); // ASSERT_GT(icon.name().size(), 0); // viewIface->setEnable(true); // EXPECT_TRUE(viewIface->isEnable()); // ASSERT_GT(viewIface->viewIdentity().size(), 0); // ASSERT_NE(viewIface->viewName(), nullptr); // EXPECT_FALSE(viewIface->viewIcon().isNull()); // EXPECT_TRUE(viewIface->supportUri("computer:///")); // ASSERT_EQ(viewIface->zoom_level_hint(), -1); // ASSERT_EQ(viewIface->minimumSupportedZoomLevel(), -1); // ASSERT_EQ(viewIface->maximumSupportedZoomLevel(), -1); // EXPECT_TRUE(viewIface->priority("computer:///")); // EXPECT_FALSE(viewIface->supportZoom()); // ASSERT_NE(viewIface->create(), nullptr); //} TEST_F(extensionsTest, engrampaPlugin) { QString path = PLUGIN_INSTALL_DIRS + QString("/peony-engrampa-menu-plugin/libpeony-engrampa-menu-plugin.so"); loader.setFileName(path); ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); plugin = loader.instance(); iface = dynamic_cast(plugin); ASSERT_NE(iface, nullptr); QString directory = "file:///data/usershare"; QStringList selectUris = (QStringList() << (directory + "/test.txt")); auto job = new Peony::FileInfoJob(Peony::FileInfo::fromUri(selectUris.first())); job->querySync(); QList actions = iface->menuActions(Peony::MenuPluginInterface::DirectoryView, directory, selectUris); ASSERT_GT(actions.size(), 0); Peony::PluginInterface::PluginType type = iface->pluginType(); ASSERT_EQ(type, Peony::PluginInterface::MenuPlugin); QString name = iface->name(); ASSERT_GT(name.size(), 0); QString description = iface->description(); ASSERT_GT(description.size(), 0); QIcon icon = iface->icon(); ASSERT_GT(icon.name().size(), 0); iface->setEnable(true); EXPECT_TRUE(iface->isEnable()); ASSERT_GT(iface->testPlugin().size(), 0); actions.first()->trigger(); } TEST_F(extensionsTest, sendToDevicePlugin) { QString path = PLUGIN_INSTALL_DIRS + QString("/peony-send-to-device/libpeony-send-to-device.so"); loader.setFileName(path); ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); plugin = loader.instance(); iface = dynamic_cast(plugin); ASSERT_NE(iface, nullptr); QString directory = "file:///data/usershare"; QStringList selectUris = (QStringList() << (directory + "/test.txt")); auto job = new Peony::FileInfoJob(Peony::FileInfo::fromUri(selectUris.first())); job->querySync(); QList actions = iface->menuActions(Peony::MenuPluginInterface::DirectoryView, directory, selectUris); ASSERT_GT(actions.size(), 0); Peony::PluginInterface::PluginType type = iface->pluginType(); ASSERT_EQ(type, Peony::PluginInterface::MenuPlugin); QString name = iface->name(); ASSERT_GT(name.size(), 0); QString description = iface->description(); ASSERT_GT(description.size(), 0); QIcon icon = iface->icon(); ASSERT_GT(icon.name().size(), 0); iface->setEnable(true); EXPECT_TRUE(iface->isEnable()); ASSERT_EQ(iface->testPlugin().size(), 0); actions.first()->trigger(); } TEST_F(extensionsTest, setWallpaperPlugin) { QString path = PLUGIN_INSTALL_DIRS + QString("/peony-set-wallpaper/libpeony-set-wallpaper.so"); loader.setFileName(path); ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); plugin = loader.instance(); iface = dynamic_cast(plugin); ASSERT_NE(iface, nullptr); QString directory = "file:///data/usershare"; QStringList selectUris = (QStringList() << (directory + "/test.png")); auto job = new Peony::FileInfoJob(Peony::FileInfo::fromUri(selectUris.first())); job->querySync(); QList actions = iface->menuActions(Peony::MenuPluginInterface::DirectoryView, directory, selectUris); ASSERT_GT(actions.size(), 0); Peony::PluginInterface::PluginType type = iface->pluginType(); ASSERT_EQ(type, Peony::PluginInterface::MenuPlugin); QString name = iface->name(); ASSERT_GT(name.size(), 0); QString description = iface->description(); ASSERT_GT(description.size(), 0); QIcon icon = iface->icon(); ASSERT_GT(icon.name().size(), 0); iface->setEnable(true); EXPECT_TRUE(iface->isEnable()); ASSERT_GT(iface->testPlugin().size(), 0); actions.first()->trigger(); } //TEST_F(extensionsTest, sharePlugin) //{ // QString path = PLUGIN_INSTALL_DIRS + QString("/peony-share/app/libpeony-share.so"); // loader.setFileName(path); // ASSERT_EQ(loader.metaData().value("MetaData").toObject().value("version").toString(), VERSION); // plugin = loader.instance(); // tabPageIface = dynamic_cast(plugin); // ASSERT_NE(tabPageIface, nullptr); // QString directory = "file:///data/usershare"; // QStringList selectUris = (QStringList() << (directory + "/test")); // auto job = new Peony::FileInfoJob(Peony::FileInfo::fromUri(selectUris.first())); // job->querySync(); // Peony::PluginInterface::PluginType type = tabPageIface->pluginType(); // ASSERT_EQ(type, Peony::PluginInterface::PropertiesWindowPlugin); // QString name = tabPageIface->name(); // ASSERT_GT(name.size(), 0); // QString description = tabPageIface->description(); // ASSERT_GT(description.size(), 0); // QIcon icon = tabPageIface->icon(); // ASSERT_GT(icon.name().size(), 0); // tabPageIface->setEnable(true); // EXPECT_TRUE(tabPageIface->isEnable()); // ASSERT_EQ(tabPageIface->tabOrder(), 99); // EXPECT_TRUE(tabPageIface->supportUris(selectUris)); // ASSERT_NE(tabPageIface->createTabPage(selectUris), nullptr); //} peony-extensions/tests/test-utils.pri0000664000175000017500000000120415156143275017030 0ustar fengfeng# test-utils.pri CONFIG += console c++11 link_pkgconfig CONFIG += thread TEST_UTILS_PATH = $$_PRO_FILE_PWD_/../kt-test-utils INCLUDEPATH += $$TEST_UTILS_PATH/cpp-stub \ $$TEST_UTILS_PATH/stub-ext \ HEADERS += $$files($$TEST_UTILS_PATH/cpp-stub/*.h) \ $$files($$TEST_UTILS_PATH/cpp-stub/*.hpp) \ $$files($$TEST_UTILS_PATH/stub-ext/*.h) SOURCES += $$files($$TEST_UTILS_PATH/cpp-stub/*.cpp) \ $$files($$TEST_UTILS_PATH/stub-ext/*.cpp) # 覆盖率 QMAKE_LFLAGS +=-fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage -fno-inline -fno-access-control -fno-exceptions LIBS += -lgtest -lgcov peony-extensions/tests/kt-test-utils/0000775000175000017500000000000015156143275016733 5ustar fengfengpeony-extensions/tests/kt-test-utils/cpp-stub/0000775000175000017500000000000015156143275020470 5ustar fengfengpeony-extensions/tests/kt-test-utils/cpp-stub/addr_pri.h0000664000175000017500000002436415156143275022436 0ustar fengfeng#ifndef __ADDR_PRI_H__ #define __ADDR_PRI_H__ #include #include //base on C++11 /********************************************************** access private function **********************************************************/ namespace std { template using enable_if_t = typename enable_if::type; template using remove_reference_t = typename remove_reference::type; } // std // Unnamed namespace is used to avoid duplicate symbols if the macros are used namespace { namespace private_access_detail { // @tparam TagType, used to declare different "get" funciton overloads for // different members/statics template struct private_access { // Normal lookup cannot find in-class defined (inline) friend functions. friend PtrType get(TagType) { return PtrValue; } }; } // namespace private_access_detail } // namespace // Used macro naming conventions: // The "namespace" of this macro library is PRIVATE_ACCESS, i.e. all // macro here has this prefix. // All implementation macro, which are not meant to be used directly have the // PRIVATE_ACCESS_DETAIL prefix. // Some macros have the ABCD_IMPL form, which means they contain the // implementation details for the specific ABCD macro. #define PRIVATE_ACCESS_DETAIL_CONCATENATE_IMPL(x, y) x##y #define PRIVATE_ACCESS_DETAIL_CONCATENATE(x, y) \ PRIVATE_ACCESS_DETAIL_CONCATENATE_IMPL(x, y) // @param PtrTypeKind E.g if we have "class A", then it can be "A::*" in case of // members, or it can be "*" in case of statics. #define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, \ PtrTypeKind) \ namespace { \ namespace private_access_detail { \ /* Tag type, used to declare different get funcitons for different \ * members \ */ \ struct Tag {}; \ /* Explicit instantiation */ \ template struct private_access; \ /* We can build the PtrType only with two aliases */ \ /* E.g. using PtrType = int(int) *; would be illformed */ \ using PRIVATE_ACCESS_DETAIL_CONCATENATE(Alias_, Tag) = Type; \ using PRIVATE_ACCESS_DETAIL_CONCATENATE(PtrType_, Tag) = \ PRIVATE_ACCESS_DETAIL_CONCATENATE(Alias_, Tag) PtrTypeKind; \ /* Declare the friend function, now it is visible in namespace scope. \ * Note, \ * we could declare it inside the Tag type too, in that case ADL would \ * find \ * the declaration. By choosing to declare it here, the Tag type remains \ * a \ * simple tag type, it has no other responsibilities. */ \ PRIVATE_ACCESS_DETAIL_CONCATENATE(PtrType_, Tag) get(Tag); \ } \ } #define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FIELD(Tag, Class, Type, Name) \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, Class::*) \ namespace { \ namespace access_private_field { \ Type &Class##Name(Class &&t) { return t.*get(private_access_detail::Tag{}); } \ Type &Class##Name(Class &t) { return t.*get(private_access_detail::Tag{}); } \ /* The following usings are here to avoid duplicate const qualifier \ * warnings \ */ \ using PRIVATE_ACCESS_DETAIL_CONCATENATE(X, Tag) = Type; \ using PRIVATE_ACCESS_DETAIL_CONCATENATE(Y, Tag) = \ const PRIVATE_ACCESS_DETAIL_CONCATENATE(X, Tag); \ PRIVATE_ACCESS_DETAIL_CONCATENATE(Y, Tag) & Class##Name(const Class &t) {\ return t.*get(private_access_detail::Tag{}); \ } \ } \ } #define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FUN(Tag, Class, Type, Name) \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, Class::*) \ namespace { \ namespace call_private_fun { \ /* We do perfect forwarding, but we want to restrict the overload set \ * only for objects which have the type Class. */ \ template , \ Class>::value> * = nullptr, \ typename... Args> \ auto Class##Name(Obj &&o, Args &&... args) -> decltype( \ (std::forward(o).* \ get(private_access_detail::Tag{}))(std::forward(args)...)) { \ return (std::forward(o).*get(private_access_detail::Tag{}))( \ std::forward(args)...); \ } \ } \ namespace get_private_fun { \ auto Class##Name() -> decltype( \ get(private_access_detail::Tag{})) { \ return (get(private_access_detail::Tag{})); \ } \ } \ } #define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FIELD(Tag, Class, Type, \ Name) \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, *) \ namespace { \ namespace access_private_static_field { \ namespace Class { \ Type &Class##Name() { return *get(private_access_detail::Tag{}); } \ } \ } \ } #define PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FUN(Tag, Class, Type, \ Name) \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE(Tag, Class, Type, Name, *) \ namespace { \ namespace call_private_static_fun { \ namespace Class { \ template \ auto Class##Name(Args &&... args) -> decltype( \ get(private_access_detail::Tag{})(std::forward(args)...)) { \ return get(private_access_detail::Tag{})( \ std::forward(args)...); \ } \ } \ } \ namespace get_private_static_fun { \ namespace Class { \ auto Class##Name() -> decltype(get(private_access_detail::Tag{})) { \ return get(private_access_detail::Tag{}); \ } \ } \ } \ } #define PRIVATE_ACCESS_DETAIL_UNIQUE_TAG \ PRIVATE_ACCESS_DETAIL_CONCATENATE(PrivateAccessTag, __COUNTER__) #define ACCESS_PRIVATE_FIELD(Class, Type, Name) \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FIELD(PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, \ Class, Type, Name) #define ACCESS_PRIVATE_FUN(Class, Type, Name) \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_FUN(PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, \ Class, Type, Name) #define ACCESS_PRIVATE_STATIC_FIELD(Class, Type, Name) \ Type Class::Name; \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FIELD( \ PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, Class, Type, Name) #define ACCESS_PRIVATE_STATIC_FUN(Class, Type, Name) \ PRIVATE_ACCESS_DETAIL_ACCESS_PRIVATE_STATIC_FUN( \ PRIVATE_ACCESS_DETAIL_UNIQUE_TAG, Class, Type, Name) #endif peony-extensions/tests/kt-test-utils/cpp-stub/addr_any.h0000664000175000017500000002113415156143275022423 0ustar fengfeng#ifndef __ADDR_ANY_H__ #define __ADDR_ANY_H__ //linux #include #include //c #include #include #include //c++ #include #include //project #include "elfio.hpp" class AddrAny { public: AddrAny() { m_init = get_exe_pathname(m_fullname); m_baseaddr = 0; } AddrAny(std::string libname) { m_init = get_lib_pathname_and_baseaddr(libname, m_fullname, m_baseaddr); } int get_local_func_addr_symtab(std::string func_name_regex_str, std::map& result) { return get_func_addr(SHT_SYMTAB, STB_LOCAL, func_name_regex_str, result); } int get_global_func_addr_symtab(std::string func_name_regex_str, std::map& result) { return get_func_addr(SHT_SYMTAB, STB_GLOBAL, func_name_regex_str, result); } int get_weak_func_addr_symtab(std::string func_name_regex_str, std::map& result) { return get_func_addr(SHT_SYMTAB, STB_WEAK, func_name_regex_str, result); } int get_global_func_addr_dynsym( std::string func_name_regex_str, std::map& result) { return get_func_addr(SHT_DYNSYM, STB_GLOBAL, func_name_regex_str, result); } int get_weak_func_addr_dynsym(std::string func_name_regex_str, std::map& result) { return get_func_addr(SHT_DYNSYM, STB_WEAK, func_name_regex_str, result); } private: bool demangle(std::string& s, std::string& name) { int status; char* pname = abi::__cxa_demangle(s.c_str(), 0, 0, &status); if (status != 0) { switch(status) { case -1: name = "memory allocation error"; break; case -2: name = "invalid name given"; break; case -3: name = "internal error: __cxa_demangle: invalid argument"; break; default: name = "unknown error occured"; break; } return false; } name = pname; free(pname); return true; } bool get_exe_pathname( std::string& name) { char line[512]; FILE *fp; uintptr_t base_addr; char perm[5]; unsigned long offset; int pathname_pos; char *pathname; size_t pathname_len; int match = 0; if(NULL == (fp = fopen("/proc/self/maps", "r"))) { return false; } while(fgets(line, sizeof(line), fp)) { if(sscanf(line, "%" PRIxPTR "-%*lx %4s %lx %*x:%*x %*d%n", &base_addr, perm, &offset, &pathname_pos) != 3) continue; if(0 != offset) continue; //get pathname while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1)) pathname_pos += 1; if(pathname_pos >= (int)(sizeof(line) - 1)) continue; pathname = line + pathname_pos; pathname_len = strlen(pathname); if(0 == pathname_len) continue; if(pathname[pathname_len - 1] == '\n') { pathname[pathname_len - 1] = '\0'; pathname_len -= 1; } if(0 == pathname_len) continue; if('[' == pathname[0]) continue; name = pathname; match = 1; break; } fclose(fp); if(0 == match) { return false; } else { return true; } } bool get_lib_pathname_and_baseaddr(std::string pathname_regex_str, std::string& name, unsigned long& addr) { char line[512]; FILE *fp; uintptr_t base_addr; char perm[5]; unsigned long offset; int pathname_pos; char *pathname; size_t pathname_len; int match; regex_t pathname_regex; regcomp(&pathname_regex, pathname_regex_str.c_str(), 0); if(NULL == (fp = fopen("/proc/self/maps", "r"))) { return false; } while(fgets(line, sizeof(line), fp)) { if(sscanf(line, "%" PRIxPTR "-%*lx %4s %lx %*x:%*x %*d%n", &base_addr, perm, &offset, &pathname_pos) != 3) continue; //check permission if(perm[0] != 'r') continue; if(perm[3] != 'p') continue; //do not touch the shared memory //check offset // //We are trying to find ELF header in memory. //It can only be found at the beginning of a mapped memory regions //whose offset is 0. if(0 != offset) continue; //get pathname while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1)) pathname_pos += 1; if(pathname_pos >= (int)(sizeof(line) - 1)) continue; pathname = line + pathname_pos; pathname_len = strlen(pathname); if(0 == pathname_len) continue; if(pathname[pathname_len - 1] == '\n') { pathname[pathname_len - 1] = '\0'; pathname_len -= 1; } if(0 == pathname_len) continue; if('[' == pathname[0]) continue; //check pathname //if we need to hook this elf? match = 0; if(0 == regexec(&pathname_regex, pathname, 0, NULL, 0)) { match = 1; name = pathname; addr = (unsigned long)base_addr; break; } if(0 == match) continue; } fclose(fp); if(0 == match) { return false; } else { return true; } } int get_func_addr(unsigned int ttype, unsigned int stype, std::string& func_name_regex_str, std::map& result) { // Create an elfio reader ELFIO::elfio reader; int count = 0; regex_t pathname_regex; if(!m_init) { return -1; } regcomp(&pathname_regex, func_name_regex_str.c_str(), 0); // Load ELF data if(!reader.load(m_fullname.c_str())) { return -1; } ELFIO::Elf_Half sec_num = reader.sections.size(); for(int i = 0; i < sec_num; ++i) { ELFIO::section* psec = reader.sections[i]; // Check section type if(psec->get_type() == ttype) { const ELFIO::symbol_section_accessor symbols( reader, psec ); for ( unsigned int j = 0; j < symbols.get_symbols_num(); ++j ) { std::string name; std::string name_mangle; ELFIO::Elf64_Addr value; ELFIO::Elf_Xword size; unsigned char bind; unsigned char type; ELFIO::Elf_Half section_index; unsigned char other; // Read symbol properties symbols.get_symbol( j, name, value, size, bind, type, section_index, other ); if(type == STT_FUNC && bind == stype) { bool ret = demangle(name,name_mangle); if(ret == true) { if (0 == regexec(&pathname_regex, name_mangle.c_str(), 0, NULL, 0)) { result.insert ( std::pair(name_mangle,(void*)(value + m_baseaddr))); count++; } } else { if (0 == regexec(&pathname_regex, name.c_str(), 0, NULL, 0)) { result.insert ( std::pair(name,(void*)(value + m_baseaddr))); count++; } } } } break; } } return count; } private: bool m_init; std::string m_name; std::string m_fullname; unsigned long m_baseaddr; }; #endif peony-extensions/tests/kt-test-utils/cpp-stub/elfio.hpp0000664000175000017500000045310415156143275022306 0ustar fengfeng /*** Start of inlined file: elfio_dump.hpp ***/ #ifndef ELFIO_DUMP_HPP #define ELFIO_DUMP_HPP #include #include #include #include #include /*** Start of inlined file: elfio.hpp ***/ #ifndef ELFIO_HPP #define ELFIO_HPP #ifdef _MSC_VER #pragma warning( push ) #pragma warning( disable : 4996 ) #pragma warning( disable : 4355 ) #pragma warning( disable : 4244 ) #endif #include #include #include #include #include #include #include #include /*** Start of inlined file: elf_types.hpp ***/ #ifndef ELFTYPES_H #define ELFTYPES_H #ifndef ELFIO_NO_OWN_TYPES #if !defined( ELFIO_NO_CSTDINT ) && !defined( ELFIO_NO_INTTYPES ) #include #else typedef unsigned char uint8_t; typedef signed char int8_t; typedef unsigned short uint16_t; typedef signed short int16_t; #ifdef _MSC_VER typedef unsigned __int32 uint32_t; typedef signed __int32 int32_t; typedef unsigned __int64 uint64_t; typedef signed __int64 int64_t; #else typedef unsigned int uint32_t; typedef signed int int32_t; typedef unsigned long long uint64_t; typedef signed long long int64_t; #endif // _MSC_VER #endif // ELFIO_NO_CSTDINT #endif // ELFIO_NO_OWN_TYPES namespace ELFIO { // Attention! Platform depended definitions. typedef uint16_t Elf_Half; typedef uint32_t Elf_Word; typedef int32_t Elf_Sword; typedef uint64_t Elf_Xword; typedef int64_t Elf_Sxword; typedef uint32_t Elf32_Addr; typedef uint32_t Elf32_Off; typedef uint64_t Elf64_Addr; typedef uint64_t Elf64_Off; #define Elf32_Half Elf_Half #define Elf64_Half Elf_Half #define Elf32_Word Elf_Word #define Elf64_Word Elf_Word #define Elf32_Sword Elf_Sword #define Elf64_Sword Elf_Sword /////////////////////// // ELF Header Constants // File type #define ET_NONE 0 #define ET_REL 1 #define ET_EXEC 2 #define ET_DYN 3 #define ET_CORE 4 #define ET_LOOS 0xFE00 #define ET_HIOS 0xFEFF #define ET_LOPROC 0xFF00 #define ET_HIPROC 0xFFFF #define EM_NONE 0 // No machine #define EM_M32 1 // AT&T WE 32100 #define EM_SPARC 2 // SUN SPARC #define EM_386 3 // Intel 80386 #define EM_68K 4 // Motorola m68k family #define EM_88K 5 // Motorola m88k family #define EM_486 6 // Intel 80486// Reserved for future use #define EM_860 7 // Intel 80860 #define EM_MIPS 8 // MIPS R3000 (officially, big-endian only) #define EM_S370 9 // IBM System/370 #define EM_MIPS_RS3_LE \ 10 // MIPS R3000 little-endian (Oct 4 1999 Draft) Deprecated #define EM_res011 11 // Reserved #define EM_res012 12 // Reserved #define EM_res013 13 // Reserved #define EM_res014 14 // Reserved #define EM_PARISC 15 // HPPA #define EM_res016 16 // Reserved #define EM_VPP550 17 // Fujitsu VPP500 #define EM_SPARC32PLUS 18 // Sun's "v8plus" #define EM_960 19 // Intel 80960 #define EM_PPC 20 // PowerPC #define EM_PPC64 21 // 64-bit PowerPC #define EM_S390 22 // IBM S/390 #define EM_SPU 23 // Sony/Toshiba/IBM SPU #define EM_res024 24 // Reserved #define EM_res025 25 // Reserved #define EM_res026 26 // Reserved #define EM_res027 27 // Reserved #define EM_res028 28 // Reserved #define EM_res029 29 // Reserved #define EM_res030 30 // Reserved #define EM_res031 31 // Reserved #define EM_res032 32 // Reserved #define EM_res033 33 // Reserved #define EM_res034 34 // Reserved #define EM_res035 35 // Reserved #define EM_V800 36 // NEC V800 series #define EM_FR20 37 // Fujitsu FR20 #define EM_RH32 38 // TRW RH32 #define EM_MCORE 39 // Motorola M*Core // May also be taken by Fujitsu MMA #define EM_RCE 39 // Old name for MCore #define EM_ARM 40 // ARM #define EM_OLD_ALPHA 41 // Digital Alpha #define EM_SH 42 // Renesas (formerly Hitachi) / SuperH SH #define EM_SPARCV9 43 // SPARC v9 64-bit #define EM_TRICORE 44 // Siemens Tricore embedded processor #define EM_ARC 45 // ARC Cores #define EM_H8_300 46 // Renesas (formerly Hitachi) H8/300 #define EM_H8_300H 47 // Renesas (formerly Hitachi) H8/300H #define EM_H8S 48 // Renesas (formerly Hitachi) H8S #define EM_H8_500 49 // Renesas (formerly Hitachi) H8/500 #define EM_IA_64 50 // Intel IA-64 Processor #define EM_MIPS_X 51 // Stanford MIPS-X #define EM_COLDFIRE 52 // Motorola Coldfire #define EM_68HC12 53 // Motorola M68HC12 #define EM_MMA 54 // Fujitsu Multimedia Accelerator #define EM_PCP 55 // Siemens PCP #define EM_NCPU 56 // Sony nCPU embedded RISC processor #define EM_NDR1 57 // Denso NDR1 microprocesspr #define EM_STARCORE 58 // Motorola Star*Core processor #define EM_ME16 59 // Toyota ME16 processor #define EM_ST100 60 // STMicroelectronics ST100 processor #define EM_TINYJ 61 // Advanced Logic Corp. TinyJ embedded processor #define EM_X86_64 62 // Advanced Micro Devices X86-64 processor #define EM_PDSP 63 // Sony DSP Processor #define EM_PDP10 64 // Digital Equipment Corp. PDP-10 #define EM_PDP11 65 // Digital Equipment Corp. PDP-11 #define EM_FX66 66 // Siemens FX66 microcontroller #define EM_ST9PLUS 67 // STMicroelectronics ST9+ 8/16 bit microcontroller #define EM_ST7 68 // STMicroelectronics ST7 8-bit microcontroller #define EM_68HC16 69 // Motorola MC68HC16 Microcontroller #define EM_68HC11 70 // Motorola MC68HC11 Microcontroller #define EM_68HC08 71 // Motorola MC68HC08 Microcontroller #define EM_68HC05 72 // Motorola MC68HC05 Microcontroller #define EM_SVX 73 // Silicon Graphics SVx #define EM_ST19 74 // STMicroelectronics ST19 8-bit cpu #define EM_VAX 75 // Digital VAX #define EM_CRIS 76 // Axis Communications 32-bit embedded processor #define EM_JAVELIN 77 // Infineon Technologies 32-bit embedded cpu #define EM_FIREPATH 78 // Element 14 64-bit DSP processor #define EM_ZSP 79 // LSI Logic's 16-bit DSP processor #define EM_MMIX 80 // Donald Knuth's educational 64-bit processor #define EM_HUANY 81 // Harvard's machine-independent format #define EM_PRISM 82 // SiTera Prism #define EM_AVR 83 // Atmel AVR 8-bit microcontroller #define EM_FR30 84 // Fujitsu FR30 #define EM_D10V 85 // Mitsubishi D10V #define EM_D30V 86 // Mitsubishi D30V #define EM_V850 87 // NEC v850 #define EM_M32R 88 // Renesas M32R (formerly Mitsubishi M32R) #define EM_MN10300 89 // Matsushita MN10300 #define EM_MN10200 90 // Matsushita MN10200 #define EM_PJ 91 // picoJava #define EM_OPENRISC 92 // OpenRISC 32-bit embedded processor #define EM_ARC_A5 93 // ARC Cores Tangent-A5 #define EM_XTENSA 94 // Tensilica Xtensa Architecture #define EM_VIDEOCORE 95 // Alphamosaic VideoCore processor #define EM_TMM_GPP 96 // Thompson Multimedia General Purpose Processor #define EM_NS32K 97 // National Semiconductor 32000 series #define EM_TPC 98 // Tenor Network TPC processor #define EM_SNP1K 99 // Trebia SNP 1000 processor #define EM_ST200 100 // STMicroelectronics ST200 microcontroller #define EM_IP2K 101 // Ubicom IP2022 micro controller #define EM_MAX 102 // MAX Processor #define EM_CR 103 // National Semiconductor CompactRISC #define EM_F2MC16 104 // Fujitsu F2MC16 #define EM_MSP430 105 // TI msp430 micro controller #define EM_BLACKFIN 106 // ADI Blackfin #define EM_SE_C33 107 // S1C33 Family of Seiko Epson processors #define EM_SEP 108 // Sharp embedded microprocessor #define EM_ARCA 109 // Arca RISC Microprocessor #define EM_UNICORE \ 110 // Microprocessor series from PKU-Unity Ltd. and MPRC of Peking University #define EM_EXCESS 111 // eXcess: 16/32/64-bit configurable embedded CPU #define EM_DXP 112 // Icera Semiconductor Inc. Deep Execution Processor #define EM_ALTERA_NIOS2 113 // Altera Nios II soft-core processor #define EM_CRX 114 // National Semiconductor CRX #define EM_XGATE 115 // Motorola XGATE embedded processor #define EM_C166 116 // Infineon C16x/XC16x processor #define EM_M16C 117 // Renesas M16C series microprocessors #define EM_DSPIC30F \ 118 // Microchip Technology dsPIC30F Digital Signal Controller #define EM_CE 119 // Freescale Communication Engine RISC core #define EM_M32C 120 // Renesas M32C series microprocessors #define EM_res121 121 // Reserved #define EM_res122 122 // Reserved #define EM_res123 123 // Reserved #define EM_res124 124 // Reserved #define EM_res125 125 // Reserved #define EM_res126 126 // Reserved #define EM_res127 127 // Reserved #define EM_res128 128 // Reserved #define EM_res129 129 // Reserved #define EM_res130 130 // Reserved #define EM_TSK3000 131 // Altium TSK3000 core #define EM_RS08 132 // Freescale RS08 embedded processor #define EM_res133 133 // Reserved #define EM_ECOG2 134 // Cyan Technology eCOG2 microprocessor #define EM_SCORE 135 // Sunplus Score #define EM_SCORE7 135 // Sunplus S+core7 RISC processor #define EM_DSP24 136 // New Japan Radio (NJR) 24-bit DSP Processor #define EM_VIDEOCORE3 137 // Broadcom VideoCore III processor #define EM_LATTICEMICO32 138 // RISC processor for Lattice FPGA architecture #define EM_SE_C17 139 // Seiko Epson C17 family #define EM_TI_C6000 140 // Texas Instruments TMS320C6000 DSP family #define EM_TI_C2000 141 // Texas Instruments TMS320C2000 DSP family #define EM_TI_C5500 142 // Texas Instruments TMS320C55x DSP family #define EM_res143 143 // Reserved #define EM_res144 144 // Reserved #define EM_res145 145 // Reserved #define EM_res146 146 // Reserved #define EM_res147 147 // Reserved #define EM_res148 148 // Reserved #define EM_res149 149 // Reserved #define EM_res150 150 // Reserved #define EM_res151 151 // Reserved #define EM_res152 152 // Reserved #define EM_res153 153 // Reserved #define EM_res154 154 // Reserved #define EM_res155 155 // Reserved #define EM_res156 156 // Reserved #define EM_res157 157 // Reserved #define EM_res158 158 // Reserved #define EM_res159 159 // Reserved #define EM_MMDSP_PLUS 160 // STMicroelectronics 64bit VLIW Data Signal Processor #define EM_CYPRESS_M8C 161 // Cypress M8C microprocessor #define EM_R32C 162 // Renesas R32C series microprocessors #define EM_TRIMEDIA 163 // NXP Semiconductors TriMedia architecture family #define EM_QDSP6 164 // QUALCOMM DSP6 Processor #define EM_8051 165 // Intel 8051 and variants #define EM_STXP7X 166 // STMicroelectronics STxP7x family #define EM_NDS32 \ 167 // Andes Technology compact code size embedded RISC processor family #define EM_ECOG1 168 // Cyan Technology eCOG1X family #define EM_ECOG1X 168 // Cyan Technology eCOG1X family #define EM_MAXQ30 169 // Dallas Semiconductor MAXQ30 Core Micro-controllers #define EM_XIMO16 170 // New Japan Radio (NJR) 16-bit DSP Processor #define EM_MANIK 171 // M2000 Reconfigurable RISC Microprocessor #define EM_CRAYNV2 172 // Cray Inc. NV2 vector architecture #define EM_RX 173 // Renesas RX family #define EM_METAG 174 // Imagination Technologies META processor architecture #define EM_MCST_ELBRUS 175 // MCST Elbrus general purpose hardware architecture #define EM_ECOG16 176 // Cyan Technology eCOG16 family #define EM_CR16 177 // National Semiconductor CompactRISC 16-bit processor #define EM_ETPU 178 // Freescale Extended Time Processing Unit #define EM_SLE9X 179 // Infineon Technologies SLE9X core #define EM_L1OM 180 // Intel L1OM #define EM_INTEL181 181 // Reserved by Intel #define EM_INTEL182 182 // Reserved by Intel #define EM_res183 183 // Reserved by ARM #define EM_res184 184 // Reserved by ARM #define EM_AVR32 185 // Atmel Corporation 32-bit microprocessor family #define EM_STM8 186 // STMicroeletronics STM8 8-bit microcontroller #define EM_TILE64 187 // Tilera TILE64 multicore architecture family #define EM_TILEPRO 188 // Tilera TILEPro multicore architecture family #define EM_MICROBLAZE 189 // Xilinx MicroBlaze 32-bit RISC soft processor core #define EM_CUDA 190 // NVIDIA CUDA architecture #define EM_TILEGX 191 // Tilera TILE-Gx multicore architecture family #define EM_CLOUDSHIELD 192 // CloudShield architecture family #define EM_COREA_1ST 193 // KIPO-KAIST Core-A 1st generation processor family #define EM_COREA_2ND 194 // KIPO-KAIST Core-A 2nd generation processor family #define EM_ARC_COMPACT2 195 // Synopsys ARCompact V2 #define EM_OPEN8 196 // Open8 8-bit RISC soft processor core #define EM_RL78 197 // Renesas RL78 family #define EM_VIDEOCORE5 198 // Broadcom VideoCore V processor #define EM_78KOR 199 // Renesas 78KOR family #define EM_56800EX 200 // Freescale 56800EX Digital Signal Controller (DSC) #define EM_BA1 201 // Beyond BA1 CPU architecture #define EM_BA2 202 // Beyond BA2 CPU architecture #define EM_XCORE 203 // XMOS xCORE processor family #define EM_MCHP_PIC 204 // Microchip 8-bit PIC(r) family #define EM_INTEL205 205 // Reserved by Intel #define EM_INTEL206 206 // Reserved by Intel #define EM_INTEL207 207 // Reserved by Intel #define EM_INTEL208 208 // Reserved by Intel #define EM_INTEL209 209 // Reserved by Intel #define EM_KM32 210 // KM211 KM32 32-bit processor #define EM_KMX32 211 // KM211 KMX32 32-bit processor #define EM_KMX16 212 // KM211 KMX16 16-bit processor #define EM_KMX8 213 // KM211 KMX8 8-bit processor #define EM_KVARC 214 // KM211 KVARC processor #define EM_CDP 215 // Paneve CDP architecture family #define EM_COGE 216 // Cognitive Smart Memory Processor #define EM_COOL 217 // iCelero CoolEngine #define EM_NORC 218 // Nanoradio Optimized RISC #define EM_CSR_KALIMBA 219 // CSR Kalimba architecture family #define EM_Z80 220 // Zilog Z80 #define EM_VISIUM 221 // Controls and Data Services VISIUMcore processor #define EM_FT32 222 // FTDI Chip FT32 high performance 32-bit RISC architecture #define EM_MOXIE 223 // Moxie processor family #define EM_AMDGPU 224 // AMD GPU architecture #define EM_RISCV 243 // RISC-V #define EM_LANAI 244 // Lanai processor #define EM_CEVA 245 // CEVA Processor Architecture Family #define EM_CEVA_X2 246 // CEVA X2 Processor Family #define EM_BPF 247 // Linux BPF – in-kernel virtual machine #define EM_GRAPHCORE_IPU 248 // Graphcore Intelligent Processing Unit #define EM_IMG1 249 // Imagination Technologies #define EM_NFP 250 // Netronome Flow Processor (P) #define EM_CSKY 252 // C-SKY processor family // File version #define EV_NONE 0 #define EV_CURRENT 1 // Identification index #define EI_MAG0 0 #define EI_MAG1 1 #define EI_MAG2 2 #define EI_MAG3 3 #define EI_CLASS 4 #define EI_DATA 5 #define EI_VERSION 6 #define EI_OSABI 7 #define EI_ABIVERSION 8 #define EI_PAD 9 #define EI_NIDENT 16 // Magic number #define ELFMAG0 0x7F #define ELFMAG1 'E' #define ELFMAG2 'L' #define ELFMAG3 'F' // File class #define ELFCLASSNONE 0 #define ELFCLASS32 1 #define ELFCLASS64 2 // Encoding #define ELFDATANONE 0 #define ELFDATA2LSB 1 #define ELFDATA2MSB 2 // OS extensions #define ELFOSABI_NONE 0 // No extensions or unspecified #define ELFOSABI_HPUX 1 // Hewlett-Packard HP-UX #define ELFOSABI_NETBSD 2 // NetBSD #define ELFOSABI_LINUX 3 // Linux #define ELFOSABI_SOLARIS 6 // Sun Solaris #define ELFOSABI_AIX 7 // AIX #define ELFOSABI_IRIX 8 // IRIX #define ELFOSABI_FREEBSD 9 // FreeBSD #define ELFOSABI_TRU64 10 // Compaq TRU64 UNIX #define ELFOSABI_MODESTO 11 // Novell Modesto #define ELFOSABI_OPENBSD 12 // Open BSD #define ELFOSABI_OPENVMS 13 // Open VMS #define ELFOSABI_NSK 14 // Hewlett-Packard Non-Stop Kernel #define ELFOSABI_AROS 15 // Amiga Research OS #define ELFOSABI_FENIXOS 16 // The FenixOS highly scalable multi-core OS // 64-255 Architecture-specific value range #define ELFOSABI_AMDGPU_HSA \ 64 // AMDGPU OS for HSA compatible compute // kernels. #define ELFOSABI_AMDGPU_PAL \ 65 // AMDGPU OS for AMD PAL compatible graphics // shaders and compute kernels. #define ELFOSABI_AMDGPU_MESA3D \ 66 // AMDGPU OS for Mesa3D compatible graphics // shaders and compute kernels. // AMDGPU specific e_flags #define EF_AMDGPU_MACH 0x0ff // AMDGPU processor selection mask. #define EF_AMDGPU_XNACK \ 0x100 // Indicates if the XNACK target feature is // enabled for all code contained in the ELF. // AMDGPU processors #define EF_AMDGPU_MACH_NONE 0x000 // Unspecified processor. #define EF_AMDGPU_MACH_R600_R600 0x001 #define EF_AMDGPU_MACH_R600_R630 0x002 #define EF_AMDGPU_MACH_R600_RS880 0x003 #define EF_AMDGPU_MACH_R600_RV670 0x004 #define EF_AMDGPU_MACH_R600_RV710 0x005 #define EF_AMDGPU_MACH_R600_RV730 0x006 #define EF_AMDGPU_MACH_R600_RV770 0x007 #define EF_AMDGPU_MACH_R600_CEDAR 0x008 #define EF_AMDGPU_MACH_R600_CYPRESS 0x009 #define EF_AMDGPU_MACH_R600_JUNIPER 0x00a #define EF_AMDGPU_MACH_R600_REDWOOD 0x00b #define EF_AMDGPU_MACH_R600_SUMO 0x00c #define EF_AMDGPU_MACH_R600_BARTS 0x00d #define EF_AMDGPU_MACH_R600_CAICOS 0x00e #define EF_AMDGPU_MACH_R600_CAYMAN 0x00f #define EF_AMDGPU_MACH_R600_TURKS 0x010 #define EF_AMDGPU_MACH_R600_RESERVED_FIRST 0x011 #define EF_AMDGPU_MACH_R600_RESERVED_LAST 0x01f #define EF_AMDGPU_MACH_R600_FIRST EF_AMDGPU_MACH_R600_R600 #define EF_AMDGPU_MACH_R600_LAST EF_AMDGPU_MACH_R600_TURKS #define EF_AMDGPU_MACH_AMDGCN_GFX600 0x020 #define EF_AMDGPU_MACH_AMDGCN_GFX601 0x021 #define EF_AMDGPU_MACH_AMDGCN_GFX700 0x022 #define EF_AMDGPU_MACH_AMDGCN_GFX701 0x023 #define EF_AMDGPU_MACH_AMDGCN_GFX702 0x024 #define EF_AMDGPU_MACH_AMDGCN_GFX703 0x025 #define EF_AMDGPU_MACH_AMDGCN_GFX704 0x026 #define EF_AMDGPU_MACH_AMDGCN_GFX801 0x028 #define EF_AMDGPU_MACH_AMDGCN_GFX802 0x029 #define EF_AMDGPU_MACH_AMDGCN_GFX803 0x02a #define EF_AMDGPU_MACH_AMDGCN_GFX810 0x02b #define EF_AMDGPU_MACH_AMDGCN_GFX900 0x02c #define EF_AMDGPU_MACH_AMDGCN_GFX902 0x02d #define EF_AMDGPU_MACH_AMDGCN_GFX904 0x02e #define EF_AMDGPU_MACH_AMDGCN_GFX906 0x02f #define EF_AMDGPU_MACH_AMDGCN_RESERVED0 0x027 #define EF_AMDGPU_MACH_AMDGCN_RESERVED1 0x030 #define EF_AMDGPU_MACH_AMDGCN_FIRST EF_AMDGPU_MACH_AMDGCN_GFX600 #define EF_AMDGPU_MACH_AMDGCN_LAST EF_AMDGPU_MACH_AMDGCN_GFX906 ///////////////////// // Sections constants // Section indexes #define SHN_UNDEF 0 #define SHN_LORESERVE 0xFF00 #define SHN_LOPROC 0xFF00 #define SHN_HIPROC 0xFF1F #define SHN_LOOS 0xFF20 #define SHN_HIOS 0xFF3F #define SHN_ABS 0xFFF1 #define SHN_COMMON 0xFFF2 #define SHN_XINDEX 0xFFFF #define SHN_HIRESERVE 0xFFFF // Section types #define SHT_NULL 0 #define SHT_PROGBITS 1 #define SHT_SYMTAB 2 #define SHT_STRTAB 3 #define SHT_RELA 4 #define SHT_HASH 5 #define SHT_DYNAMIC 6 #define SHT_NOTE 7 #define SHT_NOBITS 8 #define SHT_REL 9 #define SHT_SHLIB 10 #define SHT_DYNSYM 11 #define SHT_INIT_ARRAY 14 #define SHT_FINI_ARRAY 15 #define SHT_PREINIT_ARRAY 16 #define SHT_GROUP 17 #define SHT_SYMTAB_SHNDX 18 #define SHT_LOOS 0x60000000 #define SHT_HIOS 0x6fffffff #define SHT_LOPROC 0x70000000 #define SHT_HIPROC 0x7FFFFFFF #define SHT_LOUSER 0x80000000 #define SHT_HIUSER 0xFFFFFFFF // Section attribute flags #define SHF_WRITE 0x1 #define SHF_ALLOC 0x2 #define SHF_EXECINSTR 0x4 #define SHF_MERGE 0x10 #define SHF_STRINGS 0x20 #define SHF_INFO_LINK 0x40 #define SHF_LINK_ORDER 0x80 #define SHF_OS_NONCONFORMING 0x100 #define SHF_GROUP 0x200 #define SHF_TLS 0x400 #define SHF_MASKOS 0x0ff00000 #define SHF_MASKPROC 0xF0000000 // Section group flags #define GRP_COMDAT 0x1 #define GRP_MASKOS 0x0ff00000 #define GRP_MASKPROC 0xf0000000 // Symbol binding #define STB_LOCAL 0 #define STB_GLOBAL 1 #define STB_WEAK 2 #define STB_LOOS 10 #define STB_HIOS 12 #define STB_MULTIDEF 13 #define STB_LOPROC 13 #define STB_HIPROC 15 // Note types #define NT_AMDGPU_METADATA 1 #define NT_AMD_AMDGPU_HSA_METADATA 10 #define NT_AMD_AMDGPU_ISA 11 #define NT_AMD_AMDGPU_PAL_METADATA 12 // Symbol types #define STT_NOTYPE 0 #define STT_OBJECT 1 #define STT_FUNC 2 #define STT_SECTION 3 #define STT_FILE 4 #define STT_COMMON 5 #define STT_TLS 6 #define STT_LOOS 10 #define STT_AMDGPU_HSA_KERNEL 10 #define STT_HIOS 12 #define STT_LOPROC 13 #define STT_HIPROC 15 // Symbol visibility #define STV_DEFAULT 0 #define STV_INTERNAL 1 #define STV_HIDDEN 2 #define STV_PROTECTED 3 // Undefined name #define STN_UNDEF 0 // Relocation types #define R_386_NONE 0 #define R_X86_64_NONE 0 #define R_AMDGPU_NONE 0 #define R_386_32 1 #define R_X86_64_64 1 #define R_AMDGPU_ABS32_LO 1 #define R_386_PC32 2 #define R_X86_64_PC32 2 #define R_AMDGPU_ABS32_HI 2 #define R_386_GOT32 3 #define R_X86_64_GOT32 3 #define R_AMDGPU_ABS64 3 #define R_386_PLT32 4 #define R_X86_64_PLT32 4 #define R_AMDGPU_REL32 4 #define R_386_COPY 5 #define R_X86_64_COPY 5 #define R_AMDGPU_REL64 5 #define R_386_GLOB_DAT 6 #define R_X86_64_GLOB_DAT 6 #define R_AMDGPU_ABS32 6 #define R_386_JMP_SLOT 7 #define R_X86_64_JUMP_SLOT 7 #define R_AMDGPU_GOTPCREL 7 #define R_386_RELATIVE 8 #define R_X86_64_RELATIVE 8 #define R_AMDGPU_GOTPCREL32_LO 8 #define R_386_GOTOFF 9 #define R_X86_64_GOTPCREL 9 #define R_AMDGPU_GOTPCREL32_HI 9 #define R_386_GOTPC 10 #define R_X86_64_32 10 #define R_AMDGPU_REL32_LO 10 #define R_386_32PLT 11 #define R_X86_64_32S 11 #define R_AMDGPU_REL32_HI 11 #define R_X86_64_16 12 #define R_X86_64_PC16 13 #define R_AMDGPU_RELATIVE64 13 #define R_386_TLS_TPOFF 14 #define R_X86_64_8 14 #define R_386_TLS_IE 15 #define R_X86_64_PC8 15 #define R_386_TLS_GOTIE 16 #define R_X86_64_DTPMOD64 16 #define R_386_TLS_LE 17 #define R_X86_64_DTPOFF64 17 #define R_386_TLS_GD 18 #define R_X86_64_TPOFF64 18 #define R_386_TLS_LDM 19 #define R_X86_64_TLSGD 19 #define R_386_16 20 #define R_X86_64_TLSLD 20 #define R_386_PC16 21 #define R_X86_64_DTPOFF32 21 #define R_386_8 22 #define R_X86_64_GOTTPOFF 22 #define R_386_PC8 23 #define R_X86_64_TPOFF32 23 #define R_386_TLS_GD_32 24 #define R_X86_64_PC64 24 #define R_386_TLS_GD_PUSH 25 #define R_X86_64_GOTOFF64 25 #define R_386_TLS_GD_CALL 26 #define R_X86_64_GOTPC32 26 #define R_386_TLS_GD_POP 27 #define R_X86_64_GOT64 27 #define R_386_TLS_LDM_32 28 #define R_X86_64_GOTPCREL64 28 #define R_386_TLS_LDM_PUSH 29 #define R_X86_64_GOTPC64 29 #define R_386_TLS_LDM_CALL 30 #define R_X86_64_GOTPLT64 30 #define R_386_TLS_LDM_POP 31 #define R_X86_64_PLTOFF64 31 #define R_386_TLS_LDO_32 32 #define R_386_TLS_IE_32 33 #define R_386_TLS_LE_32 34 #define R_X86_64_GOTPC32_TLSDESC 34 #define R_386_TLS_DTPMOD32 35 #define R_X86_64_TLSDESC_CALL 35 #define R_386_TLS_DTPOFF32 36 #define R_X86_64_TLSDESC 36 #define R_386_TLS_TPOFF32 37 #define R_X86_64_IRELATIVE 37 #define R_386_SIZE32 38 #define R_386_TLS_GOTDESC 39 #define R_386_TLS_DESC_CALL 40 #define R_386_TLS_DESC 41 #define R_386_IRELATIVE 42 #define R_386_GOT32X 43 #define R_X86_64_GNU_VTINHERIT 250 #define R_X86_64_GNU_VTENTRY 251 // Segment types #define PT_NULL 0 #define PT_LOAD 1 #define PT_DYNAMIC 2 #define PT_INTERP 3 #define PT_NOTE 4 #define PT_SHLIB 5 #define PT_PHDR 6 #define PT_TLS 7 #define PT_LOOS 0x60000000 #define PT_HIOS 0x6fffffff #define PT_LOPROC 0x70000000 #define PT_HIPROC 0x7FFFFFFF // Segment flags #define PF_X 1 // Execute #define PF_W 2 // Write #define PF_R 4 // Read #define PF_MASKOS 0x0ff00000 // Unspecified #define PF_MASKPROC 0xf0000000 // Unspecified // Dynamic Array Tags #define DT_NULL 0 #define DT_NEEDED 1 #define DT_PLTRELSZ 2 #define DT_PLTGOT 3 #define DT_HASH 4 #define DT_STRTAB 5 #define DT_SYMTAB 6 #define DT_RELA 7 #define DT_RELASZ 8 #define DT_RELAENT 9 #define DT_STRSZ 10 #define DT_SYMENT 11 #define DT_INIT 12 #define DT_FINI 13 #define DT_SONAME 14 #define DT_RPATH 15 #define DT_SYMBOLIC 16 #define DT_REL 17 #define DT_RELSZ 18 #define DT_RELENT 19 #define DT_PLTREL 20 #define DT_DEBUG 21 #define DT_TEXTREL 22 #define DT_JMPREL 23 #define DT_BIND_NOW 24 #define DT_INIT_ARRAY 25 #define DT_FINI_ARRAY 26 #define DT_INIT_ARRAYSZ 27 #define DT_FINI_ARRAYSZ 28 #define DT_RUNPATH 29 #define DT_FLAGS 30 #define DT_ENCODING 32 #define DT_PREINIT_ARRAY 32 #define DT_PREINIT_ARRAYSZ 33 #define DT_MAXPOSTAGS 34 #define DT_LOOS 0x6000000D #define DT_HIOS 0x6ffff000 #define DT_LOPROC 0x70000000 #define DT_HIPROC 0x7FFFFFFF // DT_FLAGS values #define DF_ORIGIN 0x1 #define DF_SYMBOLIC 0x2 #define DF_TEXTREL 0x4 #define DF_BIND_NOW 0x8 #define DF_STATIC_TLS 0x10 // ELF file header struct Elf32_Ehdr { unsigned char e_ident[EI_NIDENT]; Elf_Half e_type; Elf_Half e_machine; Elf_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf_Word e_flags; Elf_Half e_ehsize; Elf_Half e_phentsize; Elf_Half e_phnum; Elf_Half e_shentsize; Elf_Half e_shnum; Elf_Half e_shstrndx; }; struct Elf64_Ehdr { unsigned char e_ident[EI_NIDENT]; Elf_Half e_type; Elf_Half e_machine; Elf_Word e_version; Elf64_Addr e_entry; Elf64_Off e_phoff; Elf64_Off e_shoff; Elf_Word e_flags; Elf_Half e_ehsize; Elf_Half e_phentsize; Elf_Half e_phnum; Elf_Half e_shentsize; Elf_Half e_shnum; Elf_Half e_shstrndx; }; // Section header struct Elf32_Shdr { Elf_Word sh_name; Elf_Word sh_type; Elf_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf_Word sh_size; Elf_Word sh_link; Elf_Word sh_info; Elf_Word sh_addralign; Elf_Word sh_entsize; }; struct Elf64_Shdr { Elf_Word sh_name; Elf_Word sh_type; Elf_Xword sh_flags; Elf64_Addr sh_addr; Elf64_Off sh_offset; Elf_Xword sh_size; Elf_Word sh_link; Elf_Word sh_info; Elf_Xword sh_addralign; Elf_Xword sh_entsize; }; // Segment header struct Elf32_Phdr { Elf_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf_Word p_filesz; Elf_Word p_memsz; Elf_Word p_flags; Elf_Word p_align; }; struct Elf64_Phdr { Elf_Word p_type; Elf_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf_Xword p_filesz; Elf_Xword p_memsz; Elf_Xword p_align; }; // Symbol table entry struct Elf32_Sym { Elf_Word st_name; Elf32_Addr st_value; Elf_Word st_size; unsigned char st_info; unsigned char st_other; Elf_Half st_shndx; }; struct Elf64_Sym { Elf_Word st_name; unsigned char st_info; unsigned char st_other; Elf_Half st_shndx; Elf64_Addr st_value; Elf_Xword st_size; }; #define ELF_ST_BIND( i ) ( ( i ) >> 4 ) #define ELF_ST_TYPE( i ) ( (i)&0xf ) #define ELF_ST_INFO( b, t ) ( ( ( b ) << 4 ) + ( (t)&0xf ) ) #define ELF_ST_VISIBILITY( o ) ( (o)&0x3 ) // Relocation entries struct Elf32_Rel { Elf32_Addr r_offset; Elf_Word r_info; }; struct Elf32_Rela { Elf32_Addr r_offset; Elf_Word r_info; Elf_Sword r_addend; }; struct Elf64_Rel { Elf64_Addr r_offset; Elf_Xword r_info; }; struct Elf64_Rela { Elf64_Addr r_offset; Elf_Xword r_info; Elf_Sxword r_addend; }; #define ELF32_R_SYM( i ) ( ( i ) >> 8 ) #define ELF32_R_TYPE( i ) ( (unsigned char)( i ) ) #define ELF32_R_INFO( s, t ) ( ( ( s ) << 8 ) + (unsigned char)( t ) ) #define ELF64_R_SYM( i ) ( ( i ) >> 32 ) #define ELF64_R_TYPE( i ) ( (i)&0xffffffffL ) #define ELF64_R_INFO( s, t ) \ ( ( ( ( int64_t )( s ) ) << 32 ) + ( (t)&0xffffffffL ) ) // Dynamic structure struct Elf32_Dyn { Elf_Sword d_tag; union { Elf_Word d_val; Elf32_Addr d_ptr; } d_un; }; struct Elf64_Dyn { Elf_Sxword d_tag; union { Elf_Xword d_val; Elf64_Addr d_ptr; } d_un; }; } // namespace ELFIO #endif // ELFTYPES_H /*** End of inlined file: elf_types.hpp ***/ /*** Start of inlined file: elfio_version.hpp ***/ #define ELFIO_VERSION "3.8" /*** End of inlined file: elfio_version.hpp ***/ /*** Start of inlined file: elfio_utils.hpp ***/ #ifndef ELFIO_UTILS_HPP #define ELFIO_UTILS_HPP #define ELFIO_GET_ACCESS( TYPE, NAME, FIELD ) \ TYPE get_##NAME() const { return ( *convertor )( FIELD ); } #define ELFIO_SET_ACCESS( TYPE, NAME, FIELD ) \ void set_##NAME( TYPE value ) \ { \ FIELD = value; \ FIELD = ( *convertor )( FIELD ); \ } #define ELFIO_GET_SET_ACCESS( TYPE, NAME, FIELD ) \ TYPE get_##NAME() const { return ( *convertor )( FIELD ); } \ void set_##NAME( TYPE value ) \ { \ FIELD = value; \ FIELD = ( *convertor )( FIELD ); \ } #define ELFIO_GET_ACCESS_DECL( TYPE, NAME ) virtual TYPE get_##NAME() const = 0 #define ELFIO_SET_ACCESS_DECL( TYPE, NAME ) \ virtual void set_##NAME( TYPE value ) = 0 #define ELFIO_GET_SET_ACCESS_DECL( TYPE, NAME ) \ virtual TYPE get_##NAME() const = 0; \ virtual void set_##NAME( TYPE value ) = 0 namespace ELFIO { //------------------------------------------------------------------------------ class endianess_convertor { public: //------------------------------------------------------------------------------ endianess_convertor() { need_conversion = false; } //------------------------------------------------------------------------------ void setup( unsigned char elf_file_encoding ) { need_conversion = ( elf_file_encoding != get_host_encoding() ); } //------------------------------------------------------------------------------ uint64_t operator()( uint64_t value ) const { if ( !need_conversion ) { return value; } value = ( ( value & 0x00000000000000FFull ) << 56 ) | ( ( value & 0x000000000000FF00ull ) << 40 ) | ( ( value & 0x0000000000FF0000ull ) << 24 ) | ( ( value & 0x00000000FF000000ull ) << 8 ) | ( ( value & 0x000000FF00000000ull ) >> 8 ) | ( ( value & 0x0000FF0000000000ull ) >> 24 ) | ( ( value & 0x00FF000000000000ull ) >> 40 ) | ( ( value & 0xFF00000000000000ull ) >> 56 ); return value; } //------------------------------------------------------------------------------ int64_t operator()( int64_t value ) const { if ( !need_conversion ) { return value; } return ( int64_t )( *this )( (uint64_t)value ); } //------------------------------------------------------------------------------ uint32_t operator()( uint32_t value ) const { if ( !need_conversion ) { return value; } value = ( ( value & 0x000000FF ) << 24 ) | ( ( value & 0x0000FF00 ) << 8 ) | ( ( value & 0x00FF0000 ) >> 8 ) | ( ( value & 0xFF000000 ) >> 24 ); return value; } //------------------------------------------------------------------------------ int32_t operator()( int32_t value ) const { if ( !need_conversion ) { return value; } return ( int32_t )( *this )( (uint32_t)value ); } //------------------------------------------------------------------------------ uint16_t operator()( uint16_t value ) const { if ( !need_conversion ) { return value; } value = ( ( value & 0x00FF ) << 8 ) | ( ( value & 0xFF00 ) >> 8 ); return value; } //------------------------------------------------------------------------------ int16_t operator()( int16_t value ) const { if ( !need_conversion ) { return value; } return ( int16_t )( *this )( (uint16_t)value ); } //------------------------------------------------------------------------------ int8_t operator()( int8_t value ) const { return value; } //------------------------------------------------------------------------------ uint8_t operator()( uint8_t value ) const { return value; } //------------------------------------------------------------------------------ private: //------------------------------------------------------------------------------ unsigned char get_host_encoding() const { static const int tmp = 1; if ( 1 == *(const char*)&tmp ) { return ELFDATA2LSB; } else { return ELFDATA2MSB; } } //------------------------------------------------------------------------------ private: bool need_conversion; }; //------------------------------------------------------------------------------ inline uint32_t elf_hash( const unsigned char* name ) { uint32_t h = 0, g; while ( *name ) { h = ( h << 4 ) + *name++; g = h & 0xf0000000; if ( g != 0 ) h ^= g >> 24; h &= ~g; } return h; } } // namespace ELFIO #endif // ELFIO_UTILS_HPP /*** End of inlined file: elfio_utils.hpp ***/ /*** Start of inlined file: elfio_header.hpp ***/ #ifndef ELF_HEADER_HPP #define ELF_HEADER_HPP #include namespace ELFIO { class elf_header { public: virtual ~elf_header(){}; virtual bool load( std::istream& stream ) = 0; virtual bool save( std::ostream& stream ) const = 0; // ELF header functions ELFIO_GET_ACCESS_DECL( unsigned char, class ); ELFIO_GET_ACCESS_DECL( unsigned char, elf_version ); ELFIO_GET_ACCESS_DECL( unsigned char, encoding ); ELFIO_GET_ACCESS_DECL( Elf_Half, header_size ); ELFIO_GET_ACCESS_DECL( Elf_Half, section_entry_size ); ELFIO_GET_ACCESS_DECL( Elf_Half, segment_entry_size ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, version ); ELFIO_GET_SET_ACCESS_DECL( unsigned char, os_abi ); ELFIO_GET_SET_ACCESS_DECL( unsigned char, abi_version ); ELFIO_GET_SET_ACCESS_DECL( Elf_Half, type ); ELFIO_GET_SET_ACCESS_DECL( Elf_Half, machine ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, flags ); ELFIO_GET_SET_ACCESS_DECL( Elf64_Addr, entry ); ELFIO_GET_SET_ACCESS_DECL( Elf_Half, sections_num ); ELFIO_GET_SET_ACCESS_DECL( Elf64_Off, sections_offset ); ELFIO_GET_SET_ACCESS_DECL( Elf_Half, segments_num ); ELFIO_GET_SET_ACCESS_DECL( Elf64_Off, segments_offset ); ELFIO_GET_SET_ACCESS_DECL( Elf_Half, section_name_str_index ); }; template struct elf_header_impl_types; template <> struct elf_header_impl_types { typedef Elf32_Phdr Phdr_type; typedef Elf32_Shdr Shdr_type; static const unsigned char file_class = ELFCLASS32; }; template <> struct elf_header_impl_types { typedef Elf64_Phdr Phdr_type; typedef Elf64_Shdr Shdr_type; static const unsigned char file_class = ELFCLASS64; }; template class elf_header_impl : public elf_header { public: //------------------------------------------------------------------------------ elf_header_impl( endianess_convertor* convertor_, unsigned char encoding ) { convertor = convertor_; std::fill_n( reinterpret_cast( &header ), sizeof( header ), '\0' ); header.e_ident[EI_MAG0] = ELFMAG0; header.e_ident[EI_MAG1] = ELFMAG1; header.e_ident[EI_MAG2] = ELFMAG2; header.e_ident[EI_MAG3] = ELFMAG3; header.e_ident[EI_CLASS] = elf_header_impl_types::file_class; header.e_ident[EI_DATA] = encoding; header.e_ident[EI_VERSION] = EV_CURRENT; header.e_version = ( *convertor )( (Elf_Word)EV_CURRENT ); header.e_ehsize = ( sizeof( header ) ); header.e_ehsize = ( *convertor )( header.e_ehsize ); header.e_shstrndx = ( *convertor )( (Elf_Half)1 ); header.e_phentsize = sizeof( typename elf_header_impl_types::Phdr_type ); header.e_shentsize = sizeof( typename elf_header_impl_types::Shdr_type ); header.e_phentsize = ( *convertor )( header.e_phentsize ); header.e_shentsize = ( *convertor )( header.e_shentsize ); } //------------------------------------------------------------------------------ bool load( std::istream& stream ) { stream.seekg( 0 ); stream.read( reinterpret_cast( &header ), sizeof( header ) ); return ( stream.gcount() == sizeof( header ) ); } //------------------------------------------------------------------------------ bool save( std::ostream& stream ) const { stream.seekp( 0 ); stream.write( reinterpret_cast( &header ), sizeof( header ) ); return stream.good(); } //------------------------------------------------------------------------------ // ELF header functions ELFIO_GET_ACCESS( unsigned char, class, header.e_ident[EI_CLASS] ); ELFIO_GET_ACCESS( unsigned char, elf_version, header.e_ident[EI_VERSION] ); ELFIO_GET_ACCESS( unsigned char, encoding, header.e_ident[EI_DATA] ); ELFIO_GET_ACCESS( Elf_Half, header_size, header.e_ehsize ); ELFIO_GET_ACCESS( Elf_Half, section_entry_size, header.e_shentsize ); ELFIO_GET_ACCESS( Elf_Half, segment_entry_size, header.e_phentsize ); ELFIO_GET_SET_ACCESS( Elf_Word, version, header.e_version ); ELFIO_GET_SET_ACCESS( unsigned char, os_abi, header.e_ident[EI_OSABI] ); ELFIO_GET_SET_ACCESS( unsigned char, abi_version, header.e_ident[EI_ABIVERSION] ); ELFIO_GET_SET_ACCESS( Elf_Half, type, header.e_type ); ELFIO_GET_SET_ACCESS( Elf_Half, machine, header.e_machine ); ELFIO_GET_SET_ACCESS( Elf_Word, flags, header.e_flags ); ELFIO_GET_SET_ACCESS( Elf_Half, section_name_str_index, header.e_shstrndx ); ELFIO_GET_SET_ACCESS( Elf64_Addr, entry, header.e_entry ); ELFIO_GET_SET_ACCESS( Elf_Half, sections_num, header.e_shnum ); ELFIO_GET_SET_ACCESS( Elf64_Off, sections_offset, header.e_shoff ); ELFIO_GET_SET_ACCESS( Elf_Half, segments_num, header.e_phnum ); ELFIO_GET_SET_ACCESS( Elf64_Off, segments_offset, header.e_phoff ); private: T header; endianess_convertor* convertor; }; } // namespace ELFIO #endif // ELF_HEADER_HPP /*** End of inlined file: elfio_header.hpp ***/ /*** Start of inlined file: elfio_section.hpp ***/ #ifndef ELFIO_SECTION_HPP #define ELFIO_SECTION_HPP #include #include #include namespace ELFIO { class section { friend class elfio; public: virtual ~section(){}; ELFIO_GET_ACCESS_DECL( Elf_Half, index ); ELFIO_GET_SET_ACCESS_DECL( std::string, name ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, type ); ELFIO_GET_SET_ACCESS_DECL( Elf_Xword, flags ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, info ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, link ); ELFIO_GET_SET_ACCESS_DECL( Elf_Xword, addr_align ); ELFIO_GET_SET_ACCESS_DECL( Elf_Xword, entry_size ); ELFIO_GET_SET_ACCESS_DECL( Elf64_Addr, address ); ELFIO_GET_SET_ACCESS_DECL( Elf_Xword, size ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, name_string_offset ); ELFIO_GET_ACCESS_DECL( Elf64_Off, offset ); virtual const char* get_data() const = 0; virtual void set_data( const char* pData, Elf_Word size ) = 0; virtual void set_data( const std::string& data ) = 0; virtual void append_data( const char* pData, Elf_Word size ) = 0; virtual void append_data( const std::string& data ) = 0; virtual size_t get_stream_size() const = 0; virtual void set_stream_size( size_t value ) = 0; protected: ELFIO_SET_ACCESS_DECL( Elf64_Off, offset ); ELFIO_SET_ACCESS_DECL( Elf_Half, index ); virtual void load( std::istream& stream, std::streampos header_offset ) = 0; virtual void save( std::ostream& stream, std::streampos header_offset, std::streampos data_offset ) = 0; virtual bool is_address_initialized() const = 0; }; template class section_impl : public section { public: //------------------------------------------------------------------------------ section_impl( const endianess_convertor* convertor_ ) : convertor( convertor_ ) { std::fill_n( reinterpret_cast( &header ), sizeof( header ), '\0' ); is_address_set = false; data = 0; data_size = 0; index = 0; stream_size = 0; } //------------------------------------------------------------------------------ ~section_impl() { delete[] data; } //------------------------------------------------------------------------------ // Section info functions ELFIO_GET_SET_ACCESS( Elf_Word, type, header.sh_type ); ELFIO_GET_SET_ACCESS( Elf_Xword, flags, header.sh_flags ); ELFIO_GET_SET_ACCESS( Elf_Xword, size, header.sh_size ); ELFIO_GET_SET_ACCESS( Elf_Word, link, header.sh_link ); ELFIO_GET_SET_ACCESS( Elf_Word, info, header.sh_info ); ELFIO_GET_SET_ACCESS( Elf_Xword, addr_align, header.sh_addralign ); ELFIO_GET_SET_ACCESS( Elf_Xword, entry_size, header.sh_entsize ); ELFIO_GET_SET_ACCESS( Elf_Word, name_string_offset, header.sh_name ); ELFIO_GET_ACCESS( Elf64_Addr, address, header.sh_addr ); //------------------------------------------------------------------------------ Elf_Half get_index() const { return index; } //------------------------------------------------------------------------------ std::string get_name() const { return name; } //------------------------------------------------------------------------------ void set_name( std::string name_ ) { name = name_; } //------------------------------------------------------------------------------ void set_address( Elf64_Addr value ) { header.sh_addr = value; header.sh_addr = ( *convertor )( header.sh_addr ); is_address_set = true; } //------------------------------------------------------------------------------ bool is_address_initialized() const { return is_address_set; } //------------------------------------------------------------------------------ const char* get_data() const { return data; } //------------------------------------------------------------------------------ void set_data( const char* raw_data, Elf_Word size ) { if ( get_type() != SHT_NOBITS ) { delete[] data; data = new ( std::nothrow ) char[size]; if ( 0 != data && 0 != raw_data ) { data_size = size; std::copy( raw_data, raw_data + size, data ); } else { data_size = 0; } } set_size( data_size ); } //------------------------------------------------------------------------------ void set_data( const std::string& str_data ) { return set_data( str_data.c_str(), (Elf_Word)str_data.size() ); } //------------------------------------------------------------------------------ void append_data( const char* raw_data, Elf_Word size ) { if ( get_type() != SHT_NOBITS ) { if ( get_size() + size < data_size ) { std::copy( raw_data, raw_data + size, data + get_size() ); } else { data_size = 2 * ( data_size + size ); char* new_data = new ( std::nothrow ) char[data_size]; if ( 0 != new_data ) { std::copy( data, data + get_size(), new_data ); std::copy( raw_data, raw_data + size, new_data + get_size() ); delete[] data; data = new_data; } else { size = 0; } } set_size( get_size() + size ); } } //------------------------------------------------------------------------------ void append_data( const std::string& str_data ) { return append_data( str_data.c_str(), (Elf_Word)str_data.size() ); } //------------------------------------------------------------------------------ protected: //------------------------------------------------------------------------------ ELFIO_GET_SET_ACCESS( Elf64_Off, offset, header.sh_offset ); //------------------------------------------------------------------------------ void set_index( Elf_Half value ) { index = value; } //------------------------------------------------------------------------------ void load( std::istream& stream, std::streampos header_offset ) { std::fill_n( reinterpret_cast( &header ), sizeof( header ), '\0' ); stream.seekg( 0, stream.end ); set_stream_size( stream.tellg() ); stream.seekg( header_offset ); stream.read( reinterpret_cast( &header ), sizeof( header ) ); Elf_Xword size = get_size(); if ( 0 == data && SHT_NULL != get_type() && SHT_NOBITS != get_type() && size < get_stream_size() ) { data = new ( std::nothrow ) char[size + 1]; if ( ( 0 != size ) && ( 0 != data ) ) { stream.seekg( ( *convertor )( header.sh_offset ) ); stream.read( data, size ); data[size] = 0; // Ensure data is ended with 0 to avoid oob read data_size = size; } else { data_size = 0; } } } //------------------------------------------------------------------------------ void save( std::ostream& stream, std::streampos header_offset, std::streampos data_offset ) { if ( 0 != get_index() ) { header.sh_offset = data_offset; header.sh_offset = ( *convertor )( header.sh_offset ); } save_header( stream, header_offset ); if ( get_type() != SHT_NOBITS && get_type() != SHT_NULL && get_size() != 0 && data != 0 ) { save_data( stream, data_offset ); } } //------------------------------------------------------------------------------ private: //------------------------------------------------------------------------------ void save_header( std::ostream& stream, std::streampos header_offset ) const { stream.seekp( header_offset ); stream.write( reinterpret_cast( &header ), sizeof( header ) ); } //------------------------------------------------------------------------------ void save_data( std::ostream& stream, std::streampos data_offset ) const { stream.seekp( data_offset ); stream.write( get_data(), get_size() ); } //------------------------------------------------------------------------------ size_t get_stream_size() const { return stream_size; } //------------------------------------------------------------------------------ void set_stream_size( size_t value ) { stream_size = value; } //------------------------------------------------------------------------------ private: T header; Elf_Half index; std::string name; char* data; Elf_Word data_size; const endianess_convertor* convertor; bool is_address_set; size_t stream_size; }; } // namespace ELFIO #endif // ELFIO_SECTION_HPP /*** End of inlined file: elfio_section.hpp ***/ /*** Start of inlined file: elfio_segment.hpp ***/ #ifndef ELFIO_SEGMENT_HPP #define ELFIO_SEGMENT_HPP #include #include #include namespace ELFIO { class segment { friend class elfio; public: virtual ~segment(){}; ELFIO_GET_ACCESS_DECL( Elf_Half, index ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, type ); ELFIO_GET_SET_ACCESS_DECL( Elf_Word, flags ); ELFIO_GET_SET_ACCESS_DECL( Elf_Xword, align ); ELFIO_GET_SET_ACCESS_DECL( Elf64_Addr, virtual_address ); ELFIO_GET_SET_ACCESS_DECL( Elf64_Addr, physical_address ); ELFIO_GET_SET_ACCESS_DECL( Elf_Xword, file_size ); ELFIO_GET_SET_ACCESS_DECL( Elf_Xword, memory_size ); ELFIO_GET_ACCESS_DECL( Elf64_Off, offset ); virtual const char* get_data() const = 0; virtual Elf_Half add_section_index( Elf_Half index, Elf_Xword addr_align ) = 0; virtual Elf_Half get_sections_num() const = 0; virtual Elf_Half get_section_index_at( Elf_Half num ) const = 0; virtual bool is_offset_initialized() const = 0; protected: ELFIO_SET_ACCESS_DECL( Elf64_Off, offset ); ELFIO_SET_ACCESS_DECL( Elf_Half, index ); virtual const std::vector& get_sections() const = 0; virtual void load( std::istream& stream, std::streampos header_offset ) = 0; virtual void save( std::ostream& stream, std::streampos header_offset, std::streampos data_offset ) = 0; }; //------------------------------------------------------------------------------ template class segment_impl : public segment { public: //------------------------------------------------------------------------------ segment_impl( endianess_convertor* convertor_ ) : stream_size( 0 ), index( 0 ), data( 0 ), convertor( convertor_ ) { is_offset_set = false; std::fill_n( reinterpret_cast( &ph ), sizeof( ph ), '\0' ); } //------------------------------------------------------------------------------ virtual ~segment_impl() { delete[] data; } //------------------------------------------------------------------------------ // Section info functions ELFIO_GET_SET_ACCESS( Elf_Word, type, ph.p_type ); ELFIO_GET_SET_ACCESS( Elf_Word, flags, ph.p_flags ); ELFIO_GET_SET_ACCESS( Elf_Xword, align, ph.p_align ); ELFIO_GET_SET_ACCESS( Elf64_Addr, virtual_address, ph.p_vaddr ); ELFIO_GET_SET_ACCESS( Elf64_Addr, physical_address, ph.p_paddr ); ELFIO_GET_SET_ACCESS( Elf_Xword, file_size, ph.p_filesz ); ELFIO_GET_SET_ACCESS( Elf_Xword, memory_size, ph.p_memsz ); ELFIO_GET_ACCESS( Elf64_Off, offset, ph.p_offset ); size_t stream_size; //------------------------------------------------------------------------------ size_t get_stream_size() const { return stream_size; } //------------------------------------------------------------------------------ void set_stream_size( size_t value ) { stream_size = value; } //------------------------------------------------------------------------------ Elf_Half get_index() const { return index; } //------------------------------------------------------------------------------ const char* get_data() const { return data; } //------------------------------------------------------------------------------ Elf_Half add_section_index( Elf_Half sec_index, Elf_Xword addr_align ) { sections.push_back( sec_index ); if ( addr_align > get_align() ) { set_align( addr_align ); } return (Elf_Half)sections.size(); } //------------------------------------------------------------------------------ Elf_Half get_sections_num() const { return (Elf_Half)sections.size(); } //------------------------------------------------------------------------------ Elf_Half get_section_index_at( Elf_Half num ) const { if ( num < sections.size() ) { return sections[num]; } return Elf_Half( -1 ); } //------------------------------------------------------------------------------ protected: //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void set_offset( Elf64_Off value ) { ph.p_offset = value; ph.p_offset = ( *convertor )( ph.p_offset ); is_offset_set = true; } //------------------------------------------------------------------------------ bool is_offset_initialized() const { return is_offset_set; } //------------------------------------------------------------------------------ const std::vector& get_sections() const { return sections; } //------------------------------------------------------------------------------ void set_index( Elf_Half value ) { index = value; } //------------------------------------------------------------------------------ void load( std::istream& stream, std::streampos header_offset ) { stream.seekg( 0, stream.end ); set_stream_size( stream.tellg() ); stream.seekg( header_offset ); stream.read( reinterpret_cast( &ph ), sizeof( ph ) ); is_offset_set = true; if ( PT_NULL != get_type() && 0 != get_file_size() ) { stream.seekg( ( *convertor )( ph.p_offset ) ); Elf_Xword size = get_file_size(); if ( size > get_stream_size() ) { data = 0; } else { data = new (std::nothrow) char[size + 1]; if ( 0 != data ) { stream.read( data, size ); data[size] = 0; } } } } //------------------------------------------------------------------------------ void save( std::ostream& stream, std::streampos header_offset, std::streampos data_offset ) { ph.p_offset = data_offset; ph.p_offset = ( *convertor )( ph.p_offset ); stream.seekp( header_offset ); stream.write( reinterpret_cast( &ph ), sizeof( ph ) ); } //------------------------------------------------------------------------------ private: T ph; Elf_Half index; char* data; std::vector sections; endianess_convertor* convertor; bool is_offset_set; }; } // namespace ELFIO #endif // ELFIO_SEGMENT_HPP /*** End of inlined file: elfio_segment.hpp ***/ /*** Start of inlined file: elfio_strings.hpp ***/ #ifndef ELFIO_STRINGS_HPP #define ELFIO_STRINGS_HPP #include #include #include namespace ELFIO { //------------------------------------------------------------------------------ template class string_section_accessor_template { public: //------------------------------------------------------------------------------ string_section_accessor_template( S* section_ ) : string_section( section_ ) { } //------------------------------------------------------------------------------ const char* get_string( Elf_Word index ) const { if ( string_section ) { if ( index < string_section->get_size() ) { const char* data = string_section->get_data(); if ( 0 != data ) { return data + index; } } } return 0; } //------------------------------------------------------------------------------ Elf_Word add_string( const char* str ) { Elf_Word current_position = 0; if ( string_section ) { // Strings are addeded to the end of the current section data current_position = (Elf_Word)string_section->get_size(); if ( current_position == 0 ) { char empty_string = '\0'; string_section->append_data( &empty_string, 1 ); current_position++; } string_section->append_data( str, (Elf_Word)std::strlen( str ) + 1 ); } return current_position; } //------------------------------------------------------------------------------ Elf_Word add_string( const std::string& str ) { return add_string( str.c_str() ); } //------------------------------------------------------------------------------ private: S* string_section; }; using string_section_accessor = string_section_accessor_template
; using const_string_section_accessor = string_section_accessor_template; } // namespace ELFIO #endif // ELFIO_STRINGS_HPP /*** End of inlined file: elfio_strings.hpp ***/ #define ELFIO_HEADER_ACCESS_GET( TYPE, FNAME ) \ TYPE get_##FNAME() const { return header ? ( header->get_##FNAME() ) : 0; } #define ELFIO_HEADER_ACCESS_GET_SET( TYPE, FNAME ) \ TYPE get_##FNAME() const \ { \ return header ? ( header->get_##FNAME() ) : 0; \ } \ void set_##FNAME( TYPE val ) \ { \ if ( header ) { \ header->set_##FNAME( val ); \ } \ } namespace ELFIO { //------------------------------------------------------------------------------ class elfio { public: //------------------------------------------------------------------------------ elfio() : sections( this ), segments( this ) { header = 0; current_file_pos = 0; create( ELFCLASS32, ELFDATA2LSB ); } //------------------------------------------------------------------------------ ~elfio() { clean(); } //------------------------------------------------------------------------------ void create( unsigned char file_class, unsigned char encoding ) { clean(); convertor.setup( encoding ); header = create_header( file_class, encoding ); create_mandatory_sections(); } //------------------------------------------------------------------------------ bool load( const std::string& file_name ) { std::ifstream stream; stream.open( file_name.c_str(), std::ios::in | std::ios::binary ); if ( !stream ) { return false; } return load( stream ); } //------------------------------------------------------------------------------ bool load( std::istream& stream ) { clean(); unsigned char e_ident[EI_NIDENT]; // Read ELF file signature stream.read( reinterpret_cast( &e_ident ), sizeof( e_ident ) ); // Is it ELF file? if ( stream.gcount() != sizeof( e_ident ) || e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 || e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3 ) { return false; } if ( ( e_ident[EI_CLASS] != ELFCLASS64 ) && ( e_ident[EI_CLASS] != ELFCLASS32 ) ) { return false; } convertor.setup( e_ident[EI_DATA] ); header = create_header( e_ident[EI_CLASS], e_ident[EI_DATA] ); if ( 0 == header ) { return false; } if ( !header->load( stream ) ) { return false; } load_sections( stream ); bool is_still_good = load_segments( stream ); return is_still_good; } //------------------------------------------------------------------------------ bool save( const std::string& file_name ) { std::ofstream stream; stream.open( file_name.c_str(), std::ios::out | std::ios::binary ); if ( !stream ) { return false; } return save( stream ); } //------------------------------------------------------------------------------ bool save( std::ostream& stream ) { if ( !stream || !header ) { return false; } bool is_still_good = true; // Define layout specific header fields // The position of the segment table is fixed after the header. // The position of the section table is variable and needs to be fixed // before saving. header->set_segments_num( segments.size() ); header->set_segments_offset( segments.size() ? header->get_header_size() : 0 ); header->set_sections_num( sections.size() ); header->set_sections_offset( 0 ); // Layout the first section right after the segment table current_file_pos = header->get_header_size() + header->get_segment_entry_size() * (Elf_Xword)header->get_segments_num(); calc_segment_alignment(); is_still_good = layout_segments_and_their_sections(); is_still_good = is_still_good && layout_sections_without_segments(); is_still_good = is_still_good && layout_section_table(); is_still_good = is_still_good && save_header( stream ); is_still_good = is_still_good && save_sections( stream ); is_still_good = is_still_good && save_segments( stream ); return is_still_good; } //------------------------------------------------------------------------------ // ELF header access functions ELFIO_HEADER_ACCESS_GET( unsigned char, class ); ELFIO_HEADER_ACCESS_GET( unsigned char, elf_version ); ELFIO_HEADER_ACCESS_GET( unsigned char, encoding ); ELFIO_HEADER_ACCESS_GET( Elf_Word, version ); ELFIO_HEADER_ACCESS_GET( Elf_Half, header_size ); ELFIO_HEADER_ACCESS_GET( Elf_Half, section_entry_size ); ELFIO_HEADER_ACCESS_GET( Elf_Half, segment_entry_size ); ELFIO_HEADER_ACCESS_GET_SET( unsigned char, os_abi ); ELFIO_HEADER_ACCESS_GET_SET( unsigned char, abi_version ); ELFIO_HEADER_ACCESS_GET_SET( Elf_Half, type ); ELFIO_HEADER_ACCESS_GET_SET( Elf_Half, machine ); ELFIO_HEADER_ACCESS_GET_SET( Elf_Word, flags ); ELFIO_HEADER_ACCESS_GET_SET( Elf64_Addr, entry ); ELFIO_HEADER_ACCESS_GET_SET( Elf64_Off, sections_offset ); ELFIO_HEADER_ACCESS_GET_SET( Elf64_Off, segments_offset ); ELFIO_HEADER_ACCESS_GET_SET( Elf_Half, section_name_str_index ); //------------------------------------------------------------------------------ const endianess_convertor& get_convertor() const { return convertor; } //------------------------------------------------------------------------------ Elf_Xword get_default_entry_size( Elf_Word section_type ) const { switch ( section_type ) { case SHT_RELA: if ( header->get_class() == ELFCLASS64 ) { return sizeof( Elf64_Rela ); } else { return sizeof( Elf32_Rela ); } case SHT_REL: if ( header->get_class() == ELFCLASS64 ) { return sizeof( Elf64_Rel ); } else { return sizeof( Elf32_Rel ); } case SHT_SYMTAB: if ( header->get_class() == ELFCLASS64 ) { return sizeof( Elf64_Sym ); } else { return sizeof( Elf32_Sym ); } case SHT_DYNAMIC: if ( header->get_class() == ELFCLASS64 ) { return sizeof( Elf64_Dyn ); } else { return sizeof( Elf32_Dyn ); } default: return 0; } } //------------------------------------------------------------------------------ private: bool is_offset_in_section( Elf64_Off offset, const section* sec ) const { return ( offset >= sec->get_offset() ) && ( offset < ( sec->get_offset() + sec->get_size() ) ); } //------------------------------------------------------------------------------ public: //! returns an empty string if no problems are detected, //! or a string containing an error message if problems are found std::string validate() const { // check for overlapping sections in the file for ( int i = 0; i < sections.size(); ++i ) { for ( int j = i + 1; j < sections.size(); ++j ) { const section* a = sections[i]; const section* b = sections[j]; if ( !( a->get_type() & SHT_NOBITS ) && !( b->get_type() & SHT_NOBITS ) && ( a->get_size() > 0 ) && ( b->get_size() > 0 ) && ( a->get_offset() > 0 ) && ( b->get_offset() > 0 ) ) { if ( is_offset_in_section( a->get_offset(), b ) || is_offset_in_section( a->get_offset() + a->get_size() - 1, b ) || is_offset_in_section( b->get_offset(), a ) || is_offset_in_section( b->get_offset() + b->get_size() - 1, a ) ) { return "Sections " + a->get_name() + " and " + b->get_name() + " overlap in file"; } } } } // more checks to be added here... return ""; } //------------------------------------------------------------------------------ private: //------------------------------------------------------------------------------ void clean() { delete header; header = 0; std::vector::const_iterator it; for ( it = sections_.begin(); it != sections_.end(); ++it ) { delete *it; } sections_.clear(); std::vector::const_iterator it1; for ( it1 = segments_.begin(); it1 != segments_.end(); ++it1 ) { delete *it1; } segments_.clear(); } //------------------------------------------------------------------------------ elf_header* create_header( unsigned char file_class, unsigned char encoding ) { elf_header* new_header = 0; if ( file_class == ELFCLASS64 ) { new_header = new elf_header_impl( &convertor, encoding ); } else if ( file_class == ELFCLASS32 ) { new_header = new elf_header_impl( &convertor, encoding ); } else { return 0; } return new_header; } //------------------------------------------------------------------------------ section* create_section() { section* new_section; unsigned char file_class = get_class(); if ( file_class == ELFCLASS64 ) { new_section = new section_impl( &convertor ); } else if ( file_class == ELFCLASS32 ) { new_section = new section_impl( &convertor ); } else { return 0; } new_section->set_index( (Elf_Half)sections_.size() ); sections_.push_back( new_section ); return new_section; } //------------------------------------------------------------------------------ segment* create_segment() { segment* new_segment; unsigned char file_class = header->get_class(); if ( file_class == ELFCLASS64 ) { new_segment = new segment_impl( &convertor ); } else if ( file_class == ELFCLASS32 ) { new_segment = new segment_impl( &convertor ); } else { return 0; } new_segment->set_index( (Elf_Half)segments_.size() ); segments_.push_back( new_segment ); return new_segment; } //------------------------------------------------------------------------------ void create_mandatory_sections() { // Create null section without calling to 'add_section' as no string // section containing section names exists yet section* sec0 = create_section(); sec0->set_index( 0 ); sec0->set_name( "" ); sec0->set_name_string_offset( 0 ); set_section_name_str_index( 1 ); section* shstrtab = sections.add( ".shstrtab" ); shstrtab->set_type( SHT_STRTAB ); shstrtab->set_addr_align( 1 ); } //------------------------------------------------------------------------------ Elf_Half load_sections( std::istream& stream ) { Elf_Half entry_size = header->get_section_entry_size(); Elf_Half num = header->get_sections_num(); Elf64_Off offset = header->get_sections_offset(); for ( Elf_Half i = 0; i < num; ++i ) { section* sec = create_section(); sec->load( stream, (std::streamoff)offset + (std::streampos)i * entry_size ); sec->set_index( i ); // To mark that the section is not permitted to reassign address // during layout calculation sec->set_address( sec->get_address() ); } Elf_Half shstrndx = get_section_name_str_index(); if ( SHN_UNDEF != shstrndx ) { string_section_accessor str_reader( sections[shstrndx] ); for ( Elf_Half i = 0; i < num; ++i ) { Elf_Word section_offset = sections[i]->get_name_string_offset(); const char* p = str_reader.get_string( section_offset ); if ( p != 0 ) { sections[i]->set_name( p ); } } } return num; } //------------------------------------------------------------------------------ //! Checks whether the addresses of the section entirely fall within the given segment. //! It doesn't matter if the addresses are memory addresses, or file offsets, //! they just need to be in the same address space bool is_sect_in_seg( Elf64_Off sect_begin, Elf_Xword sect_size, Elf64_Off seg_begin, Elf64_Off seg_end ) { return ( seg_begin <= sect_begin ) && ( sect_begin + sect_size <= seg_end ) && ( sect_begin < seg_end ); // this is important criteria when sect_size == 0 // Example: seg_begin=10, seg_end=12 (-> covering the bytes 10 and 11) // sect_begin=12, sect_size=0 -> shall return false! } //------------------------------------------------------------------------------ bool load_segments( std::istream& stream ) { Elf_Half entry_size = header->get_segment_entry_size(); Elf_Half num = header->get_segments_num(); Elf64_Off offset = header->get_segments_offset(); for ( Elf_Half i = 0; i < num; ++i ) { segment* seg; unsigned char file_class = header->get_class(); if ( file_class == ELFCLASS64 ) { seg = new segment_impl( &convertor ); } else if ( file_class == ELFCLASS32 ) { seg = new segment_impl( &convertor ); } else { return false; } seg->load( stream, (std::streamoff)offset + (std::streampos)i * entry_size ); seg->set_index( i ); // Add sections to the segments (similar to readelfs algorithm) Elf64_Off segBaseOffset = seg->get_offset(); Elf64_Off segEndOffset = segBaseOffset + seg->get_file_size(); Elf64_Off segVBaseAddr = seg->get_virtual_address(); Elf64_Off segVEndAddr = segVBaseAddr + seg->get_memory_size(); for ( Elf_Half j = 0; j < sections.size(); ++j ) { const section* psec = sections[j]; // SHF_ALLOC sections are matched based on the virtual address // otherwise the file offset is matched if ( ( psec->get_flags() & SHF_ALLOC ) ? is_sect_in_seg( psec->get_address(), psec->get_size(), segVBaseAddr, segVEndAddr ) : is_sect_in_seg( psec->get_offset(), psec->get_size(), segBaseOffset, segEndOffset ) ) { // Alignment of segment shall not be updated, to preserve original value // It will be re-calculated on saving. seg->add_section_index( psec->get_index(), 0 ); } } // Add section into the segments' container segments_.push_back( seg ); } return true; } //------------------------------------------------------------------------------ bool save_header( std::ostream& stream ) { return header->save( stream ); } //------------------------------------------------------------------------------ bool save_sections( std::ostream& stream ) { for ( unsigned int i = 0; i < sections_.size(); ++i ) { section* sec = sections_.at( i ); std::streampos headerPosition = (std::streamoff)header->get_sections_offset() + (std::streampos)header->get_section_entry_size() * sec->get_index(); sec->save( stream, headerPosition, sec->get_offset() ); } return true; } //------------------------------------------------------------------------------ bool save_segments( std::ostream& stream ) { for ( unsigned int i = 0; i < segments_.size(); ++i ) { segment* seg = segments_.at( i ); std::streampos headerPosition = header->get_segments_offset() + (std::streampos)header->get_segment_entry_size() * seg->get_index(); seg->save( stream, headerPosition, seg->get_offset() ); } return true; } //------------------------------------------------------------------------------ bool is_section_without_segment( unsigned int section_index ) { bool found = false; for ( unsigned int j = 0; !found && ( j < segments.size() ); ++j ) { for ( unsigned int k = 0; !found && ( k < segments[j]->get_sections_num() ); ++k ) { found = segments[j]->get_section_index_at( k ) == section_index; } } return !found; } //------------------------------------------------------------------------------ bool is_subsequence_of( segment* seg1, segment* seg2 ) { // Return 'true' if sections of seg1 are a subset of sections in seg2 const std::vector& sections1 = seg1->get_sections(); const std::vector& sections2 = seg2->get_sections(); bool found = false; if ( sections1.size() < sections2.size() ) { found = std::includes( sections2.begin(), sections2.end(), sections1.begin(), sections1.end() ); } return found; } //------------------------------------------------------------------------------ std::vector get_ordered_segments() { std::vector res; std::deque worklist; res.reserve( segments.size() ); std::copy( segments_.begin(), segments_.end(), std::back_inserter( worklist ) ); // Bring the segments which start at address 0 to the front size_t nextSlot = 0; for ( size_t i = 0; i < worklist.size(); ++i ) { if ( i != nextSlot && worklist[i]->is_offset_initialized() && worklist[i]->get_offset() == 0 ) { if ( worklist[nextSlot]->get_offset() == 0 ) { ++nextSlot; } std::swap( worklist[i], worklist[nextSlot] ); ++nextSlot; } } while ( !worklist.empty() ) { segment* seg = worklist.front(); worklist.pop_front(); size_t i = 0; for ( ; i < worklist.size(); ++i ) { if ( is_subsequence_of( seg, worklist[i] ) ) { break; } } if ( i < worklist.size() ) worklist.push_back( seg ); else res.push_back( seg ); } return res; } //------------------------------------------------------------------------------ bool layout_sections_without_segments() { for ( unsigned int i = 0; i < sections_.size(); ++i ) { if ( is_section_without_segment( i ) ) { section* sec = sections_[i]; Elf_Xword section_align = sec->get_addr_align(); if ( section_align > 1 && current_file_pos % section_align != 0 ) { current_file_pos += section_align - current_file_pos % section_align; } if ( 0 != sec->get_index() ) sec->set_offset( current_file_pos ); if ( SHT_NOBITS != sec->get_type() && SHT_NULL != sec->get_type() ) { current_file_pos += sec->get_size(); } } } return true; } //------------------------------------------------------------------------------ void calc_segment_alignment() { for ( std::vector::iterator s = segments_.begin(); s != segments_.end(); ++s ) { segment* seg = *s; for ( int i = 0; i < seg->get_sections_num(); ++i ) { section* sect = sections_[seg->get_section_index_at( i )]; if ( sect->get_addr_align() > seg->get_align() ) { seg->set_align( sect->get_addr_align() ); } } } } //------------------------------------------------------------------------------ bool layout_segments_and_their_sections() { std::vector worklist; std::vector section_generated( sections.size(), false ); // Get segments in a order in where segments which contain a // sub sequence of other segments are located at the end worklist = get_ordered_segments(); for ( unsigned int i = 0; i < worklist.size(); ++i ) { Elf_Xword segment_memory = 0; Elf_Xword segment_filesize = 0; Elf_Xword seg_start_pos = current_file_pos; segment* seg = worklist[i]; // Special case: PHDR segment // This segment contains the program headers but no sections if ( seg->get_type() == PT_PHDR && seg->get_sections_num() == 0 ) { seg_start_pos = header->get_segments_offset(); segment_memory = segment_filesize = header->get_segment_entry_size() * (Elf_Xword)header->get_segments_num(); } // Special case: else if ( seg->is_offset_initialized() && seg->get_offset() == 0 ) { seg_start_pos = 0; if ( seg->get_sections_num() ) { segment_memory = segment_filesize = current_file_pos; } } // New segments with not generated sections // have to be aligned else if ( seg->get_sections_num() && !section_generated[seg->get_section_index_at( 0 )] ) { Elf_Xword align = seg->get_align() > 0 ? seg->get_align() : 1; Elf64_Off cur_page_alignment = current_file_pos % align; Elf64_Off req_page_alignment = seg->get_virtual_address() % align; Elf64_Off error = req_page_alignment - cur_page_alignment; current_file_pos += ( seg->get_align() + error ) % align; seg_start_pos = current_file_pos; } else if ( seg->get_sections_num() ) { seg_start_pos = sections[seg->get_section_index_at( 0 )]->get_offset(); } // Write segment's data for ( unsigned int j = 0; j < seg->get_sections_num(); ++j ) { Elf_Half index = seg->get_section_index_at( j ); section* sec = sections[index]; // The NULL section is always generated if ( SHT_NULL == sec->get_type() ) { section_generated[index] = true; continue; } Elf_Xword secAlign = 0; // Fix up the alignment if ( !section_generated[index] && sec->is_address_initialized() && SHT_NOBITS != sec->get_type() && SHT_NULL != sec->get_type() && 0 != sec->get_size() ) { // Align the sections based on the virtual addresses // when possible (this is what matters for execution) Elf64_Off req_offset = sec->get_address() - seg->get_virtual_address(); Elf64_Off cur_offset = current_file_pos - seg_start_pos; if ( req_offset < cur_offset ) { // something has gone awfully wrong, abort! // secAlign would turn out negative, seeking backwards and overwriting previous data return false; } secAlign = req_offset - cur_offset; } else if ( !section_generated[index] && !sec->is_address_initialized() ) { // If no address has been specified then only the section // alignment constraint has to be matched Elf_Xword align = sec->get_addr_align(); if ( align == 0 ) { align = 1; } Elf64_Off error = current_file_pos % align; secAlign = ( align - error ) % align; } else if ( section_generated[index] ) { // Alignment for already generated sections secAlign = sec->get_offset() - seg_start_pos - segment_filesize; } // Determine the segment file and memory sizes // Special case .tbss section (NOBITS) in non TLS segment if ( ( sec->get_flags() & SHF_ALLOC ) && !( ( sec->get_flags() & SHF_TLS ) && ( seg->get_type() != PT_TLS ) && ( SHT_NOBITS == sec->get_type() ) ) ) segment_memory += sec->get_size() + secAlign; if ( SHT_NOBITS != sec->get_type() ) segment_filesize += sec->get_size() + secAlign; // Nothing to be done when generating nested segments if ( section_generated[index] ) { continue; } current_file_pos += secAlign; // Set the section addresses when missing if ( !sec->is_address_initialized() ) sec->set_address( seg->get_virtual_address() + current_file_pos - seg_start_pos ); if ( 0 != sec->get_index() ) sec->set_offset( current_file_pos ); if ( SHT_NOBITS != sec->get_type() ) current_file_pos += sec->get_size(); section_generated[index] = true; } seg->set_file_size( segment_filesize ); // If we already have a memory size from loading an elf file (value > 0), // it must not shrink! // Memory size may be bigger than file size and it is the loader's job to do something // with the surplus bytes in memory, like initializing them with a defined value. if ( seg->get_memory_size() < segment_memory ) { seg->set_memory_size( segment_memory ); } seg->set_offset( seg_start_pos ); } return true; } //------------------------------------------------------------------------------ bool layout_section_table() { // Simply place the section table at the end for now Elf64_Off alignmentError = current_file_pos % 4; current_file_pos += ( 4 - alignmentError ) % 4; header->set_sections_offset( current_file_pos ); return true; } //------------------------------------------------------------------------------ public: friend class Sections; class Sections { public: //------------------------------------------------------------------------------ Sections( elfio* parent_ ) : parent( parent_ ) {} //------------------------------------------------------------------------------ Elf_Half size() const { return (Elf_Half)parent->sections_.size(); } //------------------------------------------------------------------------------ section* operator[]( unsigned int index ) const { section* sec = 0; if ( index < parent->sections_.size() ) { sec = parent->sections_[index]; } return sec; } //------------------------------------------------------------------------------ section* operator[]( const std::string& name ) const { section* sec = 0; std::vector::const_iterator it; for ( it = parent->sections_.begin(); it != parent->sections_.end(); ++it ) { if ( ( *it )->get_name() == name ) { sec = *it; break; } } return sec; } //------------------------------------------------------------------------------ section* add( const std::string& name ) { section* new_section = parent->create_section(); new_section->set_name( name ); Elf_Half str_index = parent->get_section_name_str_index(); section* string_table( parent->sections_[str_index] ); string_section_accessor str_writer( string_table ); Elf_Word pos = str_writer.add_string( name ); new_section->set_name_string_offset( pos ); return new_section; } //------------------------------------------------------------------------------ std::vector::iterator begin() { return parent->sections_.begin(); } //------------------------------------------------------------------------------ std::vector::iterator end() { return parent->sections_.end(); } //------------------------------------------------------------------------------ std::vector::const_iterator begin() const { return parent->sections_.cbegin(); } //------------------------------------------------------------------------------ std::vector::const_iterator end() const { return parent->sections_.cend(); } //------------------------------------------------------------------------------ private: elfio* parent; } sections; //------------------------------------------------------------------------------ public: friend class Segments; class Segments { public: //------------------------------------------------------------------------------ Segments( elfio* parent_ ) : parent( parent_ ) {} //------------------------------------------------------------------------------ Elf_Half size() const { return (Elf_Half)parent->segments_.size(); } //------------------------------------------------------------------------------ segment* operator[]( unsigned int index ) const { return parent->segments_[index]; } //------------------------------------------------------------------------------ segment* add() { return parent->create_segment(); } //------------------------------------------------------------------------------ std::vector::iterator begin() { return parent->segments_.begin(); } //------------------------------------------------------------------------------ std::vector::iterator end() { return parent->segments_.end(); } //------------------------------------------------------------------------------ std::vector::const_iterator begin() const { return parent->segments_.cbegin(); } //------------------------------------------------------------------------------ std::vector::const_iterator end() const { return parent->segments_.cend(); } //------------------------------------------------------------------------------ private: elfio* parent; } segments; //------------------------------------------------------------------------------ private: elf_header* header; std::vector sections_; std::vector segments_; endianess_convertor convertor; Elf_Xword current_file_pos; }; } // namespace ELFIO /*** Start of inlined file: elfio_symbols.hpp ***/ #ifndef ELFIO_SYMBOLS_HPP #define ELFIO_SYMBOLS_HPP namespace ELFIO { //------------------------------------------------------------------------------ template class symbol_section_accessor_template { public: //------------------------------------------------------------------------------ symbol_section_accessor_template( const elfio& elf_file_, S* symbol_section_ ) : elf_file( elf_file_ ), symbol_section( symbol_section_ ) { find_hash_section(); } //------------------------------------------------------------------------------ Elf_Xword get_symbols_num() const { Elf_Xword nRet = 0; if ( 0 != symbol_section->get_entry_size() ) { nRet = symbol_section->get_size() / symbol_section->get_entry_size(); } return nRet; } //------------------------------------------------------------------------------ bool get_symbol( Elf_Xword index, std::string& name, Elf64_Addr& value, Elf_Xword& size, unsigned char& bind, unsigned char& type, Elf_Half& section_index, unsigned char& other ) const { bool ret = false; if ( elf_file.get_class() == ELFCLASS32 ) { ret = generic_get_symbol( index, name, value, size, bind, type, section_index, other ); } else { ret = generic_get_symbol( index, name, value, size, bind, type, section_index, other ); } return ret; } //------------------------------------------------------------------------------ bool get_symbol( const std::string& name, Elf64_Addr& value, Elf_Xword& size, unsigned char& bind, unsigned char& type, Elf_Half& section_index, unsigned char& other ) const { bool ret = false; if ( 0 != get_hash_table_index() ) { Elf_Word nbucket = *(const Elf_Word*)hash_section->get_data(); Elf_Word nchain = *(const Elf_Word*)( hash_section->get_data() + sizeof( Elf_Word ) ); Elf_Word val = elf_hash( (const unsigned char*)name.c_str() ); Elf_Word y = *(const Elf_Word*)( hash_section->get_data() + ( 2 + val % nbucket ) * sizeof( Elf_Word ) ); std::string str; get_symbol( y, str, value, size, bind, type, section_index, other ); while ( str != name && STN_UNDEF != y && y < nchain ) { y = *(const Elf_Word*)( hash_section->get_data() + ( 2 + nbucket + y ) * sizeof( Elf_Word ) ); get_symbol( y, str, value, size, bind, type, section_index, other ); } if ( str == name ) { ret = true; } } else { for ( Elf_Xword i = 0; i < get_symbols_num() && !ret; i++ ) { std::string symbol_name; if ( get_symbol( i, symbol_name, value, size, bind, type, section_index, other ) ) { if ( symbol_name == name ) { ret = true; } } } } return ret; } //------------------------------------------------------------------------------ bool get_symbol( const Elf64_Addr& value, std::string& name, Elf_Xword& size, unsigned char& bind, unsigned char& type, Elf_Half& section_index, unsigned char& other ) const { const endianess_convertor& convertor = elf_file.get_convertor(); Elf_Xword idx = 0; bool match = false; Elf64_Addr v = 0; if ( elf_file.get_class() == ELFCLASS32 ) { match = generic_search_symbols( [&]( const Elf32_Sym* sym ) { return convertor( sym->st_value ) == value; }, idx ); } else { match = generic_search_symbols( [&]( const Elf64_Sym* sym ) { return convertor( sym->st_value ) == value; }, idx ); } if ( match ) { return get_symbol( idx, name, v, size, bind, type, section_index, other ); } return false; } //------------------------------------------------------------------------------ Elf_Word add_symbol( Elf_Word name, Elf64_Addr value, Elf_Xword size, unsigned char info, unsigned char other, Elf_Half shndx ) { Elf_Word nRet; if ( symbol_section->get_size() == 0 ) { if ( elf_file.get_class() == ELFCLASS32 ) { nRet = generic_add_symbol( 0, 0, 0, 0, 0, 0 ); } else { nRet = generic_add_symbol( 0, 0, 0, 0, 0, 0 ); } } if ( elf_file.get_class() == ELFCLASS32 ) { nRet = generic_add_symbol( name, value, size, info, other, shndx ); } else { nRet = generic_add_symbol( name, value, size, info, other, shndx ); } return nRet; } //------------------------------------------------------------------------------ Elf_Word add_symbol( Elf_Word name, Elf64_Addr value, Elf_Xword size, unsigned char bind, unsigned char type, unsigned char other, Elf_Half shndx ) { return add_symbol( name, value, size, ELF_ST_INFO( bind, type ), other, shndx ); } //------------------------------------------------------------------------------ Elf_Word add_symbol( string_section_accessor& pStrWriter, const char* str, Elf64_Addr value, Elf_Xword size, unsigned char info, unsigned char other, Elf_Half shndx ) { Elf_Word index = pStrWriter.add_string( str ); return add_symbol( index, value, size, info, other, shndx ); } //------------------------------------------------------------------------------ Elf_Word add_symbol( string_section_accessor& pStrWriter, const char* str, Elf64_Addr value, Elf_Xword size, unsigned char bind, unsigned char type, unsigned char other, Elf_Half shndx ) { return add_symbol( pStrWriter, str, value, size, ELF_ST_INFO( bind, type ), other, shndx ); } //------------------------------------------------------------------------------ Elf_Xword arrange_local_symbols( std::function func = nullptr ) { int nRet = 0; if ( elf_file.get_class() == ELFCLASS32 ) { nRet = generic_arrange_local_symbols( func ); } else { nRet = generic_arrange_local_symbols( func ); } return nRet; } //------------------------------------------------------------------------------ private: //------------------------------------------------------------------------------ void find_hash_section() { hash_section = 0; hash_section_index = 0; Elf_Half nSecNo = elf_file.sections.size(); for ( Elf_Half i = 0; i < nSecNo && 0 == hash_section_index; ++i ) { const section* sec = elf_file.sections[i]; if ( sec->get_link() == symbol_section->get_index() ) { hash_section = sec; hash_section_index = i; } } } //------------------------------------------------------------------------------ Elf_Half get_string_table_index() const { return (Elf_Half)symbol_section->get_link(); } //------------------------------------------------------------------------------ Elf_Half get_hash_table_index() const { return hash_section_index; } //------------------------------------------------------------------------------ template const T* generic_get_symbol_ptr( Elf_Xword index ) const { if ( 0 != symbol_section->get_data() && index < get_symbols_num() ) { const T* pSym = reinterpret_cast( symbol_section->get_data() + index * symbol_section->get_entry_size() ); return pSym; } return nullptr; } //------------------------------------------------------------------------------ template bool generic_search_symbols( std::function match, Elf_Xword& idx ) const { for ( Elf_Xword i = 0; i < get_symbols_num(); i++ ) { const T* symPtr = generic_get_symbol_ptr( i ); if ( symPtr == nullptr ) return false; if ( match( symPtr ) ) { idx = i; return true; } } return false; } //------------------------------------------------------------------------------ template bool generic_get_symbol( Elf_Xword index, std::string& name, Elf64_Addr& value, Elf_Xword& size, unsigned char& bind, unsigned char& type, Elf_Half& section_index, unsigned char& other ) const { bool ret = false; if ( 0 != symbol_section->get_data() && index < get_symbols_num() ) { const T* pSym = reinterpret_cast( symbol_section->get_data() + index * symbol_section->get_entry_size() ); const endianess_convertor& convertor = elf_file.get_convertor(); section* string_section = elf_file.sections[get_string_table_index()]; string_section_accessor str_reader( string_section ); const char* pStr = str_reader.get_string( convertor( pSym->st_name ) ); if ( 0 != pStr ) { name = pStr; } value = convertor( pSym->st_value ); size = convertor( pSym->st_size ); bind = ELF_ST_BIND( pSym->st_info ); type = ELF_ST_TYPE( pSym->st_info ); section_index = convertor( pSym->st_shndx ); other = pSym->st_other; ret = true; } return ret; } //------------------------------------------------------------------------------ template Elf_Word generic_add_symbol( Elf_Word name, Elf64_Addr value, Elf_Xword size, unsigned char info, unsigned char other, Elf_Half shndx ) { const endianess_convertor& convertor = elf_file.get_convertor(); T entry; entry.st_name = convertor( name ); entry.st_value = value; entry.st_value = convertor( entry.st_value ); entry.st_size = size; entry.st_size = convertor( entry.st_size ); entry.st_info = convertor( info ); entry.st_other = convertor( other ); entry.st_shndx = convertor( shndx ); symbol_section->append_data( reinterpret_cast( &entry ), sizeof( entry ) ); Elf_Word nRet = symbol_section->get_size() / sizeof( entry ) - 1; return nRet; } //------------------------------------------------------------------------------ template Elf_Xword generic_arrange_local_symbols( std::function func ) { const endianess_convertor& convertor = elf_file.get_convertor(); const Elf_Xword size = symbol_section->get_entry_size(); Elf_Xword first_not_local = 1; // Skip the first entry. It is always NOTYPE Elf_Xword current = 0; Elf_Xword count = get_symbols_num(); while ( true ) { T* p1 = nullptr; T* p2 = nullptr; while ( first_not_local < count ) { p1 = const_cast( generic_get_symbol_ptr( first_not_local ) ); if ( ELF_ST_BIND( convertor( p1->st_info ) ) != STB_LOCAL ) break; ++first_not_local; } current = first_not_local + 1; while ( current < count ) { p2 = const_cast( generic_get_symbol_ptr( current ) ); if ( ELF_ST_BIND( convertor( p2->st_info ) ) == STB_LOCAL ) break; ++current; } if ( first_not_local < count && current < count ) { if ( func ) func( first_not_local, current ); // Swap the symbols T tmp; std::copy( p1, p1 + 1, &tmp ); std::copy( p2, p2 + 1, p1 ); std::copy( &tmp, &tmp + 1, p2 ); } else { // Update 'info' field of the section symbol_section->set_info( first_not_local ); break; } } // Elf_Word nRet = symbol_section->get_size() / sizeof(entry) - 1; return first_not_local; } //------------------------------------------------------------------------------ private: const elfio& elf_file; S* symbol_section; Elf_Half hash_section_index; const section* hash_section; }; using symbol_section_accessor = symbol_section_accessor_template
; using const_symbol_section_accessor = symbol_section_accessor_template; } // namespace ELFIO #endif // ELFIO_SYMBOLS_HPP /*** End of inlined file: elfio_symbols.hpp ***/ /*** Start of inlined file: elfio_note.hpp ***/ #ifndef ELFIO_NOTE_HPP #define ELFIO_NOTE_HPP namespace ELFIO { //------------------------------------------------------------------------------ // There are discrepancies in documentations. SCO documentation // (http://www.sco.com/developers/gabi/latest/ch5.pheader.html#note_section) // requires 8 byte entries alignment for 64-bit ELF file, // but Oracle's definition uses the same structure // for 32-bit and 64-bit formats. // (https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-18048.html) // // It looks like EM_X86_64 Linux implementation is similar to Oracle's // definition. Therefore, the same alignment works for both formats //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ template class note_section_accessor_template { public: //------------------------------------------------------------------------------ note_section_accessor_template( const elfio& elf_file_, S* section_ ) : elf_file( elf_file_ ), note_section( section_ ) { process_section(); } //------------------------------------------------------------------------------ Elf_Word get_notes_num() const { return (Elf_Word)note_start_positions.size(); } //------------------------------------------------------------------------------ bool get_note( Elf_Word index, Elf_Word& type, std::string& name, void*& desc, Elf_Word& descSize ) const { if ( index >= note_section->get_size() ) { return false; } const char* pData = note_section->get_data() + note_start_positions[index]; int align = sizeof( Elf_Word ); const endianess_convertor& convertor = elf_file.get_convertor(); type = convertor( *(const Elf_Word*)( pData + 2 * align ) ); Elf_Word namesz = convertor( *(const Elf_Word*)( pData ) ); descSize = convertor( *(const Elf_Word*)( pData + sizeof( namesz ) ) ); Elf_Xword max_name_size = note_section->get_size() - note_start_positions[index]; if ( namesz < 1 || namesz > max_name_size || (Elf_Xword)namesz + descSize > max_name_size ) { return false; } name.assign( pData + 3 * align, namesz - 1 ); if ( 0 == descSize ) { desc = 0; } else { desc = const_cast( pData + 3 * align + ( ( namesz + align - 1 ) / align ) * align ); } return true; } //------------------------------------------------------------------------------ void add_note( Elf_Word type, const std::string& name, const void* desc, Elf_Word descSize ) { const endianess_convertor& convertor = elf_file.get_convertor(); int align = sizeof( Elf_Word ); Elf_Word nameLen = (Elf_Word)name.size() + 1; Elf_Word nameLenConv = convertor( nameLen ); std::string buffer( reinterpret_cast( &nameLenConv ), align ); Elf_Word descSizeConv = convertor( descSize ); buffer.append( reinterpret_cast( &descSizeConv ), align ); type = convertor( type ); buffer.append( reinterpret_cast( &type ), align ); buffer.append( name ); buffer.append( 1, '\x00' ); const char pad[] = { '\0', '\0', '\0', '\0' }; if ( nameLen % align != 0 ) { buffer.append( pad, align - nameLen % align ); } if ( desc != 0 && descSize != 0 ) { buffer.append( reinterpret_cast( desc ), descSize ); if ( descSize % align != 0 ) { buffer.append( pad, align - descSize % align ); } } note_start_positions.push_back( note_section->get_size() ); note_section->append_data( buffer ); } private: //------------------------------------------------------------------------------ void process_section() { const endianess_convertor& convertor = elf_file.get_convertor(); const char* data = note_section->get_data(); Elf_Xword size = note_section->get_size(); Elf_Xword current = 0; note_start_positions.clear(); // Is it empty? if ( 0 == data || 0 == size ) { return; } Elf_Word align = sizeof( Elf_Word ); while ( current + (Elf_Xword)3 * align <= size ) { note_start_positions.push_back( current ); Elf_Word namesz = convertor( *(const Elf_Word*)( data + current ) ); Elf_Word descsz = convertor( *(const Elf_Word*)( data + current + sizeof( namesz ) ) ); current += (Elf_Xword)3 * sizeof( Elf_Word ) + ( ( namesz + align - 1 ) / align ) * (Elf_Xword)align + ( ( descsz + align - 1 ) / align ) * (Elf_Xword)align; } } //------------------------------------------------------------------------------ private: const elfio& elf_file; S* note_section; std::vector note_start_positions; }; using note_section_accessor = note_section_accessor_template
; using const_note_section_accessor = note_section_accessor_template; } // namespace ELFIO #endif // ELFIO_NOTE_HPP /*** End of inlined file: elfio_note.hpp ***/ /*** Start of inlined file: elfio_relocation.hpp ***/ #ifndef ELFIO_RELOCATION_HPP #define ELFIO_RELOCATION_HPP namespace ELFIO { template struct get_sym_and_type; template <> struct get_sym_and_type { static int get_r_sym( Elf_Xword info ) { return ELF32_R_SYM( (Elf_Word)info ); } static int get_r_type( Elf_Xword info ) { return ELF32_R_TYPE( (Elf_Word)info ); } }; template <> struct get_sym_and_type { static int get_r_sym( Elf_Xword info ) { return ELF32_R_SYM( (Elf_Word)info ); } static int get_r_type( Elf_Xword info ) { return ELF32_R_TYPE( (Elf_Word)info ); } }; template <> struct get_sym_and_type { static int get_r_sym( Elf_Xword info ) { return ELF64_R_SYM( info ); } static int get_r_type( Elf_Xword info ) { return ELF64_R_TYPE( info ); } }; template <> struct get_sym_and_type { static int get_r_sym( Elf_Xword info ) { return ELF64_R_SYM( info ); } static int get_r_type( Elf_Xword info ) { return ELF64_R_TYPE( info ); } }; //------------------------------------------------------------------------------ template class relocation_section_accessor_template { public: //------------------------------------------------------------------------------ relocation_section_accessor_template( const elfio& elf_file_, S* section_ ) : elf_file( elf_file_ ), relocation_section( section_ ) { } //------------------------------------------------------------------------------ Elf_Xword get_entries_num() const { Elf_Xword nRet = 0; if ( 0 != relocation_section->get_entry_size() ) { nRet = relocation_section->get_size() / relocation_section->get_entry_size(); } return nRet; } //------------------------------------------------------------------------------ bool get_entry( Elf_Xword index, Elf64_Addr& offset, Elf_Word& symbol, Elf_Word& type, Elf_Sxword& addend ) const { if ( index >= get_entries_num() ) { // Is index valid return false; } if ( elf_file.get_class() == ELFCLASS32 ) { if ( SHT_REL == relocation_section->get_type() ) { generic_get_entry_rel( index, offset, symbol, type, addend ); } else if ( SHT_RELA == relocation_section->get_type() ) { generic_get_entry_rela( index, offset, symbol, type, addend ); } } else { if ( SHT_REL == relocation_section->get_type() ) { generic_get_entry_rel( index, offset, symbol, type, addend ); } else if ( SHT_RELA == relocation_section->get_type() ) { generic_get_entry_rela( index, offset, symbol, type, addend ); } } return true; } //------------------------------------------------------------------------------ bool get_entry( Elf_Xword index, Elf64_Addr& offset, Elf64_Addr& symbolValue, std::string& symbolName, Elf_Word& type, Elf_Sxword& addend, Elf_Sxword& calcValue ) const { // Do regular job Elf_Word symbol; bool ret = get_entry( index, offset, symbol, type, addend ); // Find the symbol Elf_Xword size; unsigned char bind; unsigned char symbolType; Elf_Half section; unsigned char other; symbol_section_accessor symbols( elf_file, elf_file.sections[get_symbol_table_index()] ); ret = ret && symbols.get_symbol( symbol, symbolName, symbolValue, size, bind, symbolType, section, other ); if ( ret ) { // Was it successful? switch ( type ) { case R_386_NONE: // none calcValue = 0; break; case R_386_32: // S + A calcValue = symbolValue + addend; break; case R_386_PC32: // S + A - P calcValue = symbolValue + addend - offset; break; case R_386_GOT32: // G + A - P calcValue = 0; break; case R_386_PLT32: // L + A - P calcValue = 0; break; case R_386_COPY: // none calcValue = 0; break; case R_386_GLOB_DAT: // S case R_386_JMP_SLOT: // S calcValue = symbolValue; break; case R_386_RELATIVE: // B + A calcValue = addend; break; case R_386_GOTOFF: // S + A - GOT calcValue = 0; break; case R_386_GOTPC: // GOT + A - P calcValue = 0; break; default: // Not recognized symbol! calcValue = 0; break; } } return ret; } //------------------------------------------------------------------------------ bool set_entry( Elf_Xword index, Elf64_Addr offset, Elf_Word symbol, Elf_Word type, Elf_Sxword addend ) { if ( index >= get_entries_num() ) { // Is index valid return false; } if ( elf_file.get_class() == ELFCLASS32 ) { if ( SHT_REL == relocation_section->get_type() ) { generic_set_entry_rel( index, offset, symbol, type, addend ); } else if ( SHT_RELA == relocation_section->get_type() ) { generic_set_entry_rela( index, offset, symbol, type, addend ); } } else { if ( SHT_REL == relocation_section->get_type() ) { generic_set_entry_rel( index, offset, symbol, type, addend ); } else if ( SHT_RELA == relocation_section->get_type() ) { generic_set_entry_rela( index, offset, symbol, type, addend ); } } return true; } //------------------------------------------------------------------------------ void add_entry( Elf64_Addr offset, Elf_Xword info ) { if ( elf_file.get_class() == ELFCLASS32 ) { generic_add_entry( offset, info ); } else { generic_add_entry( offset, info ); } } //------------------------------------------------------------------------------ void add_entry( Elf64_Addr offset, Elf_Word symbol, unsigned char type ) { Elf_Xword info; if ( elf_file.get_class() == ELFCLASS32 ) { info = ELF32_R_INFO( (Elf_Xword)symbol, type ); } else { info = ELF64_R_INFO( (Elf_Xword)symbol, type ); } add_entry( offset, info ); } //------------------------------------------------------------------------------ void add_entry( Elf64_Addr offset, Elf_Xword info, Elf_Sxword addend ) { if ( elf_file.get_class() == ELFCLASS32 ) { generic_add_entry( offset, info, addend ); } else { generic_add_entry( offset, info, addend ); } } //------------------------------------------------------------------------------ void add_entry( Elf64_Addr offset, Elf_Word symbol, unsigned char type, Elf_Sxword addend ) { Elf_Xword info; if ( elf_file.get_class() == ELFCLASS32 ) { info = ELF32_R_INFO( (Elf_Xword)symbol, type ); } else { info = ELF64_R_INFO( (Elf_Xword)symbol, type ); } add_entry( offset, info, addend ); } //------------------------------------------------------------------------------ void add_entry( string_section_accessor str_writer, const char* str, symbol_section_accessor sym_writer, Elf64_Addr value, Elf_Word size, unsigned char sym_info, unsigned char other, Elf_Half shndx, Elf64_Addr offset, unsigned char type ) { Elf_Word str_index = str_writer.add_string( str ); Elf_Word sym_index = sym_writer.add_symbol( str_index, value, size, sym_info, other, shndx ); add_entry( offset, sym_index, type ); } //------------------------------------------------------------------------------ void swap_symbols( Elf_Xword first, Elf_Xword second ) { Elf64_Addr offset; Elf_Word symbol; Elf_Word rtype; Elf_Sxword addend; for ( Elf_Word i = 0; i < get_entries_num(); i++ ) { get_entry( i, offset, symbol, rtype, addend ); if ( symbol == first ) { set_entry( i, offset, (Elf_Word)second, rtype, addend ); } if ( symbol == second ) { set_entry( i, offset, (Elf_Word)first, rtype, addend ); } } } //------------------------------------------------------------------------------ private: //------------------------------------------------------------------------------ Elf_Half get_symbol_table_index() const { return (Elf_Half)relocation_section->get_link(); } //------------------------------------------------------------------------------ template void generic_get_entry_rel( Elf_Xword index, Elf64_Addr& offset, Elf_Word& symbol, Elf_Word& type, Elf_Sxword& addend ) const { const endianess_convertor& convertor = elf_file.get_convertor(); const T* pEntry = reinterpret_cast( relocation_section->get_data() + index * relocation_section->get_entry_size() ); offset = convertor( pEntry->r_offset ); Elf_Xword tmp = convertor( pEntry->r_info ); symbol = get_sym_and_type::get_r_sym( tmp ); type = get_sym_and_type::get_r_type( tmp ); addend = 0; } //------------------------------------------------------------------------------ template void generic_get_entry_rela( Elf_Xword index, Elf64_Addr& offset, Elf_Word& symbol, Elf_Word& type, Elf_Sxword& addend ) const { const endianess_convertor& convertor = elf_file.get_convertor(); const T* pEntry = reinterpret_cast( relocation_section->get_data() + index * relocation_section->get_entry_size() ); offset = convertor( pEntry->r_offset ); Elf_Xword tmp = convertor( pEntry->r_info ); symbol = get_sym_and_type::get_r_sym( tmp ); type = get_sym_and_type::get_r_type( tmp ); addend = convertor( pEntry->r_addend ); } //------------------------------------------------------------------------------ template void generic_set_entry_rel( Elf_Xword index, Elf64_Addr offset, Elf_Word symbol, Elf_Word type, Elf_Sxword ) { const endianess_convertor& convertor = elf_file.get_convertor(); T* pEntry = const_cast( reinterpret_cast( relocation_section->get_data() + index * relocation_section->get_entry_size() ) ); if ( elf_file.get_class() == ELFCLASS32 ) { pEntry->r_info = ELF32_R_INFO( (Elf_Xword)symbol, type ); } else { pEntry->r_info = ELF64_R_INFO( (Elf_Xword)symbol, type ); } pEntry->r_offset = offset; pEntry->r_offset = convertor( pEntry->r_offset ); pEntry->r_info = convertor( pEntry->r_info ); } //------------------------------------------------------------------------------ template void generic_set_entry_rela( Elf_Xword index, Elf64_Addr offset, Elf_Word symbol, Elf_Word type, Elf_Sxword addend ) { const endianess_convertor& convertor = elf_file.get_convertor(); T* pEntry = const_cast( reinterpret_cast( relocation_section->get_data() + index * relocation_section->get_entry_size() ) ); if ( elf_file.get_class() == ELFCLASS32 ) { pEntry->r_info = ELF32_R_INFO( (Elf_Xword)symbol, type ); } else { pEntry->r_info = ELF64_R_INFO( (Elf_Xword)symbol, type ); } pEntry->r_offset = offset; pEntry->r_addend = addend; pEntry->r_offset = convertor( pEntry->r_offset ); pEntry->r_info = convertor( pEntry->r_info ); pEntry->r_addend = convertor( pEntry->r_addend ); } //------------------------------------------------------------------------------ template void generic_add_entry( Elf64_Addr offset, Elf_Xword info ) { const endianess_convertor& convertor = elf_file.get_convertor(); T entry; entry.r_offset = offset; entry.r_info = info; entry.r_offset = convertor( entry.r_offset ); entry.r_info = convertor( entry.r_info ); relocation_section->append_data( reinterpret_cast( &entry ), sizeof( entry ) ); } //------------------------------------------------------------------------------ template void generic_add_entry( Elf64_Addr offset, Elf_Xword info, Elf_Sxword addend ) { const endianess_convertor& convertor = elf_file.get_convertor(); T entry; entry.r_offset = offset; entry.r_info = info; entry.r_addend = addend; entry.r_offset = convertor( entry.r_offset ); entry.r_info = convertor( entry.r_info ); entry.r_addend = convertor( entry.r_addend ); relocation_section->append_data( reinterpret_cast( &entry ), sizeof( entry ) ); } //------------------------------------------------------------------------------ private: const elfio& elf_file; S* relocation_section; }; using relocation_section_accessor = relocation_section_accessor_template
; using const_relocation_section_accessor = relocation_section_accessor_template; } // namespace ELFIO #endif // ELFIO_RELOCATION_HPP /*** End of inlined file: elfio_relocation.hpp ***/ /*** Start of inlined file: elfio_dynamic.hpp ***/ #ifndef ELFIO_DYNAMIC_HPP #define ELFIO_DYNAMIC_HPP namespace ELFIO { //------------------------------------------------------------------------------ template class dynamic_section_accessor_template { public: //------------------------------------------------------------------------------ dynamic_section_accessor_template( const elfio& elf_file_, S* section_ ) : elf_file( elf_file_ ), dynamic_section( section_ ) { } //------------------------------------------------------------------------------ Elf_Xword get_entries_num() const { Elf_Xword nRet = 0; if ( 0 != dynamic_section->get_entry_size() ) { nRet = dynamic_section->get_size() / dynamic_section->get_entry_size(); } return nRet; } //------------------------------------------------------------------------------ bool get_entry( Elf_Xword index, Elf_Xword& tag, Elf_Xword& value, std::string& str ) const { if ( index >= get_entries_num() ) { // Is index valid return false; } if ( elf_file.get_class() == ELFCLASS32 ) { generic_get_entry_dyn( index, tag, value ); } else { generic_get_entry_dyn( index, tag, value ); } // If the tag may have a string table reference, prepare the string if ( tag == DT_NEEDED || tag == DT_SONAME || tag == DT_RPATH || tag == DT_RUNPATH ) { string_section_accessor strsec = elf_file.sections[get_string_table_index()]; const char* result = strsec.get_string( value ); if ( 0 == result ) { str.clear(); return false; } str = result; } else { str.clear(); } return true; } //------------------------------------------------------------------------------ void add_entry( Elf_Xword tag, Elf_Xword value ) { if ( elf_file.get_class() == ELFCLASS32 ) { generic_add_entry( tag, value ); } else { generic_add_entry( tag, value ); } } //------------------------------------------------------------------------------ void add_entry( Elf_Xword tag, const std::string& str ) { string_section_accessor strsec = elf_file.sections[get_string_table_index()]; Elf_Xword value = strsec.add_string( str ); add_entry( tag, value ); } //------------------------------------------------------------------------------ private: //------------------------------------------------------------------------------ Elf_Half get_string_table_index() const { return (Elf_Half)dynamic_section->get_link(); } //------------------------------------------------------------------------------ template void generic_get_entry_dyn( Elf_Xword index, Elf_Xword& tag, Elf_Xword& value ) const { const endianess_convertor& convertor = elf_file.get_convertor(); // Check unusual case when dynamic section has no data if ( dynamic_section->get_data() == 0 || ( index + 1 ) * dynamic_section->get_entry_size() > dynamic_section->get_size() ) { tag = DT_NULL; value = 0; return; } const T* pEntry = reinterpret_cast( dynamic_section->get_data() + index * dynamic_section->get_entry_size() ); tag = convertor( pEntry->d_tag ); switch ( tag ) { case DT_NULL: case DT_SYMBOLIC: case DT_TEXTREL: case DT_BIND_NOW: value = 0; break; case DT_NEEDED: case DT_PLTRELSZ: case DT_RELASZ: case DT_RELAENT: case DT_STRSZ: case DT_SYMENT: case DT_SONAME: case DT_RPATH: case DT_RELSZ: case DT_RELENT: case DT_PLTREL: case DT_INIT_ARRAYSZ: case DT_FINI_ARRAYSZ: case DT_RUNPATH: case DT_FLAGS: case DT_PREINIT_ARRAYSZ: value = convertor( pEntry->d_un.d_val ); break; case DT_PLTGOT: case DT_HASH: case DT_STRTAB: case DT_SYMTAB: case DT_RELA: case DT_INIT: case DT_FINI: case DT_REL: case DT_DEBUG: case DT_JMPREL: case DT_INIT_ARRAY: case DT_FINI_ARRAY: case DT_PREINIT_ARRAY: default: value = convertor( pEntry->d_un.d_ptr ); break; } } //------------------------------------------------------------------------------ template void generic_add_entry( Elf_Xword tag, Elf_Xword value ) { const endianess_convertor& convertor = elf_file.get_convertor(); T entry; switch ( tag ) { case DT_NULL: case DT_SYMBOLIC: case DT_TEXTREL: case DT_BIND_NOW: value = 0; case DT_NEEDED: case DT_PLTRELSZ: case DT_RELASZ: case DT_RELAENT: case DT_STRSZ: case DT_SYMENT: case DT_SONAME: case DT_RPATH: case DT_RELSZ: case DT_RELENT: case DT_PLTREL: case DT_INIT_ARRAYSZ: case DT_FINI_ARRAYSZ: case DT_RUNPATH: case DT_FLAGS: case DT_PREINIT_ARRAYSZ: entry.d_un.d_val = convertor( value ); break; case DT_PLTGOT: case DT_HASH: case DT_STRTAB: case DT_SYMTAB: case DT_RELA: case DT_INIT: case DT_FINI: case DT_REL: case DT_DEBUG: case DT_JMPREL: case DT_INIT_ARRAY: case DT_FINI_ARRAY: case DT_PREINIT_ARRAY: default: entry.d_un.d_ptr = convertor( value ); break; } entry.d_tag = convertor( tag ); dynamic_section->append_data( reinterpret_cast( &entry ), sizeof( entry ) ); } //------------------------------------------------------------------------------ private: const elfio& elf_file; S* dynamic_section; }; using dynamic_section_accessor = dynamic_section_accessor_template
; using const_dynamic_section_accessor = dynamic_section_accessor_template; } // namespace ELFIO #endif // ELFIO_DYNAMIC_HPP /*** End of inlined file: elfio_dynamic.hpp ***/ /*** Start of inlined file: elfio_modinfo.hpp ***/ #ifndef ELFIO_MODINFO_HPP #define ELFIO_MODINFO_HPP #include #include namespace ELFIO { //------------------------------------------------------------------------------ template class modinfo_section_accessor_template { public: //------------------------------------------------------------------------------ modinfo_section_accessor_template( S* section_ ) : modinfo_section( section_ ) { process_section(); } //------------------------------------------------------------------------------ Elf_Word get_attribute_num() const { return (Elf_Word)content.size(); } //------------------------------------------------------------------------------ bool get_attribute( Elf_Word no, std::string& field, std::string& value ) const { if ( no < content.size() ) { field = content[no].first; value = content[no].second; return true; } return false; } //------------------------------------------------------------------------------ bool get_attribute( std::string field_name, std::string& value ) const { for ( auto i = content.begin(); i != content.end(); i++ ) { if ( field_name == i->first ) { value = i->second; return true; } } return false; } //------------------------------------------------------------------------------ Elf_Word add_attribute( std::string field, std::string value ) { Elf_Word current_position = 0; if ( modinfo_section ) { // Strings are addeded to the end of the current section data current_position = (Elf_Word)modinfo_section->get_size(); std::string attribute = field + "=" + value; modinfo_section->append_data( attribute + '\0' ); content.push_back( std::pair( field, value ) ); } return current_position; } //------------------------------------------------------------------------------ private: void process_section() { const char* pdata = modinfo_section->get_data(); if ( pdata ) { ELFIO::Elf_Xword i = 0; while ( i < modinfo_section->get_size() ) { while ( i < modinfo_section->get_size() && !pdata[i] ) i++; if ( i < modinfo_section->get_size() ) { std::string info = pdata + i; size_t loc = info.find( '=' ); std::pair attribute( info.substr( 0, loc ), info.substr( loc + 1 ) ); content.push_back( attribute ); i += info.length(); } } } } //------------------------------------------------------------------------------ private: S* modinfo_section; std::vector> content; }; using modinfo_section_accessor = modinfo_section_accessor_template
; using const_modinfo_section_accessor = modinfo_section_accessor_template; } // namespace ELFIO #endif // ELFIO_MODINFO_HPP /*** End of inlined file: elfio_modinfo.hpp ***/ #ifdef _MSC_VER #pragma warning( pop ) #endif #endif // ELFIO_HPP /*** End of inlined file: elfio.hpp ***/ namespace ELFIO { static struct class_table_t { const char key; const char* str; } class_table[] = { { ELFCLASS32, "ELF32" }, { ELFCLASS64, "ELF64" }, }; static struct endian_table_t { const char key; const char* str; } endian_table[] = { { ELFDATANONE, "None" }, { ELFDATA2LSB, "Little endian" }, { ELFDATA2MSB, "Big endian" }, }; static struct version_table_t { const Elf64_Word key; const char* str; } version_table[] = { { EV_NONE, "None" }, { EV_CURRENT, "Current" }, }; static struct type_table_t { const Elf32_Half key; const char* str; } type_table[] = { { ET_NONE, "No file type" }, { ET_REL, "Relocatable file" }, { ET_EXEC, "Executable file" }, { ET_DYN, "Shared object file" }, { ET_CORE, "Core file" }, }; static struct machine_table_t { const Elf64_Half key; const char* str; } machine_table[] = { { EM_NONE, "No machine" }, { EM_M32, "AT&T WE 32100" }, { EM_SPARC, "SUN SPARC" }, { EM_386, "Intel 80386" }, { EM_68K, "Motorola m68k family" }, { EM_88K, "Motorola m88k family" }, { EM_486, "Intel 80486// Reserved for future use" }, { EM_860, "Intel 80860" }, { EM_MIPS, "MIPS R3000 (officially, big-endian only)" }, { EM_S370, "IBM System/370" }, { EM_MIPS_RS3_LE, "MIPS R3000 little-endian (Oct 4 1999 Draft) Deprecated" }, { EM_res011, "Reserved" }, { EM_res012, "Reserved" }, { EM_res013, "Reserved" }, { EM_res014, "Reserved" }, { EM_PARISC, "HPPA" }, { EM_res016, "Reserved" }, { EM_VPP550, "Fujitsu VPP500" }, { EM_SPARC32PLUS, "Sun's v8plus" }, { EM_960, "Intel 80960" }, { EM_PPC, "PowerPC" }, { EM_PPC64, "64-bit PowerPC" }, { EM_S390, "IBM S/390" }, { EM_SPU, "Sony/Toshiba/IBM SPU" }, { EM_res024, "Reserved" }, { EM_res025, "Reserved" }, { EM_res026, "Reserved" }, { EM_res027, "Reserved" }, { EM_res028, "Reserved" }, { EM_res029, "Reserved" }, { EM_res030, "Reserved" }, { EM_res031, "Reserved" }, { EM_res032, "Reserved" }, { EM_res033, "Reserved" }, { EM_res034, "Reserved" }, { EM_res035, "Reserved" }, { EM_V800, "NEC V800 series" }, { EM_FR20, "Fujitsu FR20" }, { EM_RH32, "TRW RH32" }, { EM_MCORE, "Motorola M*Core // May also be taken by Fujitsu MMA" }, { EM_RCE, "Old name for MCore" }, { EM_ARM, "ARM" }, { EM_OLD_ALPHA, "Digital Alpha" }, { EM_SH, "Renesas (formerly Hitachi) / SuperH SH" }, { EM_SPARCV9, "SPARC v9 64-bit" }, { EM_TRICORE, "Siemens Tricore embedded processor" }, { EM_ARC, "ARC Cores" }, { EM_H8_300, "Renesas (formerly Hitachi) H8/300" }, { EM_H8_300H, "Renesas (formerly Hitachi) H8/300H" }, { EM_H8S, "Renesas (formerly Hitachi) H8S" }, { EM_H8_500, "Renesas (formerly Hitachi) H8/500" }, { EM_IA_64, "Intel IA-64 Processor" }, { EM_MIPS_X, "Stanford MIPS-X" }, { EM_COLDFIRE, "Motorola Coldfire" }, { EM_68HC12, "Motorola M68HC12" }, { EM_MMA, "Fujitsu Multimedia Accelerator" }, { EM_PCP, "Siemens PCP" }, { EM_NCPU, "Sony nCPU embedded RISC processor" }, { EM_NDR1, "Denso NDR1 microprocesspr" }, { EM_STARCORE, "Motorola Star*Core processor" }, { EM_ME16, "Toyota ME16 processor" }, { EM_ST100, "STMicroelectronics ST100 processor" }, { EM_TINYJ, "Advanced Logic Corp. TinyJ embedded processor" }, { EM_X86_64, "Advanced Micro Devices X86-64 processor" }, { EM_PDSP, "Sony DSP Processor" }, { EM_PDP10, "Digital Equipment Corp. PDP-10" }, { EM_PDP11, "Digital Equipment Corp. PDP-11" }, { EM_FX66, "Siemens FX66 microcontroller" }, { EM_ST9PLUS, "STMicroelectronics ST9+ 8/16 bit microcontroller" }, { EM_ST7, "STMicroelectronics ST7 8-bit microcontroller" }, { EM_68HC16, "Motorola MC68HC16 Microcontroller" }, { EM_68HC11, "Motorola MC68HC11 Microcontroller" }, { EM_68HC08, "Motorola MC68HC08 Microcontroller" }, { EM_68HC05, "Motorola MC68HC05 Microcontroller" }, { EM_SVX, "Silicon Graphics SVx" }, { EM_ST19, "STMicroelectronics ST19 8-bit cpu" }, { EM_VAX, "Digital VAX" }, { EM_CRIS, "Axis Communications 32-bit embedded processor" }, { EM_JAVELIN, "Infineon Technologies 32-bit embedded cpu" }, { EM_FIREPATH, "Element 14 64-bit DSP processor" }, { EM_ZSP, "LSI Logic's 16-bit DSP processor" }, { EM_MMIX, "Donald Knuth's educational 64-bit processor" }, { EM_HUANY, "Harvard's machine-independent format" }, { EM_PRISM, "SiTera Prism" }, { EM_AVR, "Atmel AVR 8-bit microcontroller" }, { EM_FR30, "Fujitsu FR30" }, { EM_D10V, "Mitsubishi D10V" }, { EM_D30V, "Mitsubishi D30V" }, { EM_V850, "NEC v850" }, { EM_M32R, "Renesas M32R (formerly Mitsubishi M32R)" }, { EM_MN10300, "Matsushita MN10300" }, { EM_MN10200, "Matsushita MN10200" }, { EM_PJ, "picoJava" }, { EM_OPENRISC, "OpenRISC 32-bit embedded processor" }, { EM_ARC_A5, "ARC Cores Tangent-A5" }, { EM_XTENSA, "Tensilica Xtensa Architecture" }, { EM_VIDEOCORE, "Alphamosaic VideoCore processor" }, { EM_TMM_GPP, "Thompson Multimedia General Purpose Processor" }, { EM_NS32K, "National Semiconductor 32000 series" }, { EM_TPC, "Tenor Network TPC processor" }, { EM_SNP1K, "Trebia SNP 1000 processor" }, { EM_ST200, "STMicroelectronics ST200 microcontroller" }, { EM_IP2K, "Ubicom IP2022 micro controller" }, { EM_MAX, "MAX Processor" }, { EM_CR, "National Semiconductor CompactRISC" }, { EM_F2MC16, "Fujitsu F2MC16" }, { EM_MSP430, "TI msp430 micro controller" }, { EM_BLACKFIN, "ADI Blackfin" }, { EM_SE_C33, "S1C33 Family of Seiko Epson processors" }, { EM_SEP, "Sharp embedded microprocessor" }, { EM_ARCA, "Arca RISC Microprocessor" }, { EM_UNICORE, "Microprocessor series from PKU-Unity Ltd. and MPRC of " "Peking University" }, { EM_EXCESS, "eXcess: 16/32/64-bit configurable embedded CPU" }, { EM_DXP, "Icera Semiconductor Inc. Deep Execution Processor" }, { EM_ALTERA_NIOS2, "Altera Nios II soft-core processor" }, { EM_CRX, "National Semiconductor CRX" }, { EM_XGATE, "Motorola XGATE embedded processor" }, { EM_C166, "Infineon C16x/XC16x processor" }, { EM_M16C, "Renesas M16C series microprocessors" }, { EM_DSPIC30F, "Microchip Technology dsPIC30F Digital Signal Controller" }, { EM_CE, "Freescale Communication Engine RISC core" }, { EM_M32C, "Renesas M32C series microprocessors" }, { EM_res121, "Reserved" }, { EM_res122, "Reserved" }, { EM_res123, "Reserved" }, { EM_res124, "Reserved" }, { EM_res125, "Reserved" }, { EM_res126, "Reserved" }, { EM_res127, "Reserved" }, { EM_res128, "Reserved" }, { EM_res129, "Reserved" }, { EM_res130, "Reserved" }, { EM_TSK3000, "Altium TSK3000 core" }, { EM_RS08, "Freescale RS08 embedded processor" }, { EM_res133, "Reserved" }, { EM_ECOG2, "Cyan Technology eCOG2 microprocessor" }, { EM_SCORE, "Sunplus Score" }, { EM_SCORE7, "Sunplus S+core7 RISC processor" }, { EM_DSP24, "New Japan Radio (NJR) 24-bit DSP Processor" }, { EM_VIDEOCORE3, "Broadcom VideoCore III processor" }, { EM_LATTICEMICO32, "RISC processor for Lattice FPGA architecture" }, { EM_SE_C17, "Seiko Epson C17 family" }, { EM_TI_C6000, "Texas Instruments TMS320C6000 DSP family" }, { EM_TI_C2000, "Texas Instruments TMS320C2000 DSP family" }, { EM_TI_C5500, "Texas Instruments TMS320C55x DSP family" }, { EM_res143, "Reserved" }, { EM_res144, "Reserved" }, { EM_res145, "Reserved" }, { EM_res146, "Reserved" }, { EM_res147, "Reserved" }, { EM_res148, "Reserved" }, { EM_res149, "Reserved" }, { EM_res150, "Reserved" }, { EM_res151, "Reserved" }, { EM_res152, "Reserved" }, { EM_res153, "Reserved" }, { EM_res154, "Reserved" }, { EM_res155, "Reserved" }, { EM_res156, "Reserved" }, { EM_res157, "Reserved" }, { EM_res158, "Reserved" }, { EM_res159, "Reserved" }, { EM_MMDSP_PLUS, "STMicroelectronics 64bit VLIW Data Signal Processor" }, { EM_CYPRESS_M8C, "Cypress M8C microprocessor" }, { EM_R32C, "Renesas R32C series microprocessors" }, { EM_TRIMEDIA, "NXP Semiconductors TriMedia architecture family" }, { EM_QDSP6, "QUALCOMM DSP6 Processor" }, { EM_8051, "Intel 8051 and variants" }, { EM_STXP7X, "STMicroelectronics STxP7x family" }, { EM_NDS32, "Andes Technology compact code size embedded RISC processor family" }, { EM_ECOG1, "Cyan Technology eCOG1X family" }, { EM_ECOG1X, "Cyan Technology eCOG1X family" }, { EM_MAXQ30, "Dallas Semiconductor MAXQ30 Core Micro-controllers" }, { EM_XIMO16, "New Japan Radio (NJR) 16-bit DSP Processor" }, { EM_MANIK, "M2000 Reconfigurable RISC Microprocessor" }, { EM_CRAYNV2, "Cray Inc. NV2 vector architecture" }, { EM_RX, "Renesas RX family" }, { EM_METAG, "Imagination Technologies META processor architecture" }, { EM_MCST_ELBRUS, "MCST Elbrus general purpose hardware architecture" }, { EM_ECOG16, "Cyan Technology eCOG16 family" }, { EM_CR16, "National Semiconductor CompactRISC 16-bit processor" }, { EM_ETPU, "Freescale Extended Time Processing Unit" }, { EM_SLE9X, "Infineon Technologies SLE9X core" }, { EM_L1OM, "Intel L1OM" }, { EM_INTEL181, "Reserved by Intel" }, { EM_INTEL182, "Reserved by Intel" }, { EM_res183, "Reserved by ARM" }, { EM_res184, "Reserved by ARM" }, { EM_AVR32, "Atmel Corporation 32-bit microprocessor family" }, { EM_STM8, "STMicroeletronics STM8 8-bit microcontroller" }, { EM_TILE64, "Tilera TILE64 multicore architecture family" }, { EM_TILEPRO, "Tilera TILEPro multicore architecture family" }, { EM_MICROBLAZE, "Xilinx MicroBlaze 32-bit RISC soft processor core" }, { EM_CUDA, "NVIDIA CUDA architecture " }, }; static struct section_type_table_t { const Elf64_Half key; const char* str; } section_type_table[] = { { SHT_NULL, "NULL" }, { SHT_PROGBITS, "PROGBITS" }, { SHT_SYMTAB, "SYMTAB" }, { SHT_STRTAB, "STRTAB" }, { SHT_RELA, "RELA" }, { SHT_HASH, "HASH" }, { SHT_DYNAMIC, "DYNAMIC" }, { SHT_NOTE, "NOTE" }, { SHT_NOBITS, "NOBITS" }, { SHT_REL, "REL" }, { SHT_SHLIB, "SHLIB" }, { SHT_DYNSYM, "DYNSYM" }, { SHT_INIT_ARRAY, "INIT_ARRAY" }, { SHT_FINI_ARRAY, "FINI_ARRAY" }, { SHT_PREINIT_ARRAY, "PREINIT_ARRAY" }, { SHT_GROUP, "GROUP" }, { SHT_SYMTAB_SHNDX, "SYMTAB_SHNDX " }, }; static struct segment_type_table_t { const Elf_Word key; const char* str; } segment_type_table[] = { { PT_NULL, "NULL" }, { PT_LOAD, "LOAD" }, { PT_DYNAMIC, "DYNAMIC" }, { PT_INTERP, "INTERP" }, { PT_NOTE, "NOTE" }, { PT_SHLIB, "SHLIB" }, { PT_PHDR, "PHDR" }, { PT_TLS, "TLS" }, }; static struct segment_flag_table_t { const Elf_Word key; const char* str; } segment_flag_table[] = { { 0, "" }, { 1, "X" }, { 2, "W" }, { 3, "WX" }, { 4, "R" }, { 5, "RX" }, { 6, "RW" }, { 7, "RWX" }, }; static struct symbol_bind_t { const Elf_Word key; const char* str; } symbol_bind_table[] = { { STB_LOCAL, "LOCAL" }, { STB_GLOBAL, "GLOBAL" }, { STB_WEAK, "WEAK" }, { STB_LOOS, "LOOS" }, { STB_HIOS, "HIOS" }, { STB_MULTIDEF, "MULTIDEF" }, { STB_LOPROC, "LOPROC" }, { STB_HIPROC, "HIPROC" }, }; static struct symbol_type_t { const Elf_Word key; const char* str; } symbol_type_table[] = { { STT_NOTYPE, "NOTYPE" }, { STT_OBJECT, "OBJECT" }, { STT_FUNC, "FUNC" }, { STT_SECTION, "SECTION" }, { STT_FILE, "FILE" }, { STT_COMMON, "COMMON" }, { STT_TLS, "TLS" }, { STT_LOOS, "LOOS" }, { STT_HIOS, "HIOS" }, { STT_LOPROC, "LOPROC" }, { STT_HIPROC, "HIPROC" }, }; static struct dynamic_tag_t { const Elf_Word key; const char* str; } dynamic_tag_table[] = { { DT_NULL, "NULL" }, { DT_NEEDED, "NEEDED" }, { DT_PLTRELSZ, "PLTRELSZ" }, { DT_PLTGOT, "PLTGOT" }, { DT_HASH, "HASH" }, { DT_STRTAB, "STRTAB" }, { DT_SYMTAB, "SYMTAB" }, { DT_RELA, "RELA" }, { DT_RELASZ, "RELASZ" }, { DT_RELAENT, "RELAENT" }, { DT_STRSZ, "STRSZ" }, { DT_SYMENT, "SYMENT" }, { DT_INIT, "INIT" }, { DT_FINI, "FINI" }, { DT_SONAME, "SONAME" }, { DT_RPATH, "RPATH" }, { DT_SYMBOLIC, "SYMBOLIC" }, { DT_REL, "REL" }, { DT_RELSZ, "RELSZ" }, { DT_RELENT, "RELENT" }, { DT_PLTREL, "PLTREL" }, { DT_DEBUG, "DEBUG" }, { DT_TEXTREL, "TEXTREL" }, { DT_JMPREL, "JMPREL" }, { DT_BIND_NOW, "BIND_NOW" }, { DT_INIT_ARRAY, "INIT_ARRAY" }, { DT_FINI_ARRAY, "FINI_ARRAY" }, { DT_INIT_ARRAYSZ, "INIT_ARRAYSZ" }, { DT_FINI_ARRAYSZ, "FINI_ARRAYSZ" }, { DT_RUNPATH, "RUNPATH" }, { DT_FLAGS, "FLAGS" }, { DT_ENCODING, "ENCODING" }, { DT_PREINIT_ARRAY, "PREINIT_ARRAY" }, { DT_PREINIT_ARRAYSZ, "PREINIT_ARRAYSZ" }, { DT_MAXPOSTAGS, "MAXPOSTAGS" }, }; static const ELFIO::Elf_Xword MAX_DATA_ENTRIES = 64; //------------------------------------------------------------------------------ class dump { #define DUMP_DEC_FORMAT( width ) \ std::setw( width ) << std::setfill( ' ' ) << std::dec << std::right #define DUMP_HEX_FORMAT( width ) \ std::setw( width ) << std::setfill( '0' ) << std::hex << std::right #define DUMP_STR_FORMAT( width ) \ std::setw( width ) << std::setfill( ' ' ) << std::hex << std::left public: //------------------------------------------------------------------------------ static void header( std::ostream& out, const elfio& reader ) { if ( !reader.get_header_size() ) { return; } out << "ELF Header" << std::endl << std::endl << " Class: " << str_class( reader.get_class() ) << std::endl << " Encoding: " << str_endian( reader.get_encoding() ) << std::endl << " ELFVersion: " << str_version( reader.get_elf_version() ) << std::endl << " Type: " << str_type( reader.get_type() ) << std::endl << " Machine: " << str_machine( reader.get_machine() ) << std::endl << " Version: " << str_version( reader.get_version() ) << std::endl << " Entry: " << "0x" << std::hex << reader.get_entry() << std::endl << " Flags: " << "0x" << std::hex << reader.get_flags() << std::endl << std::endl; } //------------------------------------------------------------------------------ static void section_headers( std::ostream& out, const elfio& reader ) { Elf_Half n = reader.sections.size(); if ( n == 0 ) { return; } out << "Section Headers:" << std::endl; if ( reader.get_class() == ELFCLASS32 ) { // Output for 32-bit out << "[ Nr ] Type Addr Size ES Flg Lk Inf " "Al Name" << std::endl; } else { // Output for 64-bit out << "[ Nr ] Type Addr Size " " ES Flg" << std::endl << " Lk Inf Al Name" << std::endl; } for ( Elf_Half i = 0; i < n; ++i ) { // For all sections section* sec = reader.sections[i]; section_header( out, i, sec, reader.get_class() ); } out << "Key to Flags: W (write), A (alloc), X (execute)\n\n" << std::endl; } //------------------------------------------------------------------------------ static void section_header( std::ostream& out, Elf_Half no, const section* sec, unsigned char elf_class ) { std::ios_base::fmtflags original_flags = out.flags(); if ( elf_class == ELFCLASS32 ) { // Output for 32-bit out << "[" << DUMP_DEC_FORMAT( 5 ) << no << "] " << DUMP_STR_FORMAT( 17 ) << str_section_type( sec->get_type() ) << " " << DUMP_HEX_FORMAT( 8 ) << sec->get_address() << " " << DUMP_HEX_FORMAT( 8 ) << sec->get_size() << " " << DUMP_HEX_FORMAT( 2 ) << sec->get_entry_size() << " " << DUMP_STR_FORMAT( 3 ) << section_flags( sec->get_flags() ) << " " << DUMP_HEX_FORMAT( 2 ) << sec->get_link() << " " << DUMP_HEX_FORMAT( 3 ) << sec->get_info() << " " << DUMP_HEX_FORMAT( 2 ) << sec->get_addr_align() << " " << DUMP_STR_FORMAT( 17 ) << sec->get_name() << " " << std::endl; } else { // Output for 64-bit out << "[" << DUMP_DEC_FORMAT( 5 ) << no << "] " << DUMP_STR_FORMAT( 17 ) << str_section_type( sec->get_type() ) << " " << DUMP_HEX_FORMAT( 16 ) << sec->get_address() << " " << DUMP_HEX_FORMAT( 16 ) << sec->get_size() << " " << DUMP_HEX_FORMAT( 4 ) << sec->get_entry_size() << " " << DUMP_STR_FORMAT( 3 ) << section_flags( sec->get_flags() ) << " " << std::endl << " " << DUMP_HEX_FORMAT( 4 ) << sec->get_link() << " " << DUMP_HEX_FORMAT( 4 ) << sec->get_info() << " " << DUMP_HEX_FORMAT( 4 ) << sec->get_addr_align() << " " << DUMP_STR_FORMAT( 17 ) << sec->get_name() << " " << std::endl; } out.flags( original_flags ); return; } //------------------------------------------------------------------------------ static void segment_headers( std::ostream& out, const elfio& reader ) { Elf_Half n = reader.segments.size(); if ( n == 0 ) { return; } out << "Segment headers:" << std::endl; if ( reader.get_class() == ELFCLASS32 ) { // Output for 32-bit out << "[ Nr ] Type VirtAddr PhysAddr FileSize Mem.Size " "Flags Align" << std::endl; } else { // Output for 64-bit out << "[ Nr ] Type VirtAddr PhysAddr " "Flags" << std::endl << " FileSize Mem.Size " "Align" << std::endl; } for ( Elf_Half i = 0; i < n; ++i ) { segment* seg = reader.segments[i]; segment_header( out, i, seg, reader.get_class() ); } out << std::endl; } //------------------------------------------------------------------------------ static void segment_header( std::ostream& out, Elf_Half no, const segment* seg, unsigned int elf_class ) { std::ios_base::fmtflags original_flags = out.flags(); if ( elf_class == ELFCLASS32 ) { // Output for 32-bit out << "[" << DUMP_DEC_FORMAT( 5 ) << no << "] " << DUMP_STR_FORMAT( 14 ) << str_segment_type( seg->get_type() ) << " " << DUMP_HEX_FORMAT( 8 ) << seg->get_virtual_address() << " " << DUMP_HEX_FORMAT( 8 ) << seg->get_physical_address() << " " << DUMP_HEX_FORMAT( 8 ) << seg->get_file_size() << " " << DUMP_HEX_FORMAT( 8 ) << seg->get_memory_size() << " " << DUMP_STR_FORMAT( 8 ) << str_segment_flag( seg->get_flags() ) << " " << DUMP_HEX_FORMAT( 8 ) << seg->get_align() << " " << std::endl; } else { // Output for 64-bit out << "[" << DUMP_DEC_FORMAT( 5 ) << no << "] " << DUMP_STR_FORMAT( 14 ) << str_segment_type( seg->get_type() ) << " " << DUMP_HEX_FORMAT( 16 ) << seg->get_virtual_address() << " " << DUMP_HEX_FORMAT( 16 ) << seg->get_physical_address() << " " << DUMP_STR_FORMAT( 16 ) << str_segment_flag( seg->get_flags() ) << " " << std::endl << " " << DUMP_HEX_FORMAT( 16 ) << seg->get_file_size() << " " << DUMP_HEX_FORMAT( 16 ) << seg->get_memory_size() << " " << DUMP_HEX_FORMAT( 16 ) << seg->get_align() << " " << std::endl; } out.flags( original_flags ); } //------------------------------------------------------------------------------ static void symbol_tables( std::ostream& out, const elfio& reader ) { Elf_Half n = reader.sections.size(); for ( Elf_Half i = 0; i < n; ++i ) { // For all sections section* sec = reader.sections[i]; if ( SHT_SYMTAB == sec->get_type() || SHT_DYNSYM == sec->get_type() ) { symbol_section_accessor symbols( reader, sec ); Elf_Xword sym_no = symbols.get_symbols_num(); if ( sym_no > 0 ) { out << "Symbol table (" << sec->get_name() << ")" << std::endl; if ( reader.get_class() == ELFCLASS32 ) { // Output for 32-bit out << "[ Nr ] Value Size Type Bind " "Sect Name" << std::endl; } else { // Output for 64-bit out << "[ Nr ] Value Size Type " " Bind Sect" << std::endl << " Name" << std::endl; } for ( Elf_Xword i = 0; i < sym_no; ++i ) { std::string name; Elf64_Addr value = 0; Elf_Xword size = 0; unsigned char bind = 0; unsigned char type = 0; Elf_Half section = 0; unsigned char other = 0; symbols.get_symbol( i, name, value, size, bind, type, section, other ); symbol_table( out, i, name, value, size, bind, type, section, reader.get_class() ); } out << std::endl; } } } } //------------------------------------------------------------------------------ static void symbol_table( std::ostream& out, Elf_Xword no, std::string& name, Elf64_Addr value, Elf_Xword size, unsigned char bind, unsigned char type, Elf_Half section, unsigned int elf_class ) { std::ios_base::fmtflags original_flags = out.flags(); if ( elf_class == ELFCLASS32 ) { // Output for 32-bit out << "[" << DUMP_DEC_FORMAT( 5 ) << no << "] " << DUMP_HEX_FORMAT( 8 ) << value << " " << DUMP_HEX_FORMAT( 8 ) << size << " " << DUMP_STR_FORMAT( 7 ) << str_symbol_type( type ) << " " << DUMP_STR_FORMAT( 8 ) << str_symbol_bind( bind ) << " " << DUMP_DEC_FORMAT( 5 ) << section << " " << DUMP_STR_FORMAT( 1 ) << name << " " << std::endl; } else { // Output for 64-bit out << "[" << DUMP_DEC_FORMAT( 5 ) << no << "] " << DUMP_HEX_FORMAT( 16 ) << value << " " << DUMP_HEX_FORMAT( 16 ) << size << " " << DUMP_STR_FORMAT( 7 ) << str_symbol_type( type ) << " " << DUMP_STR_FORMAT( 8 ) << str_symbol_bind( bind ) << " " << DUMP_DEC_FORMAT( 5 ) << section << " " << std::endl << " " << DUMP_STR_FORMAT( 1 ) << name << " " << std::endl; } out.flags( original_flags ); } //------------------------------------------------------------------------------ static void notes( std::ostream& out, const elfio& reader ) { Elf_Half no = reader.sections.size(); for ( Elf_Half i = 0; i < no; ++i ) { // For all sections section* sec = reader.sections[i]; if ( SHT_NOTE == sec->get_type() ) { // Look at notes note_section_accessor notes( reader, sec ); Elf_Word no_notes = notes.get_notes_num(); if ( no > 0 ) { out << "Note section (" << sec->get_name() << ")" << std::endl << " No Type Name" << std::endl; for ( Elf_Word j = 0; j < no_notes; ++j ) { // For all notes Elf_Word type; std::string name; void* desc; Elf_Word descsz; if ( notes.get_note( j, type, name, desc, descsz ) ) { // 'name' usually contains \0 at the end. Try to fix it name = name.c_str(); note( out, j, type, name ); } } out << std::endl; } } } } //------------------------------------------------------------------------------ static void modinfo( std::ostream& out, const elfio& reader ) { Elf_Half no = reader.sections.size(); for ( Elf_Half i = 0; i < no; ++i ) { // For all sections section* sec = reader.sections[i]; if ( ".modinfo" == sec->get_name() ) { // Look for the section out << "Section .modinfo" << std::endl; const_modinfo_section_accessor modinfo( sec ); for ( Elf_Word i = 0; i < modinfo.get_attribute_num(); i++ ) { std::string field; std::string value; if ( modinfo.get_attribute( i, field, value ) ) { out << " " << std::setw( 20 ) << field << std::setw( 0 ) << " = " << value << std::endl; } } out << std::endl; break; } } } //------------------------------------------------------------------------------ static void note( std::ostream& out, int no, Elf_Word type, const std::string& name ) { out << " [" << DUMP_DEC_FORMAT( 2 ) << no << "] " << DUMP_HEX_FORMAT( 8 ) << type << " " << DUMP_STR_FORMAT( 1 ) << name << std::endl; } //------------------------------------------------------------------------------ static void dynamic_tags( std::ostream& out, const elfio& reader ) { Elf_Half n = reader.sections.size(); for ( Elf_Half i = 0; i < n; ++i ) { // For all sections section* sec = reader.sections[i]; if ( SHT_DYNAMIC == sec->get_type() ) { dynamic_section_accessor dynamic( reader, sec ); Elf_Xword dyn_no = dynamic.get_entries_num(); if ( dyn_no > 0 ) { out << "Dynamic section (" << sec->get_name() << ")" << std::endl; out << "[ Nr ] Tag Name/Value" << std::endl; for ( Elf_Xword i = 0; i < dyn_no; ++i ) { Elf_Xword tag = 0; Elf_Xword value = 0; std::string str; dynamic.get_entry( i, tag, value, str ); dynamic_tag( out, i, tag, value, str, reader.get_class() ); if ( DT_NULL == tag ) { break; } } out << std::endl; } } } } //------------------------------------------------------------------------------ static void dynamic_tag( std::ostream& out, Elf_Xword no, Elf_Xword tag, Elf_Xword value, std::string str, unsigned int /*elf_class*/ ) { out << "[" << DUMP_DEC_FORMAT( 5 ) << no << "] " << DUMP_STR_FORMAT( 16 ) << str_dynamic_tag( tag ) << " "; if ( str.empty() ) { out << DUMP_HEX_FORMAT( 16 ) << value << " "; } else { out << DUMP_STR_FORMAT( 32 ) << str << " "; } out << std::endl; } //------------------------------------------------------------------------------ static void section_data( std::ostream& out, const section* sec ) { std::ios_base::fmtflags original_flags = out.flags(); out << sec->get_name() << std::endl; const char* pdata = sec->get_data(); if ( pdata ) { ELFIO::Elf_Xword i; for ( i = 0; i < std::min( sec->get_size(), MAX_DATA_ENTRIES ); ++i ) { if ( i % 16 == 0 ) { out << "[" << DUMP_HEX_FORMAT( 8 ) << i << "]"; } out << " " << DUMP_HEX_FORMAT( 2 ) << ( pdata[i] & 0x000000FF ); if ( i % 16 == 15 ) { out << std::endl; } } if ( i % 16 != 0 ) { out << std::endl; } out.flags( original_flags ); } return; } //------------------------------------------------------------------------------ static void section_datas( std::ostream& out, const elfio& reader ) { Elf_Half n = reader.sections.size(); if ( n == 0 ) { return; } out << "Section Data:" << std::endl; for ( Elf_Half i = 1; i < n; ++i ) { // For all sections section* sec = reader.sections[i]; if ( sec->get_type() == SHT_NOBITS ) { continue; } section_data( out, sec ); } out << std::endl; } //------------------------------------------------------------------------------ static void segment_data( std::ostream& out, Elf_Half no, const segment* seg ) { std::ios_base::fmtflags original_flags = out.flags(); out << "Segment # " << no << std::endl; const char* pdata = seg->get_data(); if ( pdata ) { ELFIO::Elf_Xword i; for ( i = 0; i < std::min( seg->get_file_size(), MAX_DATA_ENTRIES ); ++i ) { if ( i % 16 == 0 ) { out << "[" << DUMP_HEX_FORMAT( 8 ) << i << "]"; } out << " " << DUMP_HEX_FORMAT( 2 ) << ( pdata[i] & 0x000000FF ); if ( i % 16 == 15 ) { out << std::endl; } } if ( i % 16 != 0 ) { out << std::endl; } out.flags( original_flags ); } return; } //------------------------------------------------------------------------------ static void segment_datas( std::ostream& out, const elfio& reader ) { Elf_Half n = reader.segments.size(); if ( n == 0 ) { return; } out << "Segment Data:" << std::endl; for ( Elf_Half i = 0; i < n; ++i ) { // For all sections segment* seg = reader.segments[i]; segment_data( out, i, seg ); } out << std::endl; } private: //------------------------------------------------------------------------------ template std::string static find_value_in_table( const T& table, const K& key ) { std::string res = "?"; for ( unsigned int i = 0; i < sizeof( table ) / sizeof( table[0] ); ++i ) { if ( table[i].key == key ) { res = table[i].str; break; } } return res; } //------------------------------------------------------------------------------ template static std::string format_assoc( const T& table, const K& key ) { std::string str = find_value_in_table( table, key ); if ( str == "?" ) { std::ostringstream oss; oss << str << " (0x" << std::hex << key << ")"; str = oss.str(); } return str; } //------------------------------------------------------------------------------ template static std::string format_assoc( const T& table, const char key ) { return format_assoc( table, (const int)key ); } //------------------------------------------------------------------------------ static std::string section_flags( Elf_Xword flags ) { std::string ret = ""; if ( flags & SHF_WRITE ) { ret += "W"; } if ( flags & SHF_ALLOC ) { ret += "A"; } if ( flags & SHF_EXECINSTR ) { ret += "X"; } return ret; } //------------------------------------------------------------------------------ #define STR_FUNC_TABLE( name ) \ template static std::string str_##name( const T key ) \ { \ return format_assoc( name##_table, key ); \ } STR_FUNC_TABLE( class ) STR_FUNC_TABLE( endian ) STR_FUNC_TABLE( version ) STR_FUNC_TABLE( type ) STR_FUNC_TABLE( machine ) STR_FUNC_TABLE( section_type ) STR_FUNC_TABLE( segment_type ) STR_FUNC_TABLE( segment_flag ) STR_FUNC_TABLE( symbol_bind ) STR_FUNC_TABLE( symbol_type ) STR_FUNC_TABLE( dynamic_tag ) #undef STR_FUNC_TABLE #undef DUMP_DEC_FORMAT #undef DUMP_HEX_FORMAT #undef DUMP_STR_FORMAT }; // class dump }; // namespace ELFIO #endif // ELFIO_DUMP_HPP /*** End of inlined file: elfio_dump.hpp ***/ peony-extensions/tests/kt-test-utils/cpp-stub/stub.h0000664000175000017500000002340715156143275021624 0ustar fengfeng#ifndef __STUB_H__ #define __STUB_H__ #ifdef _WIN32 //windows #include #include #else //linux #include #include #include #endif //c #include #include //c++ #include #define ADDR(CLASS_NAME,MEMBER_NAME) (&CLASS_NAME::MEMBER_NAME) /********************************************************** replace function **********************************************************/ #ifdef _WIN32 #define CACHEFLUSH(addr, size) FlushInstructionCache(GetCurrentProcess(), addr, size) #else #define CACHEFLUSH(addr, size) __builtin___clear_cache(addr, addr + size) #endif #if defined(__aarch64__) || defined(_M_ARM64) #define CODESIZE 16U #define CODESIZE_MIN 16U #define CODESIZE_MAX CODESIZE // ldr x9, +8 // br x9 // addr #define REPLACE_FAR(t, fn, fn_stub)\ ((uint32_t*)fn)[0] = 0x58000040 | 9;\ ((uint32_t*)fn)[1] = 0xd61f0120 | (9 << 5);\ *(long long *)(fn + 8) = (long long )fn_stub;\ CACHEFLUSH((char *)fn, CODESIZE); #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) #elif defined(__arm__) || defined(_M_ARM) #define CODESIZE 8U #define CODESIZE_MIN 8U #define CODESIZE_MAX CODESIZE // ldr pc, [pc, #-4] #define REPLACE_FAR(t, fn, fn_stub)\ ((uint32_t*)fn)[0] = 0xe51ff004;\ ((uint32_t*)fn)[1] = (uint32_t)fn_stub;\ CACHEFLUSH((char *)fn, CODESIZE); #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) #elif defined(__mips64) #define CACHEFLUSH(addr, size) __builtin___clear_cache(addr, addr + size) #define CODESIZE 80U #define CODESIZE_MIN 80U #define CODESIZE_MAX CODESIZE //mips没有PC指针,所以需要手动入栈出栈 //120000ce0: 67bdffe0 daddiu sp, sp, -32 //入栈 //120000ce4: ffbf0018 sd ra, 24(sp) //120000ce8: ffbe0010 sd s8, 16(sp) //120000cec: ffbc0008 sd gp, 8(sp) //120000cf0: 03a0f025 move s8, sp //120000d2c: 03c0e825 move sp, s8 //出栈 //120000d30: dfbf0018 ld ra, 24(sp) //120000d34: dfbe0010 ld s8, 16(sp) //120000d38: dfbc0008 ld gp, 8(sp) //120000d3c: 67bd0020 daddiu sp, sp, 32 //120000d40: 03e00008 jr ra #define REPLACE_FAR(t, fn, fn_stub)\ ((uint32_t *)fn)[0] = 0x67bdffe0;\ ((uint32_t *)fn)[1] = 0xffbf0018;\ ((uint32_t *)fn)[2] = 0xffbe0010;\ ((uint32_t *)fn)[3] = 0xffbc0008;\ ((uint32_t *)fn)[4] = 0x03a0f025;\ *(uint16_t *)(fn + 20) = (long long)fn_stub >> 32;\ *(fn + 22) = 0x19;\ *(fn + 23) = 0x24;\ ((uint32_t *)fn)[6] = 0x0019cc38;\ *(uint16_t *)(fn + 28) = (long long)fn_stub >> 16;\ *(fn + 30) = 0x39;\ *(fn + 31) = 0x37;\ ((uint32_t *)fn)[8] = 0x0019cc38;\ *(uint16_t *)(fn + 36) = (long long)fn_stub;\ *(fn + 38) = 0x39;\ *(fn + 39) = 0x37;\ ((uint32_t *)fn)[10] = 0x0320f809;\ ((uint32_t *)fn)[11] = 0x00000000;\ ((uint32_t *)fn)[12] = 0x00000000;\ ((uint32_t *)fn)[13] = 0x03c0e825;\ ((uint32_t *)fn)[14] = 0xdfbf0018;\ ((uint32_t *)fn)[15] = 0xdfbe0010;\ ((uint32_t *)fn)[16] = 0xdfbc0008;\ ((uint32_t *)fn)[17] = 0x67bd0020;\ ((uint32_t *)fn)[18] = 0x03e00008;\ ((uint32_t *)fn)[19] = 0x00000000;\ CACHEFLUSH((char *)fn, CODESIZE); #define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub) #elif defined(__thumb__) || defined(_M_THUMB) #error "Thumb is not supported" #else //__i386__ _x86_64__ #define CODESIZE 13U #define CODESIZE_MIN 5U #define CODESIZE_MAX CODESIZE //13 byte(jmp m16:64) //movabs $0x102030405060708,%r11 //jmpq *%r11 static void REPLACE_FAR(void *t, char *fn, char *fn_stub) { *fn = 0x49; *(fn + 1) = 0xbb; *(long long *)(fn + 2) = (long long)fn_stub; *(fn + 10) = 0x41; *(fn + 11) = 0xff; *(fn + 12) = 0xe3; CACHEFLUSH((char *)fn, CODESIZE); } //5 byte(jmp rel32) #define REPLACE_NEAR(t, fn, fn_stub)\ *fn = 0xE9;\ *(int *)(fn + 1) = (int)(fn_stub - fn - CODESIZE_MIN);\ CACHEFLUSH((char *)fn, CODESIZE); #endif struct func_stub { char *fn; unsigned char code_buf[CODESIZE]; bool far_jmp; }; class Stub { public: Stub() { #ifdef _WIN32 SYSTEM_INFO sys_info; GetSystemInfo(&sys_info); m_pagesize = sys_info.dwPageSize; #else m_pagesize = sysconf(_SC_PAGE_SIZE); #endif if (m_pagesize < 0) { m_pagesize = 4096; } } ~Stub() { clear(); } virtual void clear() { std::map::iterator iter; struct func_stub *pstub; for(iter=m_result.begin(); iter != m_result.end(); iter++) { pstub = iter->second; #ifdef _WIN32 DWORD lpflOldProtect; if(0 != VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect)) #else if (0 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC)) #endif { if(pstub->far_jmp) { std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MAX); } else { std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MIN); } #ifdef _WIN32 VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect); #else CACHEFLUSH(pstub->fn,CODESIZE); mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC); #endif } iter->second = NULL; delete pstub; } m_result.clear(); return; } template bool set(T addr, S addr_stub) { char * fn; char * fn_stub; fn = addrof(addr); fn_stub = addrof(addr_stub); struct func_stub *pstub; std::map::iterator iter = m_result.find(fn); if (iter == m_result.end()) { pstub = new func_stub; //start pstub->fn = fn; if(distanceof(fn, fn_stub)) { pstub->far_jmp = true; std::memcpy(pstub->code_buf, fn, CODESIZE_MAX); } else { pstub->far_jmp = false; std::memcpy(pstub->code_buf, fn, CODESIZE_MIN); } } else { pstub = iter->second; pstub->far_jmp = distanceof(fn, fn_stub); } #ifdef _WIN32 DWORD lpflOldProtect; if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect)) #else if (-1 == mprotect(pageof(pstub->fn), static_cast(m_pagesize * 2), PROT_READ | PROT_WRITE | PROT_EXEC)) #endif { throw("stub set memory protect to w+r+x faild"); return false; } if(pstub->far_jmp) { REPLACE_FAR(this, fn, fn_stub); } else { REPLACE_NEAR(this, fn, fn_stub); } #ifdef _WIN32 if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect)) #else if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC)) #endif { throw("stub set memory protect to r+x failed"); return false; } m_result.insert(std::pair(fn,pstub)); return true; } template bool reset(T addr) { char * fn; fn = addrof(addr); std::map::iterator iter = m_result.find(fn); if (iter == m_result.end()) { return true; } struct func_stub *pstub; pstub = iter->second; #ifdef _WIN32 DWORD lpflOldProtect; if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect)) #else if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC)) #endif { throw("stub reset memory protect to w+r+x faild"); return false; } if(pstub->far_jmp) { std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MAX); } else { std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MIN); } #ifdef _WIN32 if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect)) #else CACHEFLUSH(pstub->fn,CODESIZE); if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC)) #endif { throw("stub reset memory protect to r+x failed"); return false; } m_result.erase(iter); delete pstub; return true; } protected: char *pageof(char* addr) { #ifdef _WIN32 return (char *)((unsigned long long)addr & ~(m_pagesize - 1)); #else return (char *)((unsigned long)addr & ~(m_pagesize - 1)); #endif } template char* addrof(T addr) { union { T _s; char* _d; }ut; ut._s = addr; return ut._d; } bool distanceof(char* addr, char* addr_stub) { std::ptrdiff_t diff = addr_stub >= addr ? addr_stub - addr : addr - addr_stub; if((sizeof(addr) > 4) && (((diff >> 31) - 1) > 0)) { return true; } return false; } protected: #ifdef _WIN32 //LLP64 long long m_pagesize; #else //LP64 long m_pagesize; #endif std::map m_result; }; #endif peony-extensions/tests/kt-test-utils/stub-ext/0000775000175000017500000000000015156143275020506 5ustar fengfengpeony-extensions/tests/kt-test-utils/stub-ext/stubext.h0000664000175000017500000000734115156143275022362 0ustar fengfeng #ifndef STUBEXT_H #define STUBEXT_H /* * Author: Zhang Yu * Maintainer: Zhang Yu * * MIT License * * Copyright (c) 2020 Zhang Yu * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ //需修改Stub的私用成员函数和成员变量为保护类型 #include "stub.h" #include "stub-shadow.h" #ifdef DEBUG_STUB_INVOKE // use to make sure the stub function is invoked. # define __DBG_STUB_INVOKE__ printf("stub at %s:%d is invoked.\n", __FILE__, __LINE__); #else # define __DBG_STUB_INVOKE__ #endif #define VADDR(CLASS_NAME, MEMBER_NAME) (typename stub_ext::VFLocator::Func)(&CLASS_NAME::MEMBER_NAME) namespace stub_ext { class StubExt : public Stub { public: StubExt() : Stub() { } template bool set_lamda(T addr, Lamda lamda) { char *fn = addrof(addr); if (m_result.find(fn) != m_result.end()) reset(addr); Wrapper *wrapper = nullptr; auto addr_stub = depictShadow(&wrapper, addr, lamda); if (set(addr, addr_stub)) { m_wrappers.insert(std::make_pair(fn, wrapper)); return true; } else { freeWrapper(wrapper); } return false; } template void reset(T addr) { Stub::reset(addr); char *fn = addrof(addr); auto iter = m_wrappers.find(fn); if (iter != m_wrappers.end()) { freeWrapper(iter->second); m_wrappers.erase(iter); } } ~StubExt() { clear(); } void clear() override { Stub::clear(); for (auto iter = m_wrappers.begin(); iter != m_wrappers.end(); ++iter) { freeWrapper(iter->second); } m_wrappers.clear(); } template static void *get_ctor_addr(bool start = true) { // the start vairable must be true, or the compiler will optimize out. if (start) goto Start; Call_Constructor: // This line of code will not be executed. // The purpose of the code is to allow the compiler to generate the assembly code that calls the constructor. T(); Start: // The address of the line of code T() obtained by assembly char *p = (char *)&&Call_Constructor; // https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html // CALL rel32 void *ret = 0; char pos; char call = 0xe8; do { pos = *p; if (pos == call) { ret = p + 5 + (*(int *)(p + 1)); } } while (!ret && (++p)); return ret; } protected: std::map m_wrappers; }; } #endif // STUBEXT_H peony-extensions/tests/kt-test-utils/stub-ext/stub-shadow.cpp0000664000175000017500000000064515156143275023457 0ustar fengfeng#include "stub-shadow.h" namespace stub_ext { WrapperMap stub_wrappers; Wrapper::Wrapper() { } Wrapper::~Wrapper() { } void freeWrapper(Wrapper *wrapper) { if (!wrapper) return; for (auto iter = stub_wrappers.begin(); iter != stub_wrappers.end();) { if (iter->second == wrapper) iter = stub_wrappers.erase(iter); else ++iter; } delete wrapper; } } peony-extensions/tests/kt-test-utils/stub-ext/stub-shadow.h0000664000175000017500000000713315156143275023123 0ustar fengfeng#ifndef STUBSHADOW_H #define STUBSHADOW_H #include #include namespace stub_ext { #define LAMDA_FUNCTION_TYPE decltype(&Lamda::operator()) class Wrapper { public: Wrapper(); virtual ~Wrapper(); }; typedef std::unordered_map WrapperMap; extern WrapperMap stub_wrappers; template class LamdaWrapper : public Wrapper { public: LamdaWrapper(Lamda func): Wrapper(),_func(func){} ~LamdaWrapper(){} Lamda _func; }; template struct VFLocator { }; template struct VFLocator { typedef Ret (*Func)(Obj*, Args...); }; template struct VFLocator { typedef Ret (*Func)(Obj*, Args...); }; template struct LamdaCaller { }; template struct LamdaCaller { template static Ret call(LamdaWrapper *wrapper, OrgArgs&&... args) { return wrapper->_func(std::forward(args)...); } }; template struct LamdaCaller { template static Ret call(LamdaWrapper *wrapper, OrgArgs&&... args) { return wrapper->_func(); } }; template struct FuncShadow { }; template struct FuncShadow { typedef Ret (*Shadow)(Args...); typedef Ret RetType; static Ret call(Args ...args) { Shadow shadow = &call; long id = (long)shadow; auto iter = stub_wrappers.find(id); assert(stub_wrappers.find(id) != stub_wrappers.end()); LamdaWrapper *wrapper = dynamic_cast *>(iter->second); return LamdaCaller::call(wrapper, args...); } }; template struct FuncShadow { typedef Ret (*Shadow)(Obj *,Args...); typedef Ret RetType; static Ret call(Obj *obj, Args ...args) { Shadow shadow = &call; long id = (long)shadow; auto iter = stub_wrappers.find(id); assert(stub_wrappers.find(id) != stub_wrappers.end()); LamdaWrapper *wrapper = dynamic_cast *>(iter->second); return LamdaCaller::call(wrapper, obj, args...); } }; template struct FuncShadow { typedef Ret (*Shadow)(Obj *,Args...); typedef Ret RetType; static Ret call(Obj *obj, Args ...args) { Shadow shadow = &call; long id = (long)shadow; auto iter = stub_wrappers.find(id); assert(stub_wrappers.find(id) != stub_wrappers.end()); LamdaWrapper *wrapper = dynamic_cast *>(iter->second); return LamdaCaller::call(wrapper, obj, args...); } }; template typename FuncShadow::Shadow depictShadow(Wrapper **wrapper, Func func, Lamda lamda) { *wrapper = new LamdaWrapper(lamda); typename FuncShadow::Shadow shadow = &FuncShadow::call; long id = (long)shadow; assert(stub_wrappers.find(id) == stub_wrappers.end()); stub_wrappers.insert(std::make_pair(id,*wrapper)); return shadow; } void freeWrapper(Wrapper *wrapper); } #endif // STUBSHADOW_H peony-extensions/tests/tests.pro0000664000175000017500000000006515156143275016067 0ustar fengfengTEMPLATE = subdirs SUBDIRS += \ extensions-test peony-extensions/peony-bluetooth-plugin/0000775000175000017500000000000015156143275017471 5ustar fengfengpeony-extensions/peony-bluetooth-plugin/bluetoothplugin.cpp0000664000175000017500000001353515156143275023430 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: tang guang * */ #include "bluetoothplugin.h" #include #include #include #include #include #include #ifdef KYLIN_COMMON #include #endif #define V10_SP1_EDU "V10SP1-edu" using namespace Peony; static QString executeLinuxCmd(QString strCmd) { QProcess p; p.start("/usr/bin/bash", QStringList() <<"-c" << strCmd); p.waitForFinished(); QString strResult = p.readAllStandardOutput(); return strResult.toLower(); } static bool IsHuawei() { QString hardwareString = executeLinuxCmd("cat /proc/cpuinfo | grep -i hardware"); QString dmidecodeString = executeLinuxCmd("dmidecode |grep \"String 4\""); if(hardwareString.length() == 0) { if(dmidecodeString.indexOf("pgu") != -1) { return true; } return false; } if(hardwareString.indexOf("huawei") != -1 || hardwareString.indexOf("pangu") != -1 || hardwareString.indexOf("kirin") != -1) { if(hardwareString.indexOf("m900") != -1 && (dmidecodeString.isEmpty() || dmidecodeString.indexOf("pwc30") == -1)) { return false; } else { return true; } } else { if(dmidecodeString.indexOf("pgu") != -1) { return true; } } return false; } static QString getDefaultAdapterAddr() { QString reply_res; QDBusInterface iface("com.ukui.bluetooth", "/com/ukui/bluetooth", "com.ukui.bluetooth", QDBusConnection::sessionBus()); QDBusPendingCall pcall = iface.asyncCall("getDefaultAdapterAddress"); pcall.waitForFinished(); QDBusMessage res = pcall.reply(); if(res.type() == QDBusMessage::ReplyMessage) { if(res.arguments().size() > 0) { reply_res = res.arguments().takeFirst().toString(); //qInfo() << reply_res; } } else { //qWarning() << res.errorName() << ": "<< res.errorMessage(); } return reply_res; } BluetoothPlugin::BluetoothPlugin(QObject *parent) : QObject(parent) { m_t = new QTranslator(this); qDebug()<<"system().name:"<load(":/translations/peony-bluetooth-plugin_"+QLocale::system().name()); QApplication::installTranslator(m_t); } BluetoothPlugin::~BluetoothPlugin() { qDebug()<< "~BluetoothPlugin"; if(nullptr != m_t) { QApplication::removeTranslator(m_t); } } QList BluetoothPlugin::menuActions(Peony::MenuPluginInterface::Types types, const QString &uri, const QStringList &selectionUris) { qDebug() << Q_FUNC_INFO << uri << selectionUris.size(); QList actions; // QProcess process; // process.start("/usr/sbin/rfkill list"); // process.waitForFinished(); // QByteArray output = process.readAllStandardOutput(); // QString str_output = output; // if(!str_output.contains(QString("bluetooth"), Qt::CaseInsensitive)) // return actions; QString m_defaultAdapterAddr = getDefaultAdapterAddr(); if (m_defaultAdapterAddr.isEmpty() || 6 != m_defaultAdapterAddr.split(":").size()) { qInfo() << __func__ << m_defaultAdapterAddr; return actions; } //华为机型不提供文件发送功能 if(IsHuawei()){ return actions; } if(!QFileInfo::exists("/usr/bin/ukui-bluetooth")){ return actions; } if (types == MenuPluginInterface::DirectoryView || types == MenuPluginInterface::DesktopWindow) { if (! selectionUris.isEmpty()) { auto info = FileInfo::fromUri(selectionUris.first()); //special type mountable, return qDebug()<<"info isVirtual:"<isVirtual()<mimeType(); if (!selectionUris.first().startsWith("file:///")) return actions; else{ if(info->mimeType().split("/").at(1) != "directory"){ QAction *compress = new QAction(QIcon::fromTheme("blueman-tray"), tr("Send from bluetooth to..."), nullptr); actions << compress; connect(compress, &QAction::triggered, [=](){ QStringList target; for (auto& str : selectionUris) { QString strDecode = Peony::FileUtils::urlDecode(str); qDebug() << Q_FUNC_INFO << str << " = " << strDecode; target << strDecode; } QDBusMessage m = QDBusMessage::createMethodCall("com.ukui.bluetooth","/com/ukui/bluetooth","com.ukui.bluetooth","setSendTransferFileMesg"); m << target; qDebug() << Q_FUNC_INFO << m.arguments().at(0).value() <<__LINE__; // 发送Message QDBusConnection::sessionBus().call(m); }); } } } } return actions; } peony-extensions/peony-bluetooth-plugin/translations/0000775000175000017500000000000015156143275022212 5ustar fengfengpeony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_ky_KG.ts0000664000175000017500000000171115156143275030277 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... عا ۅجۅتۉن كۆكچىش ارقىلۇۇ جىبەرىش .... Peony-Qt bluetooth Extension كۆكچىش ۅجۅتۉۉ Bluetooth Menu Extension bluetooth Menu Extension. bluetooth Меню кеңейтүү. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_zh_CN.ts0000664000175000017500000000203415156143275030273 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... 从蓝牙发送文件到... Peony-Qt bluetooth Extension 蓝牙插件 Bluetooth Menu Extension 蓝牙菜单扩展 bluetooth Menu Extension 蓝牙菜单扩展 bluetooth Menu Extension. 蓝牙菜单扩展. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_es.ts0000664000175000017500000000206515156143275027705 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... Enviar desde bluetooth a... Peony-Qt bluetooth Extension Extensión bluetooth Peony-Qt Bluetooth Menu Extension bluetooth Menu Extension Extensión de menú bluetooth bluetooth Menu Extension. 蓝牙菜单扩展. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_ug_CN.ts0000664000175000017500000000171415156143275030271 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... كۆكچىشتىن ئەۋەت... Peony-Qt bluetooth Extension مودەن-Qt كۆكچىش ئۇلانمىسى Bluetooth Menu Extension bluetooth Menu Extension. كۆكچىش تىزىملىكىنى ئۇزارتىش. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_fr.ts0000664000175000017500000000206315156143275027703 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... Envoyer du bluetooth vers... Peony-Qt bluetooth Extension Peony-Qt bluetooth Extension Bluetooth Menu Extension bluetooth Menu Extension Extension de menu bluetooth bluetooth Menu Extension. 蓝牙菜单扩展. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_mn.ts0000664000175000017500000000202115156143275027700 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... ᠯᠠᠨᠶᠠ ᠪᠡᠷ ᠹᠠᠢᠯ ᠢ᠋ ᠢᠯᠡᠬᠡᠬᠡᠳ... Peony-Qt bluetooth Extension ᠯᠠᠨᠶᠠ ᠤᠭᠯᠤᠷᠭ᠎ᠠ ᠲᠤᠨᠤᠭ Bluetooth Menu Extension bluetooth Menu Extension. ᠯᠠᠨᠶᠠ ᠲᠤᠪᠶᠤᠭ ᠤ᠋ᠨ ᠦᠷᠭᠡᠳᠭᠡᠯ. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_bo_CN.ts0000664000175000017500000000203715156143275030255 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... སོ་སྔོན་ནས་ཡིག་ཆ་བསྐུར་བ། Peony-Qt bluetooth Extension སོ་སྔོན་བསྒར་ལྷུ། Bluetooth Menu Extension bluetooth Menu Extension. སོ་སྔོན་འདེམས་བྱང་རྒྱ་སྐྱེད། peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_kk_KZ.ts0000664000175000017500000000172115156143275030305 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... عا حۇجاتتى كۆكچىش ارقىلى جٸبەرۋ.... Peony-Qt bluetooth Extension كۆكچىش حۇجاتى Bluetooth Menu Extension bluetooth Menu Extension. bluetooth мәзірінің кеңейтімі. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_de.ts0000664000175000017500000000206615156143275027667 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... Senden Sie von Bluetooth an... Peony-Qt bluetooth Extension Peony-Qt Bluetooth-Erweiterung Bluetooth Menu Extension bluetooth Menu Extension Bluetooth-Menüerweiterung bluetooth Menu Extension. 蓝牙菜单扩展. peony-extensions/peony-bluetooth-plugin/translations/peony-bluetooth-plugin_zh_HK.ts0000664000175000017500000000203715156143275030300 0ustar fengfeng Peony::BluetoothPlugin Send from bluetooth to... 從藍牙發送到... Peony-Qt bluetooth Extension Peony-Qt 藍牙擴展 Bluetooth Menu Extension 藍牙功能表擴展 bluetooth Menu Extension 蓝牙菜单扩展 bluetooth Menu Extension. 蓝牙菜单扩展. peony-extensions/peony-bluetooth-plugin/peony-bluetooth-plugin_global.h0000664000175000017500000000217615156143137025616 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: tang guang * */ #ifndef PEONYBLUETOOTHPLUGIN_GLOBAL_H #define PEONYBLUETOOTHPLUGIN_GLOBAL_H #include #if defined(PEONYENGRAMPAMENUPLUGIN_LIBRARY) # define PEONYQTENGRAMPAMENUPLUGINSHARED_EXPORT Q_DECL_EXPORT #else # define PEONYQTENGRAMPAMENUPLUGINSHARED_EXPORT Q_DECL_IMPORT #endif #endif // PEONYBLUETOOTHPLUGIN_GLOBAL_H peony-extensions/peony-bluetooth-plugin/bluetoothplugin.h0000664000175000017500000000433015156143275023066 0ustar fengfeng/* * Peony-Qt's Library * * Copyright (C) 2019, Tianjin KYLIN Information Technology Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: tang guang * */ #ifndef BLUETOOTHPLUGIN_H #define BLUETOOTHPLUGIN_H #include #include #include "peony-bluetooth-plugin_global.h" #include #include #include #include #include #include #include #include #include #include namespace Peony { class PEONYQTENGRAMPAMENUPLUGINSHARED_EXPORT BluetoothPlugin: public QObject, public MenuPluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID MenuPluginInterface_iid FILE "common.json") Q_INTERFACES(Peony::MenuPluginInterface) public: explicit BluetoothPlugin(QObject *parent = nullptr); virtual ~BluetoothPlugin(); PluginInterface::PluginType pluginType() override {return PluginInterface::MenuPlugin;} const QString name() override {return tr("Peony-Qt bluetooth Extension");} const QString description() override {return tr("Bluetooth Menu Extension");} const QIcon icon() override {return QIcon::fromTheme("blueman-tray");} void setEnable(bool enable) override {m_enable = enable;} bool isEnable() override {return m_enable;} QString testPlugin() override {return "test compress";} QList menuActions(Types types, const QString &uri, const QStringList &selectionUris) override; private: bool m_enable; QTranslator * m_t = nullptr; }; } #endif // BLUETOOTHPLUGIN_H peony-extensions/peony-bluetooth-plugin/peony-bluetooth-plugin.pro0000664000175000017500000000423315156143275024646 0ustar fengfeng###################################################################### # Automatically generated by qmake (3.1) Wed Dec 2 15:23:52 2020 ###################################################################### QT += widgets dbus TEMPLATE = lib TARGET = peony-bluetooth-plugin #INCLUDEPATH += . DEFINES += PEONYENGRAMPAMENUPLUGIN_LIBRARY include(../common.pri) # The following define makes your compiler warn you if you use any # feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 CONFIG += debug link_pkgconfig plugin PKGCONFIG += peony TRANSLATIONS += translations/peony-bluetooth-plugin_zh_CN.ts \ translations/peony-bluetooth-plugin_de.ts \ translations/peony-bluetooth-plugin_es.ts \ translations/peony-bluetooth-plugin_fr.ts \ translations/peony-bluetooth-plugin_kk_KZ.ts \ translations/peony-bluetooth-plugin_ug_CN.ts \ translations/peony-bluetooth-plugin_ky_KG.ts \ translations/peony-bluetooth-plugin_bo_CN.ts \ translations/peony-bluetooth-plugin_mn.ts \ translations/peony-bluetooth-plugin_zh_HK.ts #DESTDIR += ../testdir HEADERS += \ bluetoothplugin.h \ peony-bluetooth-plugin_global.h SOURCES += \ bluetoothplugin.cpp target.path = $$[QT_INSTALL_LIBS]/peony-extensions INSTALLS += target #RESOURCES += \ # peony-bluetooth-plugin.qrc CONFIG += lrelease embed_translations QM_FILES_RESOURCE_PREFIX = /translations/ SKIP_TEST = $$(EXTENSIONS_SKIP_TEST) isEmpty(SKIP_TEST) { message("build with tests") QMAKE_LFLAGS += -fprofile-arcs -ftest-coverage QMAKE_CXXFLAGS += --coverage LIBS += -lgcov } peony-extensions/NEWS0000664000175000017500000000040115156143137013527 0ustar fengfeng### peony-extensions 2.0.0 * Rewrite the plugins according to the peony-2.0.0. ### peony-extensions 1.1.0 * Initial release. * Add archive extension. ### peony-extensions 1.1.4 * Drop peony-admin since python-gi and python-caja depends python2. peony-extensions/peony-extensions.pro0000664000175000017500000000147115156143275017114 0ustar fengfengTEMPLATE = subdirs VERSION = 3.2.2 DEFINES += VERSION='\\"$${VERSION}\\"' SUBDIRS = \ # peony-admin \ peony-share \ peony-drive-rename \ peony-send-to-device \ peony-set-wallpaper \ peony-bluetooth-plugin \ peony-engrampa-menu-plugin \ peony-extension-computer-view \ peony-menu-plugin-mate-terminal \ peony-intelligent-data-management-service \ peony-intelligent-plugin \ peony-spaceview-plugin # test-computer-view # 根据环境变量设置是否编译tests子项目,默认编译 SKIP_TEST = $$(EXTENSIONS_SKIP_TEST) isEmpty(SKIP_TEST) { message("build with tests") SUBDIRS += tests } else { message("skip tests") }